diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-05 02:29:27 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-05 02:29:27 +0200 |
| commit | ee9798fe3b88face2fa34f14874770f509c788a5 (patch) | |
| tree | f63e9506882863a885a907796eb977ec69137718 /docs/MESHBAY_DESIGN.md | |
| parent | 243bcf017b4957237b3ddf556bd0a37296eb8798 (diff) | |
| download | meshbay-ee9798fe3b88face2fa34f14874770f509c788a5.tar.gz | |
feat(cast): music on the TV, the music bar as its remote
A cast button in Music's toolbar and in the music bar. With a television
chosen, each decrypted track goes to the relay with its cover — found as the
album card finds it — and plays there as music with its title, artist and
album; the bar's play, pause, seek, previous and next drive the receiver, its
clock is the receiver's, and the end of a track there moves the queue on.
A film or a photo taking the television pauses the bar; stopping the cast
carries the track on locally.
Photos and tracks now share one path: a whole file sent to the relay in
pieces (binary frames on Android, written to disk there), served at /file
with byte ranges and its cover at /cover, and loaded as what the relay says
it is. cast:image is gone; cast:chromecast:seek is new.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 54 |
1 files changed, 38 insertions, 16 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 64364b0..5d33f5f 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -3423,27 +3423,49 @@ pauses the local element too, which otherwise goes on fetching for nobody. The copy-URL cast has no receiver to read and keeps the ordinary player. **A television is chosen for the session, not for one film** -(`cast-session.js`). Any cast button — Videos' toolbar, the Photos toolbar, a -photo album's bar, the lightbox, the player — sets it, and it is held in memory -only. A film opened +(`cast-session.js`). Any cast button — Videos', Photos' and Music's toolbars, a +photo album's bar, the lightbox, the video player, the music bar — sets it, and +it is held in memory only. A film opened while one is set starts its cast as a restart does: pending until the first segment, then the relay and `connect` (or `reload`, when the receiver is already connected), with the player drawn as the remote from the start. *Stop casting* clears it, from the player's remote or from any button. -**A photo is served at `/image`, one at a time** (`cast:image`). The page scales -it to fit 1920×1080, applies its EXIF orientation and re-encodes it as JPEG -before it leaves: a camera original is too large for a receiver to decode in good -time, and some formats it does not decode at all. The relay starts for it if -nothing else has, serves it behind the stream's token with a versioned address -(a receiver handed the same URL twice shows what it already has), and accepts -only `image/jpeg`, `image/png` or `image/webp`. **The shell, not the page, says -what the receiver loads:** the relay's photo URL is loaded as a picture -(`streamType: NONE`, photo metadata, no subtitles), any other relay URL as the -stream, and on Android no URL but the relay's own is accepted at all. A photo -does not start the foreground service, and its load answers as soon as the -receiver accepts it: a photo never reaches PLAYING. Only the newest photo asked -for is sent; paging quickly shows where the reader stopped. +**A photo or a music track is served whole at `/file`, one at a time** +(`cast:file:begin`, `:write`, `:end`), its cover at `/cover`. It crosses in +pieces — binary frames of a megabyte on Android — because a lossless track is +too large for one bridge message, and on Android it is written to the relay's +spool directory rather than held. The relay starts for it if nothing else has, +serves it behind the stream's token with a versioned address (a receiver handed +the same URL twice shows what it already has), honours byte ranges (a receiver +seeks in a track that way), and accepts only pictures and the audio types the +music player plays. **The shell, not the page, says what the receiver loads:** +the relay's file is loaded as what the relay was told it is — a photo as a +picture (`streamType: NONE`), a track as music (`BUFFERED`, with title, artist, +album and the relay's cover URL), at `startAt` seconds for a track picked up +mid-song — any other relay URL as the stream, and on Android no URL but the +relay's own is accepted at all. A file does not start the foreground service; a +photo's load answers as soon as the receiver accepts it, since a photo never +reaches PLAYING. + +**A photo is scaled before it leaves.** The page fits it to 1920×1080, applies +its EXIF orientation and re-encodes it as JPEG: a camera original is too large +for a receiver to decode in good time, and some formats it does not decode at +all. Only the newest photo asked for is sent; paging quickly shows where the +reader stopped. + +**With a television chosen, the music bar is its remote.** Each track, once +decrypted, goes to the relay with its cover instead of to the `<audio>` +element, which keeps the source for its duration only. Play, pause, seek +(`cast:chromecast:seek`) and previous drive the receiver; the bar's clock is the +receiver's, polled once a second; and the receiver's IDLE/FINISHED moves the +queue on as `ended` does, once per track, because the receiver keeps reporting +it until it is given something else. The session records which view the +television is showing (`castSession.owner`): a film or a photo opened meanwhile +takes it, the bar pauses and stops reading the receiver, and play gives it the +track back from where it was. A television chosen mid-track picks the track up +where it was; *Stop casting* carries it on locally from where the television +was. **The interface itself is not cast.** The default media receiver plays what it is given and renders nothing of its own, and a rendered interface sent to it as |