Skip to content

Encrypted playback, from first principles

A six-part primer on how this app’s video is protected, how the key that unlocks it is obtained, and how three different playback paths each solve that problem differently. Written for someone who has never worked with encrypted streaming — each doc builds on the last, and each one ties the general concept back to the real files in this codebase.

Read them in order the first time through:

  1. AES-128 and the KMS — what “encrypted video” actually means here, why it is not DRM, where the key lives, and the long obfuscated journey the credentials take before they can ask for it.
  2. Online Playback — the whole path from a tap on a poster to a decrypted frame on screen, through Flutter, a Pigeon bridge, and finally ExoPlayer or AVPlayer.
  3. Offline Downloads — what actually lands on disk when you download a film, why a tiny web server runs inside the app to play it back, and why the key file needs its own MIME type.
  4. Cast and AirPlay — why a TV cannot fetch the key on its own, and the LAN proxy that signs the request on its behalf. The short version of ../casting/04 and ../casting/06.
  5. Threat Model and Gaps — what this design protects against, what it does not, and what is already fixed on a branch but not on main.
  6. The New KMS Authorization — an assessment of the incoming x-entitlement / x-content-access scheme: which of those gaps it closes, which it does not, and what it would cost this app to adopt. Proposed; not implemented.
  7. KMS Token Authorization — Implementation Plan — the shipped client-side plan for the scheme doc 6 assessed: where each token is fetched, cached and forwarded, and how each playback path was taught to carry it.
  8. The Stale KMS Token Fix — headers written at load time instead of up front, the per-title token service, and the one-shot retry after a playback error.
  9. A Fresh Entitlement Token on Every Play — PlaybackAuthResolver: every play fetches x-entitlement from the server, falling back to the cached one if that fails. Superseded by doc 10.
  10. Entitlement Token: Cache First, Refresh on Expiry — the current design: the cached token is used while its JWT is valid, refreshed shortly before exp, and forced on purchase, KMS rejection and account change. Domain repository + use cases.

The video is not DRM-protected. Every .ts segment is encrypted with plain AES-128, a scheme built into the HLS spec itself, and the 16-byte key sits behind a URL that only answers to three HMAC-signed HTTP headers. Every compliant player already knows how to fetch that key and decrypt — so the app’s entire job is to make sure those three headers are attached to the request. On the phone that is trivial. Offline, the key is fetched once at download time and replayed from disk. On a Chromecast or Apple TV it is genuinely hard, because a receiver can send no custom headers at all, and that single fact is why this app runs a web server.

main, as of August 2026. Where a fix exists on feature/anti-piracy-hardening but is not merged, doc 5 says so explicitly — nothing in docs 1–4 describes unmerged code. Doc 6 is the one forward-looking chapter: it describes a backend change that has not shipped, and is marked as such at the top.

Doc Primary files
1. AES-128 & the KMS lib/core/helper/encryption_helper.dart, lib/data/models/settings_model.dart, lib/data/datasources/settings_data_source.dart
2. Online Playback lib/presentation/widgets/player/player_media_builder.dart, lib/presentation/pages/content_pages/video_player_screen.dart, lib/presentation/widgets/player/cast/cast_aware_navigation.dart, plus ottstack_player / kvideo (path deps outside this repo)
3. Offline Downloads lib/data/services/hls/ (parser, task factory, orchestrator, playlist writer), lib/domain/services/local_server.dart, lib/data/services/local_playback_server_controller_impl.dart
4. Cast & AirPlay lib/data/services/cast/cast_lan_proxy_server_impl.dart, cast_playlist_rewriter.dart, lib/presentation/widgets/player/airplay/airplay_controller.dart
5. Threat Model all of the above, plus lib/core/security/key_file_crypto.dart on feature/anti-piracy-hardening
6. New KMS Auth nothing yet — would touch encryption_helper.dart, player_media_builder.dart, cast_lan_proxy_server_impl.dart, download_orchestrator.dart, user_auth_data_source.dart
7. Implementation lib/data/services/entitlement_service.dart, lib/data/datasources/entitlement_data_source.dart, lib/core/helper/encryption_helper.dart, lib/data/services/cast/cast_lan_proxy_server_impl.dart, lib/domain/entities/cache_content_entity.dart
8. Stale Token Fix lib/data/services/content_access_token_service.dart, lib/core/helper/token_expiry.dart, lib/data/services/entitlement_service.dart, lib/presentation/widgets/player/player_media_builder.dart, lib/presentation/pages/content_pages/video_player_screen.dart
9. Entitlement per Play lib/domain/services/playback_auth_resolver.dart, lib/data/services/playback_auth_resolver_impl.dart, lib/presentation/widgets/player/player_media_builder.dart, lib/presentation/widgets/player/cast/cast_aware_navigation.dart, lib/presentation/widgets/player/cast_controller.dart, lib/data/services/cast/cast_lan_proxy_server_impl.dart, lib/data/services/hls/download_orchestrator.dart
10. Cache-first Entitlement lib/domain/entities/entitlement_token.dart, lib/domain/repositories/entitlement_repository.dart, lib/data/repositories/entitlement_repository_impl.dart, lib/data/datasources/local/entitlement_token_store.dart, lib/data/services/entitlement_service.dart, lib/data/services/playback_auth_resolver_impl.dart
12. Playback Ownership lib/presentation/widgets/player/chorki_player_controller.dart (PlaybackLease), playback_route_observer.dart, media_copy.dart, lib/presentation/pages/content_pages/single_content_screen.dart, ottstack lib/controller/os_player_controller.dart
  • 12. Playback Ownership — the shared player’s lease API, the slow-network trailer leak behind the Subscribe page, and why ottstack’s load guard is pinned by a contract test.

  • ../casting/ — the seven-part casting primer. Doc 4 of that series is the deep version of doc 4 here; doc 6 covers AirPlay in full.

  • ../api/README.md — the REST surface. Note that the KMS does not appear there: it is not an app endpoint.