Serve stored Viaplay DRM MPD+keys to clients before play-live, matching the admin key table.
This commit is contained in:
parent
133f51284d
commit
75969d87ec
1 changed files with 80 additions and 11 deletions
|
|
@ -108,10 +108,19 @@ function parseKey(value: string): { kid: string; key: string } | null {
|
|||
return { kid: kid!, key: key! };
|
||||
}
|
||||
|
||||
async function keysForMediaGuid(
|
||||
function hasKapWindow(url: string | null | undefined): boolean {
|
||||
return !!url && /\/start\/\d+\/stop\/\d+/.test(url);
|
||||
}
|
||||
|
||||
type StoredDrmPlay = {
|
||||
keys: Array<{ kid: string; key: string }>;
|
||||
mpdUrl: string | null;
|
||||
};
|
||||
|
||||
async function storedDrmPlay(
|
||||
sessionSecret: string,
|
||||
mediaGuid: string
|
||||
): Promise<Array<{ kid: string; key: string }>> {
|
||||
): Promise<StoredDrmPlay> {
|
||||
const rows = await prisma.drmKey.findMany({
|
||||
where: { mediaGuid, source: "viaplay" },
|
||||
orderBy: { lastSeenAt: "desc" },
|
||||
|
|
@ -126,18 +135,30 @@ async function keysForMediaGuid(
|
|||
rows.push(...alt);
|
||||
}
|
||||
const { decryptSecret } = await import("../security/crypto");
|
||||
const out: Array<{ kid: string; key: string }> = [];
|
||||
const keys: Array<{ kid: string; key: string }> = [];
|
||||
const seen = new Set<string>();
|
||||
let mpdUrl: string | null = null;
|
||||
for (const row of rows) {
|
||||
if (seen.has(row.kid)) continue;
|
||||
seen.add(row.kid);
|
||||
try {
|
||||
out.push({ kid: row.kid, key: decryptSecret(row.keyEnc, sessionSecret) });
|
||||
keys.push({ kid: row.kid, key: decryptSecret(row.keyEnc, sessionSecret) });
|
||||
} catch {
|
||||
/* skip */
|
||||
continue;
|
||||
}
|
||||
const url = row.mpdUrl?.trim() || null;
|
||||
if (!mpdUrl && url && /^https?:\/\//i.test(url) && !isDontGoPlaceholder(url)) {
|
||||
mpdUrl = url;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return { keys, mpdUrl };
|
||||
}
|
||||
|
||||
async function keysForMediaGuid(
|
||||
sessionSecret: string,
|
||||
mediaGuid: string
|
||||
): Promise<Array<{ kid: string; key: string }>> {
|
||||
return (await storedDrmPlay(sessionSecret, mediaGuid)).keys;
|
||||
}
|
||||
|
||||
async function keysForChannelPath(
|
||||
|
|
@ -262,9 +283,57 @@ export async function ensureViaplayEventReady(
|
|||
const ended = Date.now() > event.endMs;
|
||||
const cachedMpd = channelMpdFromEvents(settings.config.events, event);
|
||||
|
||||
let cachedKeys = await keysForMediaGuid(sessionSecret, mediaGuid);
|
||||
const stored = await storedDrmPlay(sessionSecret, mediaGuid);
|
||||
let cachedKeys = stored.keys;
|
||||
if (!cachedKeys.length) {
|
||||
cachedKeys = await keysForChannelPath(sessionSecret, event.mpdUrl || cachedMpd);
|
||||
cachedKeys = await keysForChannelPath(
|
||||
sessionSecret,
|
||||
stored.mpdUrl || event.mpdUrl || cachedMpd
|
||||
);
|
||||
}
|
||||
|
||||
// Zelfde bron als de DRM-tabel in admin: opgeslagen MPD + KID/KEY.
|
||||
// Alleen opnieuw play-live als die set ontbreekt of Don't go is.
|
||||
if (!opts?.forceRefresh && cachedKeys.length) {
|
||||
let url = stored.mpdUrl || event.mpdUrl || cachedMpd;
|
||||
if (url && isReusableLiveMpd(url)) {
|
||||
if (ended && !hasKapWindow(url)) {
|
||||
try {
|
||||
const kap = await resolveKapMpd(
|
||||
url,
|
||||
event.startMs,
|
||||
event.endMs,
|
||||
Math.min(tweaks.httpTimeoutMs, 12_000)
|
||||
);
|
||||
if (isReusableLiveMpd(kap.mpdUrl)) url = kap.mpdUrl;
|
||||
} catch {
|
||||
/* gebruik de drm-MPD zoals de losse speler */
|
||||
}
|
||||
}
|
||||
const trustStoredKap = ended && hasKapWindow(url);
|
||||
const probed =
|
||||
trustStoredKap || (await liveCachedMpdPlayable(url, tweaks.httpTimeoutMs));
|
||||
if (probed && url) {
|
||||
recordLivePlay({
|
||||
guid: mediaGuid,
|
||||
ok: true,
|
||||
title: event.title,
|
||||
mediaPath: mediaPathOf(url),
|
||||
hasLicense: true,
|
||||
fromCache: true,
|
||||
cookies: emptyCookies(),
|
||||
error: null,
|
||||
});
|
||||
return {
|
||||
streamUrl: url,
|
||||
format: "dash",
|
||||
keys: cachedKeys,
|
||||
fromCache: true,
|
||||
mediaGuid,
|
||||
live: !ended && !hasKapWindow(url),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cacheFresh =
|
||||
|
|
@ -291,7 +360,7 @@ export async function ensureViaplayEventReady(
|
|||
keys: cachedKeys,
|
||||
fromCache: true,
|
||||
mediaGuid,
|
||||
live: !ended,
|
||||
live: !ended && !hasKapWindow(event.mpdUrl),
|
||||
};
|
||||
}
|
||||
await dropEventMpdCache(
|
||||
|
|
@ -382,7 +451,7 @@ export async function ensureViaplayEventReady(
|
|||
keys: cachedKeys,
|
||||
fromCache: true,
|
||||
mediaGuid,
|
||||
live: !ended,
|
||||
live: !ended && !hasKapWindow(event.mpdUrl),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -522,7 +591,7 @@ export async function ensureViaplayEventReady(
|
|||
fromCache: false,
|
||||
mediaGuid,
|
||||
accountId: account.id,
|
||||
live: !ended,
|
||||
live: !ended && !hasKapWindow(kap.mpdUrl),
|
||||
};
|
||||
} catch (err) {
|
||||
lastError = err instanceof Error ? err : new Error(String(err));
|
||||
|
|
|
|||
Loading…
Reference in a new issue