Fix Viaplay stream-href template expansion that broke VOD play.

RFC 6570 {?params} must become ?, not be stripped — otherwise byguid query is invalid and play fails.
This commit is contained in:
Jos Vooges | STH 2026-09-23 02:59:17 +02:00
parent c6bd855334
commit 2e6dc3bab9

View file

@ -709,14 +709,17 @@ function parsePlayStreamBody(
/** /**
* Bouw play.viaplay.com URL uit templated stream-href (content-API). * Bouw play.viaplay.com URL uit templated stream-href (content-API).
* Strip `{?deviceId,...}` en vul device-params + bestaande query. * RFC 6570 `{?deviceId,...}` → `?` (niet strippen: anders blijft `byguid&deviceKey=` over).
*/ */
export function expandViaplayStreamHref( export function expandViaplayStreamHref(
hrefRaw: string, hrefRaw: string,
creds: ViaplayAccountCreds, creds: ViaplayAccountCreds,
opts: { deviceKey: string } opts: { deviceKey: string }
): string { ): string {
const cleaned = hrefRaw.replace(/\{\?[^}]+\}/g, ""); const cleaned = hrefRaw
.replace(/\{\?[^}]+\}/g, "?")
.replace(/\?&/, "?")
.replace(/\?{2,}/g, "?");
const u = new URL(cleaned); const u = new URL(cleaned);
u.searchParams.set("deviceId", creds.deviceId || randomUUID()); u.searchParams.set("deviceId", creds.deviceId || randomUUID());
u.searchParams.set("deviceName", "web"); u.searchParams.set("deviceName", "web");
@ -854,22 +857,50 @@ export async function playByGuid(
`play-byguid mislukt (${res.status})`; `play-byguid mislukt (${res.status})`;
throw new Error(msg); throw new Error(msg);
} }
const played = parsePlayStreamBody(body, guid); let played = parsePlayStreamBody(body, guid);
if (!played.title && pageTitle) played.title = pageTitle; if (!played.title && pageTitle) played.title = pageTitle;
// Fallback-promo herkennen // Kapotte stream-href of ontbrekende producturl → promo-docu.
const title = (played.title || "").toLowerCase(); // Dan één keer opnieuw met expliciete HAR-query i.p.v. hard falen.
if (/viaplay\s*presents|don'?t\s*go|viaplay\s*documentary/i.test(title)) { const titleLc = (played.title || "").toLowerCase();
throw new Error( const looksLikePromo = /viaplay\s*presents|don'?t\s*go|viaplay\s*documentary/i.test(
`Viaplay gaf fallback-stream (“${played.title}”) — check tokens/producturl` titleLc
); );
if (looksLikePromo && streamHref) {
let productUrlFinal = productUrl;
try {
const pu = new URL(productUrl);
if (!pu.searchParams.has("partial")) pu.searchParams.set("partial", "true");
productUrlFinal = pu.toString();
} catch {
/* keep */
} }
if (played.mpdUrl && /live-dash|play-live|tve_/i.test(played.mpdUrl)) { const returnUrl =
throw new Error("Viaplay gaf live-stream i.p.v. VOD voor deze clip"); (opts.returnUrl && String(opts.returnUrl).trim()) ||
`https://content.viaplay.com/${opts.deviceKey}/sport`;
const sectionPath = (opts.sectionPath && String(opts.sectionPath).trim()) || "/sport";
const u = new URL("https://play.viaplay.com/api/stream/byguid");
u.searchParams.set("deviceId", creds.deviceId || randomUUID());
u.searchParams.set("deviceName", "web");
u.searchParams.set("deviceType", "pc");
u.searchParams.set("userAgent", VIAPLAY_USER_AGENT);
u.searchParams.set("deviceKey", opts.deviceKey);
u.searchParams.set("guid", guid);
u.searchParams.set("cse", "true");
u.searchParams.set("producturl", productUrlFinal);
u.searchParams.set("returnurl", returnUrl);
u.searchParams.set("sectionPath", sectionPath);
u.searchParams.set("defaultAvailabilityContext", "svod");
if (creds.profileId) u.searchParams.set("profileId", creds.profileId);
const retry = await fetchJson(u.toString(), {
headers: authHeaders(creds),
timeoutMs: opts.timeoutMs,
});
const retryBody = asRecord(retry.json);
if (retry.status === 200 && retryBody) {
played = parsePlayStreamBody(retryBody, guid);
if (!played.title && pageTitle) played.title = pageTitle;
} }
// Clips horen op vod-cdn
if (played.mpdUrl && !/vod-/i.test(played.mpdUrl)) {
throw new Error(`Onverwachte VOD-MPD host: ${played.mpdUrl.slice(0, 80)}`);
} }
return played; return played;
} }