3. Loading Media & HLS Basics
3. Loading Media & HLS Basics
Section titled “3. Loading Media & HLS Basics”Where we left off
Section titled “Where we left off”Doc 2 ended with a LOAD message flowing over the media namespace, and a
MEDIA_STATUS message flowing back. This doc is about what’s actually
inside that LOAD message, and the video streaming format almost every
app — including this one — uses to make it possible.
The most important idea in this whole doc
Section titled “The most important idea in this whole doc”The TV does not “download the video from your phone.” Your phone tells the TV a URL, and the TV fetches that URL directly from the internet (or your local network), completely independently of the phone, using its own network connection. Your phone is a remote control, not a video source. This single fact explains almost every design decision in this app’s cast code — including the whole of doc 4.
❌ NOT what happens: ✅ what actually happens:
Phone ──(video bytes)──► TV Phone ──(a URL string)──► TV (phone has to stream │ the whole video itself) ▼ TV ──(fetches the URL itself)──► CDN (phone is done — just sent one small instruction)The LOAD message
Section titled “The LOAD message”Here’s a simplified version of what actually gets sent (see doc 2 for the envelope it travels in):
{ "type": "LOAD", "autoplay": true, "currentTime": 0, "media": { "contentId": "https://cdn.example.com/movie/master.m3u8", "contentType": "application/x-mpegURL", "streamType": "BUFFERED", "duration": 5400, "metadata": { "title": "Some Movie", "images": [...] }, "tracks": [ /* subtitle tracks, see below */ ] }}contentId— the URL. This is the one field that matters most: everything else is metadata dressing.contentType— the MIME type, telling the receiver’s player how to interpret that URL.application/x-mpegURLmeans “this is an HLS playlist,” as opposed to, say, a directvideo/mp4file.streamType—BUFFERED(a normal video-on-demand with a seekable timeline) vsLIVE(a live stream, where showing a seek bar the user can scrub to “the end” doesn’t make sense).tracks— optional sideloaded subtitle tracks (more below).
The standard: HLS (HTTP Live Streaming)
Section titled “The standard: HLS (HTTP Live Streaming)”contentType: application/x-mpegURL names the format this app (and most
streaming apps) actually uses: HLS, an Apple-originated but now
near-universal standard for delivering video over plain HTTP. The core
trick of HLS is that a “video” isn’t one file — it’s a small text
manifest that lists a sequence of much smaller video chunks (segments,
typically ~4-10 seconds each), plus enough metadata for a player to
request them in order and stitch them together seamlessly.
There are two levels of manifest:
master.m3u8 (the "menu" — lists quality options) │ ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ 1080p/media.m3u8 720p/media.m3u8 audio/media.m3u8 (the actual segment list for that one rendition) │ ▼ segment_000.ts segment_001.ts segment_002.ts ... (a few seconds of video each, fetched one at a time as playback advances)- The master playlist doesn’t contain any video — it’s a menu of available renditions (1080p, 720p, audio-only, etc.), each tagged with its bitrate and resolution, so the player can pick one based on available bandwidth (this is the “adaptive” in “adaptive bitrate streaming” — a player can switch renditions mid-playback if the network gets worse).
- Each media (variant) playlist is the “real” list: a sequence of
segment URLs (often relative, like
segment_000.ts) plus timing info. - Segments are the actual video bytes, usually MPEG-TS (
.ts) containers, fetched one at a time as playback progresses — which is exactly why HLS streams well over flaky networks: losing one segment is a small, recoverable blip, not a dropped connection to one giant file.
Subtitles: a second kind of “track”
Section titled “Subtitles: a second kind of “track””Separately from the video/audio in the manifest, LOAD can list
sideloaded subtitle tracks — WebVTT files the receiver overlays on top
of the video, independent of whatever the HLS manifest itself contains:
"tracks": [ { "trackId": 1, "type": "TEXT", "subtype": "SUBTITLES", "trackContentId": "https://cdn.example.com/subs/en.vtt", "trackContentType": "text/vtt", "language": "en", "name": "English" }]One practical constraint worth knowing: the Chromecast Default Media
Receiver only renders WebVTT (and TTML) — not .srt, .ass, or other
common subtitle formats. An app has to filter those out before offering
them, or the receiver may reject the whole LOAD.
The feedback loop: MEDIA_STATUS
Section titled “The feedback loop: MEDIA_STATUS”Once loaded, the receiver periodically (and on every state change) reports
back a MEDIA_STATUS message:
{ "type": "MEDIA_STATUS", "status": [{ "mediaSessionId": 7, "playerState": "PLAYING", "currentTime": 145.2, "media": { "contentId": "...", "duration": 5400 } }]}mediaSessionId is worth calling out specifically: once a LOAD succeeds,
every follow-up command (pause, seek, change subtitle track) has to
reference that specific session id — it’s how the receiver disambiguates
“which currently-loaded video are you talking about,” in case a queue or a
reload is in flight.
Queues: casting more than one item
Section titled “Queues: casting more than one item”For series playback (autoplay-next), CASTV2 has a QUEUE_LOAD variant of
LOAD that hands over a whole ordered list of items up front, so the
receiver can advance to the next episode on its own without the phone
needing to send a fresh LOAD the moment one episode ends (which would
cause a visible stall at every episode boundary).
Where this lives in the code
Section titled “Where this lives in the code”lib/data/services/cast/cast_media_builder.dart—CastMediaBuilder.mediaFor()builds themediaobject above: pickscontentType, computesstreamTypefrom whether the content is a live stream, builds themetadatablock, and filters subtitle tracks down to ones the receiver can actually render.lib/data/services/cast/cast_messages.dart— wraps thatmediaobject into the fullLOAD/QUEUE_LOADJSON payload.lib/data/services/cast/cast_media_status.dart— parses incomingMEDIA_STATUSback into a typed Dart object.lib/presentation/widgets/player/cast_controller.dart— ties it together: dispatches the load, polls status, and exposesqualityVariants/subtitleTracks/activeSubtitleTrackfor the UI.
The takeaway
Section titled “The takeaway”Casting a video is, at its core, “send the TV a URL and a MIME type, then listen for status updates.” HLS is the format that URL almost always points to, precisely because it’s just plain HTTP + a text manifest + small segment files — nothing about it requires the TV and the phone to have any special relationship beyond “both can reach the same URL over HTTP.” Which raises the obvious next question: what happens when the phone can reach that URL (because it’s logged in, or has a special header) but the TV can’t? That’s doc 4.