resources/lib/c4.py — Channel 4 resolver (live + VOD)
Role
Resolves both live channels (Channel 4, E4, More4, Film4, 4seven) and on-demand programme episodes. Channel 4 wraps every stream's manifest URL with an AES-CBC-encrypted token that you decrypt client-side; the decrypted token goes into a JSON license-acquisition payload POSTed to Redbee's Widevine proxy.
Constants
AWS-hosted Redbee Media license proxy. Same endpoint for live and VOD.
Two acceptable VOD profile names returned in videoProfiles.
We try the first one (the bigscreendash — TV-optimized), fall back to
dashwv-, finally to anything containing "dash".
A static Basic-Auth credential — same as the one Channel 4's web app
uses for its token request. Unused by the resolver today (because
_get_access_token() always returns None at line
32); here for the future path where the addon supports authenticated C4
content (some shows require sign-in). The base64 blob decodes to a
client_id:client_secret string for the C4 OAuth flow.
Two AES-CBC key/IV pairs — different per C4 "client" persona (amazonfire-dash = the Fire TV app's keys, web = the channel4.com web keys). Hard-coded (reverse-engineered from the C4 player JS). When C4 rotate these, all C4 playback breaks and the addon needs a key bump.
Always returns None. There's an OAuth flow scaffolded but
not wired — for now C4 content (UK-IP geofenced) plays anonymous as
the "web" client. When you authenticate in the future, this is the hook
point.
Live resolver
Steps:
- If a non-
Noneaccess token exists, use theamazonfire-dashclient + the live-API URL; otherwise useclient="web"+ the live-WEB URL (https://www.channel4.com/simulcast/channels/). - Fetch the JSON. Read
channelInfo.videoProfiles(the modern shape) or the baredata.videoProfiles(the legacy shape — line 48'sdata.get("channelInfo", data)). - Look for a profile with name "dashwv-live-stream-iso-dash-sp-tl" — the standard C4 live DASH+Widevine profile. Fall back to anything with "dash" in the name verbatim.
- Pick
streams[0].token(encrypted) andstreams[0].uri(manifest URL). - Decrypt the token with the right client's AES key+IV via
PyCryptodome (line 65). The decrypted form is either
…&t=(regex match) or… …|(split on|… |) — the resolver handles both. - Build the JSON license payload:
{"token":, "video": {"type": "simulcast", "url": }, "message": "b{SSM}"} - The "b{SSM}" message tells Redbee to
expect a binary-prefixed Widevine challenge; IA will substitute
{SSM}with the actual challenge at license time. - Hand to
setup_dash_item()withlicense_payload=payload— this triggers the "JBlicense" terminal token (vs the usual "R{SSM}") because we're sending the payload verbatim.
The stream_url passed to setup_dash_item as
setPath() at the same time is the unmodified URL C4 returned.
That URL contains query-string params tying the manifest to this token, so
it can't be reused for any other request — Kodi will only load it
once.
VOD resolver
Same shape as live but against the VOD endpoint
https://www.channel4.com/vod/stream/. The decrypted
token is just split |[1] (VOD tokens don't use the
&t= form). The license payload uses
"type": "ondemand" and includes "request_id":
asset_id or programme_id so Redbee can correlate the request.
asset_id optional, programme_id required
If asset_id isn't supplied, the payload falls back to
programme_id as the request_id. In practice this
happens for C4 movies (single-asset programmes) where the caller didn't
fetch the asset id. Multi-asset programmes (series with multiple versions
— signed, audio-described) need the asset_id for Redbee to find the right
license.