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! };
|
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,
|
sessionSecret: string,
|
||||||
mediaGuid: string
|
mediaGuid: string
|
||||||
): Promise<Array<{ kid: string; key: string }>> {
|
): Promise<StoredDrmPlay> {
|
||||||
const rows = await prisma.drmKey.findMany({
|
const rows = await prisma.drmKey.findMany({
|
||||||
where: { mediaGuid, source: "viaplay" },
|
where: { mediaGuid, source: "viaplay" },
|
||||||
orderBy: { lastSeenAt: "desc" },
|
orderBy: { lastSeenAt: "desc" },
|
||||||
|
|
@ -126,18 +135,30 @@ async function keysForMediaGuid(
|
||||||
rows.push(...alt);
|
rows.push(...alt);
|
||||||
}
|
}
|
||||||
const { decryptSecret } = await import("../security/crypto");
|
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>();
|
const seen = new Set<string>();
|
||||||
|
let mpdUrl: string | null = null;
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (seen.has(row.kid)) continue;
|
if (seen.has(row.kid)) continue;
|
||||||
seen.add(row.kid);
|
seen.add(row.kid);
|
||||||
try {
|
try {
|
||||||
out.push({ kid: row.kid, key: decryptSecret(row.keyEnc, sessionSecret) });
|
keys.push({ kid: row.kid, key: decryptSecret(row.keyEnc, sessionSecret) });
|
||||||
} catch {
|
} 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(
|
async function keysForChannelPath(
|
||||||
|
|
@ -262,9 +283,57 @@ export async function ensureViaplayEventReady(
|
||||||
const ended = Date.now() > event.endMs;
|
const ended = Date.now() > event.endMs;
|
||||||
const cachedMpd = channelMpdFromEvents(settings.config.events, event);
|
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) {
|
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 =
|
const cacheFresh =
|
||||||
|
|
@ -291,7 +360,7 @@ export async function ensureViaplayEventReady(
|
||||||
keys: cachedKeys,
|
keys: cachedKeys,
|
||||||
fromCache: true,
|
fromCache: true,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
live: !ended,
|
live: !ended && !hasKapWindow(event.mpdUrl),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await dropEventMpdCache(
|
await dropEventMpdCache(
|
||||||
|
|
@ -382,7 +451,7 @@ export async function ensureViaplayEventReady(
|
||||||
keys: cachedKeys,
|
keys: cachedKeys,
|
||||||
fromCache: true,
|
fromCache: true,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
live: !ended,
|
live: !ended && !hasKapWindow(event.mpdUrl),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -522,7 +591,7 @@ export async function ensureViaplayEventReady(
|
||||||
fromCache: false,
|
fromCache: false,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
live: !ended,
|
live: !ended && !hasKapWindow(kap.mpdUrl),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
lastError = err instanceof Error ? err : new Error(String(err));
|
lastError = err instanceof Error ? err : new Error(String(err));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue