Two authorities, two kinds of refusal
An app that plays protected video usually splits the decision. Its own backend checks the account and returns playback material: a manifest URL, a licence URL, a token, a playback session identifier. The licence server then checks the request that carries that material and decides whether to issue keys. The two authorities fail differently, and telling them apart is the difference between debugging your client and debugging the account.
An HTTP error with a JSON body from the app's backend points at the entitlement call. A successful licence POST whose response the player rejects points at the licence server or the CDM. A licence POST that never happens usually means the entitlement call did not return what the player expected.
The shape of the flow
Most applications follow a recognisable order, though not every step appears in every app:
- The user selects a title, and the app calls its backend with the content identifier and the account context.
- The backend returns playback material, which may include a manifest URL, a licence URL, a token, and a session identifier that ties the playback to that account and title.
- The player fetches the manifest, which carries the protection metadata for each track.
- For each protected track, the CDM produces a key request and the app posts it to the licence URL with the token and identifiers attached.
- The player fetches media segments, often with per-request signing or a CDN token in the query string.
- Progress, heartbeat or concurrency calls run in parallel, and some of them refresh the playback session.
The licence call sits in the middle of that sequence. Its authorization usually comes from a call that happened seconds earlier, which is why tracing forward from the licence request is often easier than guessing where the token originates.
Start at the licence POST and walk backwards
Capture or hook the licence request and record its complete header set. Then find the code that wrote each header, following the value rather than the endpoint. In an app built on Media3 or ExoPlayer, the headers travel through the DRM configuration or through the HTTP callback, so the assignment may sit far from the playback screen.
- Headers whose names match the app's API conventions were probably set by a shared interceptor or session helper.
- Headers containing a long random value are usually tokens that the entitlement call returned.
- Query parameters naming a session or playback identifier were assembled when the title was selected.
- A header that changes between requests indicates a value computed per call rather than stored.
The same walk applies to the licence URL. When it comes from the backend response, the entitlement call is authoritative and the URL should be treated as a value to fetch rather than a constant to hardcode. When it is a literal in the package, the app has only one licence host and the interesting variation is in the token.
Tokens bound to playback, not to the account
Playback tokens behave differently from login tokens. They are usually short lived, scoped to one title or one playback session, and sometimes single use. A client that caches one and reuses it an hour later fails in a way that looks like a licence server problem.
Concurrency causes a related failure. A player with separate audio and video tracks can send two licence requests close together. If the first request consumes the token, the second is refused. Watching the order of those two requests in a trace explains an intermittent failure that is otherwise hard to reproduce, and any rebuilt client needs to serialise the calls or request a token that covers both.
What to observe on the device
Four observation points cover most of the flow. The app's HTTP layer shows control calls and their tokens. The MediaDrm key request and key response methods show the CDM boundary and the moment a licence is accepted or refused. Session state and key status callbacks show renewals, which tell you the licence lifetime the server issued. The file that stores the playback state shows whether the app keeps anything across restarts, and a stored key set identifier indicates offline playback support.
Time the observations. The interval between the entitlement call and the licence request, and between the licence and the first segment, is part of the flow. A rebuilt client that ignores those intervals can work in testing and fail against a server that expects a session to progress.
Rebuilding the authorisation layer
The deliverable is a client that performs the app's authorisation steps in the right order with fresh values: request playback material, use the returned licence URL and token, present the correct identifiers, handle a refusal, and refresh before expiry. That client sits alongside the device's DRM rather than replacing it, since key handling stays in the CDM. Keeping the token logic separate from the playback logic makes the client testable, because a failure then names the step that returned an unexpected value.
Related work
Reviewed 28 September 2026 · SReverse research desk