resources/lib/stv.py — STV live resolver
Role
Resolves the two STV live channels — STV (the Scottish ITV affiliate) and STV +1 — to a playable manifest. The STV player API returns multiple streams targeted at different devices; this module picks the best one and configures the listitem appropriately (DASH vs HLS).
Constants
The single endpoint. STV is one of the few UK providers with a tidy public channel-info API.
Two non-obvious headers:
- "stv-drm": "true" — tells STV's API to serve the DRM-protected
DASH manifest (with embedded Widevine PSSH) rather than the clear-AVC
mobile stream.
setup_dash_item()then receives a manifest whose Widevine LICENSE URL is inside the MPD itself, so we don't explicitly pass alicense_url. - User-Agent: "" — empty on this dict; the resolver populates
it with
get_ua()at runtime.
Order of preference among the streams[*].device values.
fvp_dash (Freeview Play) is the highest-resolution stream STV
exposes; desktop is the web tier; mobile is the
least-preferred fallback. If none of the known device names match,
min(... key=DEVICE_PRIORITY.get(s, 99)) uses 99 → that
unknown stream ends up last but is still selected if it's the only one.
Resolver
Note one quirks upfront: line 13 rewrites stv_plusone
to stv-plus-1 — these are the two channel_ids the addon
exposes, but STV's API expects "stv-plus-1" for the +1. Then:
- Build headers from the HEADERS template with a populated UA.
- HTTP GET the LIVE_API.
- Pick the stream with the lowest device-priority via
min(streams, key=…). - Inspect the
streamUrl: if it ends in.m3u8or contains.m3u8?, treat as HLS; otherwise MPD. - HLS path: bypass
setup_dash_itemand set the four listitem properties manually (setPath,inputstream,inputstream.adaptive.manifest_type=hls) — becausesetup_dash_itemdefaults to DASH license handling, which isn't appropriate for the simple HLS case. - DASH path: call
setup_dash_itemwithmanifest_type="mpd", no license URL (Widevine info is in the MPD itself, picked up by inputstream.adaptive's PSSH parser).
If data.results.streams is empty, the resolver raises
"STV: No streams found". The caller in
default.py:_resolve_channel
wraps in try/except and shows a notification dialog, so the user gets
"STV: No streams found" as a popup. There's no separate error-code
path for "outside UK geofence" — STV just returns zero streams in that
case, so the failure mode is identical to "service is down".