Fix Viaplay catch-up without live replay.

Reuse the channel MPD and keys with a KAP window instead of play-live plus CDM per account.
This commit is contained in:
Jos Vooges | STH 2026-09-28 03:45:54 +02:00
parent 7cdff2bde4
commit d1c3176ffe
2 changed files with 74 additions and 6 deletions

View file

@ -1204,25 +1204,31 @@ export async function playByGuid(
throw lastError || new Error("Viaplay VOD-play mislukt"); throw lastError || new Error("Viaplay VOD-play mislukt");
} }
/** Haal een eventuele /start/N/stop/N window uit de MPD-URL. */
export function stripKapWindow(mpdUrl: string): string {
return mpdUrl.replace(/\/start\/\d+\/stop\/\d+/g, "");
}
/** Bouw MPD-URL met EPG start/stop kap (seconden). */ /** Bouw MPD-URL met EPG start/stop kap (seconden). */
export function kapMpdUrl( export function kapMpdUrl(
mpdUrl: string, mpdUrl: string,
startMs: number, startMs: number,
endMs: number endMs: number
): string[] { ): string[] {
const base = stripKapWindow(mpdUrl);
const startSec = Math.floor(startMs / 1000); const startSec = Math.floor(startMs / 1000);
const stopSec = Math.floor(endMs / 1000); const stopSec = Math.floor(endMs / 1000);
try { try {
const u = new URL(mpdUrl); const u = new URL(base);
const file = u.pathname.split("/").pop() || "dash.mpd"; const file = u.pathname.split("/").pop() || "dash.mpd";
const dir = mpdUrl.replace(/\/[^/]+$/, ""); const dir = base.replace(/\/[^/]+$/, "");
return [ return [
`${dir}/start/${startSec}/stop/${stopSec}/${file}`, `${dir}/start/${startSec}/stop/${stopSec}/${file}`,
`${dir}/start/${startSec}/stop/${stopSec}/index.mpd`, `${dir}/start/${startSec}/stop/${stopSec}/index.mpd`,
mpdUrl, base,
]; ];
} catch { } catch {
return [mpdUrl]; return [base];
} }
} }

View file

@ -161,6 +161,23 @@ function accountOrder(
return ordered; return ordered;
} }
function channelMpdFromEvents(
events: ViaplayEventSnapshot[],
event: ViaplayEventSnapshot
): string | null {
if (event.mpdUrl?.trim()) return event.mpdUrl.trim();
const channel = event.channelName?.trim().toLowerCase();
const station = event.stationId?.trim();
if (!channel && !station) return null;
for (const other of events) {
const url = other.mpdUrl?.trim();
if (!url) continue;
if (station && other.stationId?.trim() === station) return url;
if (channel && other.channelName?.trim().toLowerCase() === channel) return url;
}
return null;
}
export async function ensureViaplayEventReady( export async function ensureViaplayEventReady(
sessionSecret: string, sessionSecret: string,
event: ViaplayEventSnapshot, event: ViaplayEventSnapshot,
@ -174,13 +191,17 @@ export async function ensureViaplayEventReady(
event.accountIds || event.accountIds ||
[]; [];
const ended = Date.now() > event.endMs;
const cachedMpd = channelMpdFromEvents(settings.config.events, event);
let cachedKeys = await keysForMediaGuid(sessionSecret, mediaGuid); let cachedKeys = await keysForMediaGuid(sessionSecret, mediaGuid);
if (!cachedKeys.length) { if (!cachedKeys.length) {
cachedKeys = await keysForChannelPath(sessionSecret, event.mpdUrl); cachedKeys = await keysForChannelPath(sessionSecret, event.mpdUrl || cachedMpd);
} }
const cacheFresh = const cacheFresh =
!opts?.forceRefresh && !opts?.forceRefresh &&
!ended &&
!!event.mpdUrl && !!event.mpdUrl &&
isMpdCacheFresh(event.mpdCachedAt, tweaks.mpdCacheTtlMinutes); isMpdCacheFresh(event.mpdCachedAt, tweaks.mpdCacheTtlMinutes);
@ -194,6 +215,40 @@ export async function ensureViaplayEventReady(
}; };
} }
// Terugkijken: zelfde pad als het werkende script — KAP op zender-MPD, geen play-live.
if (ended && cachedMpd && !opts?.forceRefresh) {
try {
const kap = await resolveKapMpd(
cachedMpd,
event.startMs,
event.endMs,
tweaks.httpTimeoutMs
);
if (!cachedKeys.length) {
cachedKeys = await keysForChannelPath(sessionSecret, kap.mpdUrl);
}
if (cachedKeys.length) {
await patchViaplayEventCache(mediaGuid, {
mpdUrl: kap.mpdUrl,
mpdCachedAt: new Date().toISOString(),
hasPssh: kap.hasPssh,
hasKeys: true,
lastPlayError: null,
lastPlayAt: new Date().toISOString(),
});
return {
streamUrl: kap.mpdUrl,
format: "dash",
keys: cachedKeys,
fromCache: true,
mediaGuid,
};
}
} catch {
/* play-live hieronder */
}
}
// Cache MPD zonder keys is ok als keys later komen — of clear feed // Cache MPD zonder keys is ok als keys later komen — of clear feed
if (cacheFresh && event.mpdUrl && !(opts?.fetchKeys ?? tweaks.fetchKeysIfMissing)) { if (cacheFresh && event.mpdUrl && !(opts?.fetchKeys ?? tweaks.fetchKeysIfMissing)) {
return { return {
@ -243,6 +298,9 @@ export async function ensureViaplayEventReady(
); );
let keys = cachedKeys; let keys = cachedKeys;
if (!keys.length) {
keys = await keysForChannelPath(sessionSecret, play.mpdUrl);
}
const wantKeys = opts?.fetchKeys ?? tweaks.fetchKeysIfMissing; const wantKeys = opts?.fetchKeys ?? tweaks.fetchKeysIfMissing;
if (wantKeys && play.licenseUrl && !keys.length) { if (wantKeys && play.licenseUrl && !keys.length) {
const pssh = const pssh =
@ -314,7 +372,11 @@ export async function ensureViaplayEventReady(
lastPlayError: lastError?.message || "Play mislukt", lastPlayError: lastError?.message || "Play mislukt",
lastPlayAt: new Date().toISOString(), lastPlayAt: new Date().toISOString(),
}); });
throw lastError || new Error("Viaplay play mislukt"); throw new AppError(
"UPSTREAM",
lastError?.message || "Viaplay play mislukt",
502
);
} }
export async function resolveViaplayPlay( export async function resolveViaplayPlay(