From 7cfe2598a84a5f2d8e6db732c6b5abd5c852a6a1 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Mon, 28 Sep 2026 23:59:19 +0200 Subject: [PATCH] Route ended EPG S-ids through play-live KAP, not byguid VOD. --- apps/master-api/src/viaplay/client.ts | 20 +++++++++++++------- apps/master-api/src/viaplay/events.ts | 6 +++--- apps/master-api/src/viaplay/play.ts | 17 +++++++++++------ apps/master-api/src/viaplay/sync.ts | 6 +++++- apps/master-api/src/viewer/routes.ts | 2 +- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/apps/master-api/src/viaplay/client.ts b/apps/master-api/src/viaplay/client.ts index ed59b7e..601bdc5 100644 --- a/apps/master-api/src/viaplay/client.ts +++ b/apps/master-api/src/viaplay/client.ts @@ -655,15 +655,9 @@ function productToRailItem(node: Record): ViaplaySportRailItem else if (type === "series" || type === "sportseries" || type === "episode") kind = "series"; else if (type === "movie" || type === "film") kind = "series"; // docs/specials → VOD byguid - const now = Date.now(); - const stillLive = - startMs != null && (endMs == null || endMs > now); let playMode: ViaplaySportRailItem["playMode"] = null; - if (href?.includes("play-live.viaplay.com") || (kind === "sport" && stillLive)) { + if (href?.includes("play-live.viaplay.com") || (kind === "sport" && startMs != null)) { playMode = "live"; - } else if (kind === "sport" && endMs != null && endMs <= now) { - // Afgelopen hoofduitzending: web speelt byguid/vod-dash, niet live-TV. - playMode = "vod"; } else if ( href?.includes("play.viaplay.com/api/stream/byguid") || kind === "clip" || @@ -899,6 +893,18 @@ export function isVodDashMpd(mpdUrl: string | null | undefined): boolean { return /\/vp-sports-clip-/i.test(mpdUrl) && /\.ism\//i.test(mpdUrl); } +/** EPG/live-product (S2609…) — geen byguid-VOD. */ +export function isViaplayEpgGuid(guid: string | null | undefined): boolean { + const g = String(guid || "") + .trim() + .replace(/-NL$/i, ""); + return /^S\d{8,}$/i.test(g); +} + +export function isViaplayClipGuid(guid: string | null | undefined): boolean { + return /vp-sports-clip/i.test(String(guid || "")); +} + function assertPlayNotSoftFail( played: ViaplayPlayResult, opts?: { allowVod?: boolean } diff --git a/apps/master-api/src/viaplay/events.ts b/apps/master-api/src/viaplay/events.ts index 9b4b7a9..8d5807f 100644 --- a/apps/master-api/src/viaplay/events.ts +++ b/apps/master-api/src/viaplay/events.ts @@ -184,10 +184,10 @@ function isWeekendLocal(ms: number): boolean { } function railItemToHome(item: ViaplaySportRailItem, nowMs: number): ViaplayHomeItem { + const epg = /^S\d+/i.test(String(item.guid || "").replace(/-NL$/i, "")); const isEvent = - item.kind === "sport" && - item.playMode === "live" && - (item.endMs == null || item.endMs > nowMs); + (item.kind === "sport" && item.playMode === "live") || + (item.kind === "sport" && epg); const openAs: ViaplayHomeItem["openAs"] = isEvent ? "event" : "content"; const id = openAs === "event" ? toEventId(toMediaGuid(item.guid)) : `viaplay-content:${item.guid}`; diff --git a/apps/master-api/src/viaplay/play.ts b/apps/master-api/src/viaplay/play.ts index 14bd3b7..09cc3c5 100644 --- a/apps/master-api/src/viaplay/play.ts +++ b/apps/master-api/src/viaplay/play.ts @@ -19,6 +19,8 @@ import { isDontGoPlaceholder, isLiveDashMpd, isVodDashMpd, + isViaplayEpgGuid, + isViaplayClipGuid, mpdLooksLikeDontGo, mpdHasContentProtection, stripKapWindow, @@ -458,10 +460,6 @@ async function dropEventMpdCache(mediaGuid: string, reason: string) { }); } -function isViaplayClipGuid(guid: string | null | undefined): boolean { - return /vp-sports-clip/i.test(String(guid || "")); -} - function isNoPrioritizedVideo(message: string | null | undefined): boolean { return /no prioritized video found/i.test(String(message || "")); } @@ -519,7 +517,7 @@ async function findCatchupClip( let best: { guid: string; pageUrl: string | null; score: number } | null = null; for (const rail of rails) { for (const item of rail.items) { - if (item.playMode !== "vod") continue; + if (item.playMode !== "vod" || !isViaplayClipGuid(item.guid)) continue; let score = catchupTitleScore(event.title, item.title); if (item.startMs != null && event.startMs) { const delta = Math.abs(item.startMs - event.startMs); @@ -994,7 +992,14 @@ export async function resolveViaplayGuidPlay( opts?: { productUrl?: string | null } ): Promise { try { - return await resolveViaplayGuidPlayInner(sessionSecret, guidRaw, opts); + const guid = String(guidRaw || "").trim(); + if (isViaplayEpgGuid(guid)) { + return await resolveViaplayPlay( + sessionSecret, + `viaplay:${toMediaGuid(guid)}` + ); + } + return await resolveViaplayGuidPlayInner(sessionSecret, guid, opts); } catch (err) { if (err instanceof AppError) throw err; throw new AppError( diff --git a/apps/master-api/src/viaplay/sync.ts b/apps/master-api/src/viaplay/sync.ts index 18672e8..1afb952 100644 --- a/apps/master-api/src/viaplay/sync.ts +++ b/apps/master-api/src/viaplay/sync.ts @@ -167,7 +167,11 @@ export function findViaplayEvent( const id = raw.replace(/^viaplay:/i, ""); return ( config.events.find( - (e) => e.mediaGuid === id || e.guid === id || e.mediaGuid === `${id}-NL` + (e) => + e.mediaGuid === id || + e.guid === id || + e.mediaGuid === `${id}-NL` || + e.mediaGuid.replace(/-NL$/i, "") === id.replace(/-NL$/i, "") ) ?? null ); } diff --git a/apps/master-api/src/viewer/routes.ts b/apps/master-api/src/viewer/routes.ts index b2e47fa..8e11bdf 100644 --- a/apps/master-api/src/viewer/routes.ts +++ b/apps/master-api/src/viewer/routes.ts @@ -1134,7 +1134,7 @@ export function registerViewerRoutes( format: played.format, fallbackStreamUrl: null, fallbackFormat: null, - live: false, + live: played.live, /** Deploy/debug: bevestigt VOD-resolver revisie na Dokploy. */ resolver: "viaplay-byguid-v10", mediaPath: (() => {