Fix Viaplay live and catch-up play.

Allow 48h catch-up instead of rejecting ended events, and fetch keys via bootstrap PSSH when the live MPD has none.
This commit is contained in:
Jos Vooges | STH 2026-09-28 03:36:08 +02:00
parent b783ba137f
commit 7cdff2bde4
4 changed files with 52 additions and 5 deletions

View file

@ -24,8 +24,10 @@ def main():
or "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36",
"Content-Type": "application/octet-stream",
}
lic_url = data.get("licUrl") or ""
auth = (data.get("licAuth") or "").strip()
if auth:
# Token zit al in de URL — extra Authorization/Cookie geeft 431/403 (werkend script).
if auth and "token=" not in lic_url and "auth=" not in lic_url:
headers["Authorization"] = f"Basic {auth}"
response = requests.post(
data["licUrl"],

View file

@ -1242,6 +1242,32 @@ export async function fetchMpdXml(
return { status: response.status, xml, url };
}
/**
* Live MPD's zijn soms zonder PSSH. Het werkende Viaplay-script gebruikt
* dan de Widevine-PSSH van een bekende Xtra-zender voor de license-challenge.
*/
const VIAPLAY_BOOTSTRAP_MPD =
"https://live-dash-cdn7-cf-mp-v2.cdn.viaplay.tv/mediapkg-prod-eu-north-1/vp-xtra16/vp-xtra16-dashwv/index.mpd";
let bootstrapPsshCache: string | null = null;
export async function fetchViaplayBootstrapPssh(
timeoutMs: number
): Promise<string | null> {
if (bootstrapPsshCache) return bootstrapPsshCache;
try {
const res = await fetchMpdXml(VIAPLAY_BOOTSTRAP_MPD, Math.min(timeoutMs, 15_000));
const pssh = extractPsshFromMpd(res.xml);
if (pssh) {
bootstrapPsshCache = pssh;
return pssh;
}
} catch {
/* next play mag opnieuw proberen */
}
return null;
}
export function extractPsshFromMpd(xml: string): string | null {
const widevine = xml.match(
/<ContentProtection\b[^>]*schemeIdUri=["']urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed["'][\s\S]*?<\/ContentProtection>/i

View file

@ -13,6 +13,7 @@ import {
resolveKapMpd,
fetchMpdXml,
extractPsshFromMpd,
fetchViaplayBootstrapPssh,
VIAPLAY_USER_AGENT,
} from "./client";
import {
@ -243,10 +244,15 @@ export async function ensureViaplayEventReady(
let keys = cachedKeys;
const wantKeys = opts?.fetchKeys ?? tweaks.fetchKeysIfMissing;
if (wantKeys && kap.pssh && play.licenseUrl && !keys.length) {
if (wantKeys && play.licenseUrl && !keys.length) {
const pssh =
kap.pssh || (await fetchViaplayBootstrapPssh(tweaks.httpTimeoutMs));
if (!pssh) {
throw new Error("Geen PSSH (MPD + bootstrap) — keys niet op te halen");
}
const extracted = await extractViaplayKeys({
wvdPath: settings.wvdPath,
pssh: kap.pssh,
pssh,
licUrl: play.licenseUrl,
licAuth: play.licenseAuth,
userAgent: VIAPLAY_USER_AGENT,
@ -275,6 +281,10 @@ export async function ensureViaplayEventReady(
}
}
if (play.licenseUrl && !keys.length) {
throw new Error("Viaplay-licentie aanwezig maar geen keys");
}
await patchViaplayEventCache(mediaGuid, {
mpdUrl: kap.mpdUrl,
mpdCachedAt: new Date().toISOString(),
@ -409,7 +419,13 @@ export async function resolveViaplayGuidPlay(
// Clear VOD (HAR: geen license) — nooit stale ClearKey van eerdere plays plakken.
if (!play.licenseUrl) {
keys = [];
} else if (wantKeys && pssh && !keys.length) {
} else if (wantKeys && !keys.length) {
if (!pssh) {
pssh = await fetchViaplayBootstrapPssh(tweaks.httpTimeoutMs);
}
if (!pssh) {
throw new Error("Geen PSSH (MPD + bootstrap) — VOD-keys niet op te halen");
}
const extracted = await extractViaplayKeys({
wvdPath: settings.wvdPath,
pssh,
@ -442,6 +458,9 @@ export async function resolveViaplayGuidPlay(
}
}
}
if (!keys.length) {
throw new Error("Viaplay-licentie aanwezig maar geen VOD-keys");
}
}
recordVodPlay({

View file

@ -1116,7 +1116,7 @@ export async function getScheduleEventPlay(
if (!providerAllowed(pub.provider, disabledGlobal, access?.allowedProviders ?? null)) {
throw new AppError("NOT_FOUND", "Event niet gevonden", 404);
}
if (pub.phase === "ended") {
if (pub.phase === "ended" && !pub.playable) {
throw new AppError("GONE", "Dit event is afgelopen", 410);
}
if (pub.phase === "upcoming") {