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:
parent
b783ba137f
commit
7cdff2bde4
4 changed files with 52 additions and 5 deletions
|
|
@ -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",
|
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",
|
"Content-Type": "application/octet-stream",
|
||||||
}
|
}
|
||||||
|
lic_url = data.get("licUrl") or ""
|
||||||
auth = (data.get("licAuth") or "").strip()
|
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}"
|
headers["Authorization"] = f"Basic {auth}"
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
data["licUrl"],
|
data["licUrl"],
|
||||||
|
|
|
||||||
|
|
@ -1242,6 +1242,32 @@ export async function fetchMpdXml(
|
||||||
return { status: response.status, xml, url };
|
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 {
|
export function extractPsshFromMpd(xml: string): string | null {
|
||||||
const widevine = xml.match(
|
const widevine = xml.match(
|
||||||
/<ContentProtection\b[^>]*schemeIdUri=["']urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed["'][\s\S]*?<\/ContentProtection>/i
|
/<ContentProtection\b[^>]*schemeIdUri=["']urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed["'][\s\S]*?<\/ContentProtection>/i
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
resolveKapMpd,
|
resolveKapMpd,
|
||||||
fetchMpdXml,
|
fetchMpdXml,
|
||||||
extractPsshFromMpd,
|
extractPsshFromMpd,
|
||||||
|
fetchViaplayBootstrapPssh,
|
||||||
VIAPLAY_USER_AGENT,
|
VIAPLAY_USER_AGENT,
|
||||||
} from "./client";
|
} from "./client";
|
||||||
import {
|
import {
|
||||||
|
|
@ -243,10 +244,15 @@ export async function ensureViaplayEventReady(
|
||||||
|
|
||||||
let keys = cachedKeys;
|
let keys = cachedKeys;
|
||||||
const wantKeys = opts?.fetchKeys ?? tweaks.fetchKeysIfMissing;
|
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({
|
const extracted = await extractViaplayKeys({
|
||||||
wvdPath: settings.wvdPath,
|
wvdPath: settings.wvdPath,
|
||||||
pssh: kap.pssh,
|
pssh,
|
||||||
licUrl: play.licenseUrl,
|
licUrl: play.licenseUrl,
|
||||||
licAuth: play.licenseAuth,
|
licAuth: play.licenseAuth,
|
||||||
userAgent: VIAPLAY_USER_AGENT,
|
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, {
|
await patchViaplayEventCache(mediaGuid, {
|
||||||
mpdUrl: kap.mpdUrl,
|
mpdUrl: kap.mpdUrl,
|
||||||
mpdCachedAt: new Date().toISOString(),
|
mpdCachedAt: new Date().toISOString(),
|
||||||
|
|
@ -409,7 +419,13 @@ export async function resolveViaplayGuidPlay(
|
||||||
// Clear VOD (HAR: geen license) — nooit stale ClearKey van eerdere plays plakken.
|
// Clear VOD (HAR: geen license) — nooit stale ClearKey van eerdere plays plakken.
|
||||||
if (!play.licenseUrl) {
|
if (!play.licenseUrl) {
|
||||||
keys = [];
|
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({
|
const extracted = await extractViaplayKeys({
|
||||||
wvdPath: settings.wvdPath,
|
wvdPath: settings.wvdPath,
|
||||||
pssh,
|
pssh,
|
||||||
|
|
@ -442,6 +458,9 @@ export async function resolveViaplayGuidPlay(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!keys.length) {
|
||||||
|
throw new Error("Viaplay-licentie aanwezig maar geen VOD-keys");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recordVodPlay({
|
recordVodPlay({
|
||||||
|
|
|
||||||
|
|
@ -1116,7 +1116,7 @@ export async function getScheduleEventPlay(
|
||||||
if (!providerAllowed(pub.provider, disabledGlobal, access?.allowedProviders ?? null)) {
|
if (!providerAllowed(pub.provider, disabledGlobal, access?.allowedProviders ?? null)) {
|
||||||
throw new AppError("NOT_FOUND", "Event niet gevonden", 404);
|
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);
|
throw new AppError("GONE", "Dit event is afgelopen", 410);
|
||||||
}
|
}
|
||||||
if (pub.phase === "upcoming") {
|
if (pub.phase === "upcoming") {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue