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).
* Strip `{?deviceId,...}` en vul device-params + bestaande query.
* RFC 6570 `{?deviceId,...}` → `?` (niet strippen: anders blijft `byguid&deviceKey=` over).
*/
export function expandViaplayStreamHref(
hrefRaw: string,
creds: ViaplayAccountCreds,
opts: { deviceKey: string }
): string {
const cleaned = hrefRaw.replace(/\{\?[^}]+\}/g, "");
const cleaned = hrefRaw
.replace(/\{\?[^}]+\}/g, "?")
.replace(/\?&/, "?")
.replace(/\?{2,}/g, "?");
const u = new URL(cleaned);
u.searchParams.set("deviceId", creds.deviceId || randomUUID());
u.searchParams.set("deviceName", "web");
@ -854,22 +857,50 @@ export async function playByGuid(
`play-byguid mislukt (${res.status})`;
throw new Error(msg);
}
const played = parsePlayStreamBody(body, guid);
let played = parsePlayStreamBody(body, guid);
if (!played.title && pageTitle) played.title = pageTitle;
// Fallback-promo herkennen
const title = (played.title || "").toLowerCase();
if (/viaplay\s*presents|don'?t\s*go|viaplay\s*documentary/i.test(title)) {
throw new Error(
`Viaplay gaf fallback-stream (“${played.title}”) — check tokens/producturl`
// Kapotte stream-href of ontbrekende producturl → promo-docu.
// Dan één keer opnieuw met expliciete HAR-query i.p.v. hard falen.
const titleLc = (played.title || "").toLowerCase();
const looksLikePromo = /viaplay\s*presents|don'?t\s*go|viaplay\s*documentary/i.test(
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)) {
throw new Error("Viaplay gaf live-stream i.p.v. VOD voor deze clip");
const returnUrl =
(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;
}