From ee9798fe3b88face2fa34f14874770f509c788a5 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 5 Oct 2026 02:29:27 +0200 Subject: feat(cast): music on the TV, the music bar as its remote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/MESHBAY_DESIGN.md | 54 +++++++++++++++++++++++++++++++++++--------------- docs/USERGUIDE.md | 27 +++++++++++++++---------- 2 files changed, 55 insertions(+), 26 deletions(-) (limited to 'docs') 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 `