Skip to content

Casting, from first principles

A nine-part primer (casting proper, plus AirPlay) on how “casting” (Chromecast-style) actually works, and how this app implements it. Written for someone who has never touched casting before — each doc builds on the last, and each one ties the general concept back to the real files in this codebase so you can read code and theory side by side.

Read them in order the first time through:

  1. Discovery & Connection — how a phone and a TV find each other on a Wi-Fi network, and how they open a connection once found.
  2. The Wire Protocol & Sessions — what actually gets sent over that connection: CASTV2, Google’s binary protocol, and the handshake/heartbeat that keeps a session alive.
  3. Loading Media & HLS Basics — how the phone tells the TV what to play, and the HLS streaming format almost every app (including this one) uses to deliver video.
  4. DRM, Encryption & the LAN Proxy Trick — why casting protected content is harder than casting a public URL, and the clever workaround this app uses to solve it.
  5. The App’s Floating Remote UI — how Flutter lets a “remote control” bar float above the entire app, and the general compositing model (Stack, Overlay, Navigator) behind it.
  6. AirPlay: Reusing the LAN Proxy for Apple TV — how the doc‑4 proxy trick was extended to AirPlay: why Apple TV isn’t blocked by the encryption at all, what actually breaks (the app’s signed headers don’t cross the external‑playback boundary), and why the same LAN proxy is therefore the right fix. Then how the session is kept alive — the audio-session route policy the whole feature hangs on, the background pause that stopped the TV, and what “parity with Chromecast” can and cannot mean when the receiver is fed by your own process.
  7. Session Persistence & Recovery — what happens when the phone goes away but the TV doesn’t: remembering a session across app restarts, spotting a socket that has died without saying so, and the cost the doc‑4 proxy quietly added.
  8. Telling Failures Apart — why a session that was working would flash “playback failed” over a film playing perfectly well, or silently lose its subtitles for good: every error reply the receiver sent was being read as a refusal of the media. Plus the reconnect timer that hijacked the user’s new device, the sockets that leaked with it, noticing the TV-side Stop, the volume slider that was addressed to the wrong endpoint on the TV, why bytes reached an Apple TV but never a Chromecast (iOS was doing it for us), and giving the local player the same treatment the cast remote already had.
  9. Telling the Backend What the TV Is — which receiver details each protocol exposes, the privacy-safe PlaybackOutput the app sends with watch-history, Matomo and Marktag, and what is deliberately never sent.

Casting is really just: find a device on the network, open a persistent connection to it, and repeatedly tell it “play this URL” / “here’s how far you’ve gotten.” The receiver (the TV) does the actual video decoding and playback — your phone is a remote control, not a video source. Almost everything interesting in this app’s cast code exists to bridge one gap: the TV can fetch a plain public URL just fine, but the moment a URL needs authentication or a content-type fix, the phone has to become a tiny web server standing in the middle.

Doc Primary files
1. Discovery & Connection local_lib/flutter_cast/lib/discovery_service.dart, device.dart, socket.dart
2. Protocol & Sessions local_lib/flutter_cast/lib/session.dart, session_manager.dart, cast_channel/cast_channel.pb.dart
3. Loading Media & HLS lib/data/services/cast/cast_media_builder.dart, cast_messages.dart, lib/presentation/widgets/player/cast_controller.dart
4. DRM & LAN Proxy lib/data/services/cast/cast_lan_proxy_server_impl.dart, cast_playlist_rewriter.dart, lib/core/helper/encryption_helper.dart
5. App UI lib/presentation/widgets/player/cast/cast_remote_overlay.dart, cast_expanded_remote.dart, cast_mini_player.dart
6. AirPlay reuses lib/data/services/cast/cast_lan_proxy_server_impl.dart, cast_playlist_rewriter.dart, lib/core/helper/encryption_helper.dart; ios/Runner/AirPlayRouteMonitor.swift, AirPlayButtonFactory.swift, AppDelegate.swift; lib/presentation/widgets/player/airplay/, chorki_player_controller.dart, remote_playback_controller.dart, remote_playback_coordinator.dart, lib/data/services/cast/airplay_session_snapshot.dart
7. Persistence & Recovery lib/data/services/cast/cast_session_snapshot.dart, cast_session_store_impl.dart, cast_foreground_service.dart, lib/presentation/widgets/player/cast_controller.dart, android/.../CastForegroundService.kt
8. Telling Failures Apart lib/presentation/widgets/player/cast_controller.dart (_handleMessage, _beginReconnect, _applyReceiverStatus), lib/data/services/cast/cast_media_status.dart, lib/presentation/widgets/player/player_error.dart, player_controls.dart, trailer_controls.dart