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:
parent
7cdff2bde4
commit
d1c3176ffe
2 changed files with 74 additions and 6 deletions
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue