diff --git a/apps/master-api/src/viaplay/client.ts b/apps/master-api/src/viaplay/client.ts index 69b8e6f..9fa49b3 100644 --- a/apps/master-api/src/viaplay/client.ts +++ b/apps/master-api/src/viaplay/client.ts @@ -766,8 +766,12 @@ function extractProducts(json: unknown): ViaplayEpgProduct[] { return out; } -/** HAR: Don't go-placeholder (live soft-fail). Clips mogen dit pad wél hebben. */ -export const VIAPLAY_DONT_GO_MEDIA_RE = /\/7c4cd64\/88000100-1778484912807\.ism\b/i; +/** + * Don't go is Viaplay's soft-fail (200 + promo-VOD) i.p.v. 403. + * Titel/guid van het event kloppen vaak wél; alleen de media is fout. + */ +const VIAPLAY_DONT_GO_MEDIA_RE = + /88000100-1778484912807|\b7c4cd64\b|\/dont[-_]?go/i; export function isDontGoPlaceholder(mpdUrl: string | null | undefined): boolean { return !!mpdUrl && VIAPLAY_DONT_GO_MEDIA_RE.test(mpdUrl); @@ -777,31 +781,18 @@ export function isLiveDashMpd(mpdUrl: string | null | undefined): boolean { return !!mpdUrl && /live-dash/i.test(mpdUrl); } -function sportContentUrls( - pageUrl: string | null | undefined, - deviceKey: string, - mediaGuid: string -): { productUrl: string; contextualUrl: string; contextualv2Url: string } { - const guidPath = mediaGuid.replace(/-NL$/i, "").toLowerCase(); - let clean = ""; - const raw = (pageUrl || "").trim(); - if (raw) { - try { - const u = new URL(raw); - u.search = ""; - clean = u.toString().replace(/\/$/, ""); - } catch { - clean = ""; - } +function assertLivePlayNotSoftFail(played: ViaplayPlayResult): void { + if (isPromoVodTitle(played.title)) { + throw new Error(`Viaplay gaf promo-stream (“${played.title}”)`); } - if (!clean) { - clean = `https://content.viaplay.com/${deviceKey}/sport/${guidPath}`; + if (isDontGoPlaceholder(played.mpdUrl)) { + throw new Error("Viaplay gaf Don't go-placeholder i.p.v. de wedstrijdstream"); + } + if (!isLiveDashMpd(played.mpdUrl)) { + throw new Error( + "Viaplay gaf geen live-dash stream (Don't go/VOD-fallback — sessie of rechten)" + ); } - return { - productUrl: `${clean}?partial=true`, - contextualUrl: `${clean}?contextual=true`, - contextualv2Url: `${clean}?contextualV2=true&productType=sport`, - }; } export async function playByMediaGuid( @@ -810,11 +801,8 @@ export async function playByMediaGuid( opts: { deviceKey: string; timeoutMs: number; pageUrl?: string | null } ): Promise { const mediaGuid = toMediaGuid(mediaGuidRaw); - const pages = sportContentUrls(opts.pageUrl, opts.deviceKey, mediaGuid); - const returnUrl = new URL(`https://content.viaplay.com/${opts.deviceKey}`); - returnUrl.searchParams.set("language", "nl"); - if (creds.profileId) returnUrl.searchParams.set("profileId", creds.profileId); - + // Zelfde query als het werkende live-script. Extra HAR-svod/producturl + // triggert bij terugkijken de Don't go-VOD-fallback. const u = new URL("https://play-live.viaplay.com/api/stream/bymediaguid"); u.searchParams.set("deviceId", creds.deviceId || randomUUID()); u.searchParams.set("deviceName", "web"); @@ -822,19 +810,7 @@ export async function playByMediaGuid( u.searchParams.set("userAgent", VIAPLAY_USER_AGENT); u.searchParams.set("deviceKey", opts.deviceKey); u.searchParams.set("mediaGuid", mediaGuid); - u.searchParams.set("cse", "true"); u.searchParams.set("nocc", "autoplayer"); - u.searchParams.set( - "splitIOFlagIds", - "all_sports_beyondLiveBeacon_on,web_player_pause_ads_on" - ); - u.searchParams.set("returnurl", returnUrl.toString()); - u.searchParams.set("producturl", pages.productUrl); - u.searchParams.set("contextualurl", pages.contextualUrl); - u.searchParams.set("contextualv2url", pages.contextualv2Url); - u.searchParams.set("sectionPath", "/sport"); - u.searchParams.set("defaultAvailabilityContext", "svod"); - u.searchParams.set("win10edge", "true"); if (creds.profileId) u.searchParams.set("profileId", creds.profileId); const res = await fetchJson(u.toString(), { @@ -849,11 +825,7 @@ export async function playByMediaGuid( throw new Error(msg); } const played = parsePlayStreamBody(body, mediaGuid); - if (isDontGoPlaceholder(played.mpdUrl)) { - throw new Error( - "Viaplay play-live gaf Don't go-placeholder i.p.v. de wedstrijdstream" - ); - } + assertLivePlayNotSoftFail(played); return played; } @@ -969,8 +941,7 @@ function playBodyGuid(body: Record): string | null { } /** - * HAR afspelen.har: web speelt clip als guid+titel kloppen, ook zonder DRM - * en met cse mediaGuid=unknown. Alleen een promo-titel is een echte soft-fail. + * Soft-fail: 200 met juiste product-metadata maar Don't go / promo-MPD. */ function assertVodPlayMatchesGuid( body: Record, @@ -991,6 +962,11 @@ function assertVodPlayMatchesGuid( if (isPromoVodTitle(played.title)) { throw new Error(`Viaplay gaf promo-stream (“${played.title}”)`); } + if (isDontGoPlaceholder(played.mpdUrl)) { + throw new Error( + "Viaplay gaf Don't go-placeholder i.p.v. de gevraagde stream" + ); + } const links = asRecord(body._links); const productHref = asRecord(links?.["viaplay:product"])?.href; diff --git a/apps/master-api/src/viaplay/play.ts b/apps/master-api/src/viaplay/play.ts index 773cd84..0a1b630 100644 --- a/apps/master-api/src/viaplay/play.ts +++ b/apps/master-api/src/viaplay/play.ts @@ -368,6 +368,12 @@ export async function ensureViaplayEventReady( if (play.licenseUrl && !keys.length) { throw new Error("Viaplay-licentie aanwezig maar geen keys"); } + if (isDontGoPlaceholder(play.mpdUrl) || isDontGoPlaceholder(kap.mpdUrl)) { + throw new Error("Viaplay gaf Don't go-placeholder i.p.v. de wedstrijdstream"); + } + if (!isLiveDashMpd(play.mpdUrl)) { + throw new Error("Viaplay gaf geen live-dash stream (Don't go/VOD-fallback)"); + } await patchViaplayEventCache(mediaGuid, { mpdUrl: kap.mpdUrl, @@ -576,6 +582,9 @@ async function resolveViaplayGuidPlayInner( throw new Error("Viaplay-licentie aanwezig maar geen VOD-keys"); } } + if (isDontGoPlaceholder(mpdUrl)) { + throw new Error("Viaplay gaf Don't go-placeholder i.p.v. de gevraagde stream"); + } recordVodPlay({ guid,