From c03512aeab576a06f8d5026e5eb484897ec45f99 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 18 Sep 2026 09:47:01 +0200 Subject: feat(cast): carry subtitles to a Chromecast, on the relay's clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The relay forwards the node's fragments untouched, and those begin at zero at the seek point. The player never notices because its SourceBuffer is given `timestampOffset = start`; a receiver has no equivalent, so the cues are shifted by `-start` before they leave, recomputed at every restart of the relay. Sent as they are, a subtitle would be out by the whole seek. The document is served from the relay's own port at /subs.vtt, behind the same token as the stream and with CORS: a receiver fetches a side-loaded track with XHR from its own origin, and without the headers it fails as a network error with nothing on screen to say so. The URL carries a version because a track is cached by address — changing the cues behind a fixed URL leaves the previous language showing. Cues that end before the stream begins are dropped rather than clamped, so a line from before the seek cannot appear over the first frames after it. The relay is plain Node, so the tests start it and fetch from it rather than reading its source. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UGY17EPph5LsLzePPXhUVc --- docs/MESHBAY_DESIGN.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'docs/MESHBAY_DESIGN.md') diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 8f91a87..781438a 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -2596,6 +2596,25 @@ any LAN renderer can play; the relay is device-agnostic. Chromecast discovery an control ship. DLNA/UPnP is designed and not built: it is a second device backend beside the first, not a second relay. +**Subtitles are rebased onto the relay's clock before they are sent.** The node +extracts a track whole, so its cues carry the film's timeline, and the player +can use them unchanged because its SourceBuffer is given `timestampOffset = +start`. The relay has no such offset: it forwards the node's fragments, which +begin at zero at the seek point. A receiver is therefore sent the cues shifted +by `-start`, recomputed at every restart of the relay, and cues that end before +the stream begins are dropped rather than clamped to zero. + +**The subtitle is served from the relay's own port, behind the same token as the +stream, and with CORS.** A receiver fetches a side-loaded track with XHR from +its own origin rather than handing it to a media element, so without +`Access-Control-Allow-Origin` it fails as a network error and the film plays on +with no subtitles and no message. The stream carries the same headers, because a +receiver given a side-loaded track reads the media through the same checked +path: on one and not the other, the load fails whole. They widen nothing the +token does not already govern. The subtitle URL carries a version because a +receiver caches a track by address: changing the cues behind a fixed URL leaves +the previous language on screen. + --- ## 12. Testing posture -- cgit v1.2.3