Playback
Video protection is the heart of the app. The full story is the ten-part playback deep dive; this is the summary you need before touching player code.
The one-paragraph version
Section titled “The one-paragraph version”Content is HLS AES-128 — not Widevine/FairPlay DRM (both exist in
kvideo but are never configured). Every .ts segment is encrypted with a
16-byte AES key that sits behind a URL at kms.chorki.net, and that URL only
answers to three HMAC-signed HTTP headers. Every compliant player already
knows how to fetch the key and decrypt — so the app’s entire job is making
sure those headers are attached to the request. On the phone that’s trivial;
offline it’s replayed from disk; on a Chromecast it’s genuinely hard, because
a receiver can send no custom headers at all — and that single fact is why
the app runs a local web server.
The KMS request headers
Section titled “The KMS request headers”EncryptionHelper.playerHeader() (lib/core/helper/encryption_helper.dart)
builds them:
| Header | What it is |
|---|---|
referer |
Signed referer |
vpsid |
HMAC-signed viewer/session id |
platform |
mobile |
x-entitlement / x-content-access |
KMS tokens, when token headers are enabled |
Entitlement tokens (current design)
Section titled “Entitlement tokens (current design)”The design evolved through several iterations (deep-dive docs 9–10 tell the history). The shipped behaviour is cache-first:
x-entitlementis a JWT, cached and replayed while itsexpis valid (lib/domain/entities/entitlement_token.dart, the entitlement repository- use cases).
- Refreshed shortly before
exp; forced refresh on purchase, KMS rejection and account change. x-content-accesssimilarly guards the key file itself.- Headers are written at load time, not up front — the stale-token class of bugs (deep dive 8).
The three paths
Section titled “The three paths”| Path | How the key arrives | Key files |
|---|---|---|
| Online | Player fetches it with the signed headers | player_media_builder.dart, video_player_screen.dart |
| Offline | Fetched once at download time, stored encrypted on disk, replayed by the in-app local server | lib/data/services/hls/, local_server.dart |
| Cast / AirPlay | The receiver can’t send headers → a LAN proxy inside the app signs key requests on its behalf and rewrites playlists | cast_lan_proxy_server_impl.dart, cast_playlist_rewriter.dart |
Threat model
Section titled “Threat model”What this protects against and what it doesn’t is written down honestly in Threat model and gaps — read it before proposing changes to the security model. Key-extraction attack paths are analysed in deep dive 11.