F1TV: drm.scheme (cbcs) meesturen zodat de app zelf kan ontsleutelen.
This commit is contained in:
parent
cc9f3b175a
commit
e89ba46eb8
4 changed files with 21 additions and 9 deletions
|
|
@ -107,21 +107,25 @@ async function fetchMasterPlaylist(url: string, userAgent: string): Promise<stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ClearKey-sleutels voor een Widevine-versleutelde F1TV HLS-stream (leeg = stream is clear). */
|
/**
|
||||||
|
* ClearKey-sleutels voor een Widevine-versleutelde F1TV HLS-stream (leeg = stream is clear).
|
||||||
|
* `scheme` = "cbcs" bij SAMPLE-AES: Android ClearKey kan alleen AES-CTR, de app ontsleutelt dan zelf.
|
||||||
|
*/
|
||||||
export async function getF1tvClearKeys(input: {
|
export async function getF1tvClearKeys(input: {
|
||||||
streamUrl: string;
|
streamUrl: string;
|
||||||
licenseUrl: string;
|
licenseUrl: string;
|
||||||
ascendontoken: string;
|
ascendontoken: string;
|
||||||
entitlementtoken: string;
|
entitlementtoken: string;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
}): Promise<Array<{ kid: string; key: string }>> {
|
}): Promise<{ keys: Array<{ kid: string; key: string }>; scheme: "cbcs" | "cenc" | null }> {
|
||||||
const master = await fetchMasterPlaylist(input.streamUrl, input.userAgent);
|
const master = await fetchMasterPlaylist(input.streamUrl, input.userAgent);
|
||||||
const psshList = extractWidevinePsshFromHls(master);
|
const psshList = extractWidevinePsshFromHls(master);
|
||||||
if (!psshList.length) return [];
|
if (!psshList.length) return { keys: [], scheme: null };
|
||||||
|
const scheme = /^#EXT-X-(SESSION-)?KEY:.*METHOD=SAMPLE-AES/im.test(master) ? "cbcs" : "cenc";
|
||||||
|
|
||||||
const cacheKey = psshList.slice().sort().join("|");
|
const cacheKey = psshList.slice().sort().join("|");
|
||||||
const hit = keyCache.get(cacheKey);
|
const hit = keyCache.get(cacheKey);
|
||||||
if (hit && Date.now() - hit.at < KEY_CACHE_TTL_MS) return hit.keys;
|
if (hit && Date.now() - hit.at < KEY_CACHE_TTL_MS) return { keys: hit.keys, scheme };
|
||||||
|
|
||||||
const raw = await runCdm({
|
const raw = await runCdm({
|
||||||
wvdPath: await resolveWvdPath(),
|
wvdPath: await resolveWvdPath(),
|
||||||
|
|
@ -139,5 +143,5 @@ export async function getF1tvClearKeys(input: {
|
||||||
.filter((k): k is { kid: string; key: string } => !!k);
|
.filter((k): k is { kid: string; key: string } => !!k);
|
||||||
if (!keys.length) throw new Error("F1TV license leverde geen content-sleutels");
|
if (!keys.length) throw new Error("F1TV license leverde geen content-sleutels");
|
||||||
keyCache.set(cacheKey, { keys, at: Date.now() });
|
keyCache.set(cacheKey, { keys, at: Date.now() });
|
||||||
return keys;
|
return { keys, scheme };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ export async function resolveF1tvPlay(
|
||||||
feedCount: number;
|
feedCount: number;
|
||||||
/** ClearKey kid/key (hex) voor Widevine-sessies; leeg bij clear streams. */
|
/** ClearKey kid/key (hex) voor Widevine-sessies; leeg bij clear streams. */
|
||||||
keys: Array<{ kid: string; key: string }>;
|
keys: Array<{ kid: string; key: string }>;
|
||||||
|
/** "cbcs" = app moet zelf ontsleutelen (Android ClearKey ondersteunt alleen AES-CTR). */
|
||||||
|
drmScheme: "cbcs" | "cenc" | null;
|
||||||
session: F1tvSessionSnapshot | null;
|
session: F1tvSessionSnapshot | null;
|
||||||
}> {
|
}> {
|
||||||
const { accounts, config } = await loadF1tvSettings(sessionSecret);
|
const { accounts, config } = await loadF1tvSettings(sessionSecret);
|
||||||
|
|
@ -111,18 +113,19 @@ export async function resolveF1tvPlay(
|
||||||
}
|
}
|
||||||
|
|
||||||
let keys: Array<{ kid: string; key: string }> = [];
|
let keys: Array<{ kid: string; key: string }> = [];
|
||||||
|
let drmScheme: "cbcs" | "cenc" | null = null;
|
||||||
if (played.drmType === "widevine") {
|
if (played.drmType === "widevine") {
|
||||||
if (!played.licenseUrl) {
|
if (!played.licenseUrl) {
|
||||||
throw new AppError("UPSTREAM_ERROR", "F1TV-stream is versleuteld maar zonder license-URL", 502);
|
throw new AppError("UPSTREAM_ERROR", "F1TV-stream is versleuteld maar zonder license-URL", 502);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
keys = await getF1tvClearKeys({
|
({ keys, scheme: drmScheme } = await getF1tvClearKeys({
|
||||||
streamUrl: feed.url,
|
streamUrl: feed.url,
|
||||||
licenseUrl: played.licenseUrl,
|
licenseUrl: played.licenseUrl,
|
||||||
ascendontoken: auth.ascendontoken,
|
ascendontoken: auth.ascendontoken,
|
||||||
entitlementtoken: played.entitlementToken || auth.entitlementtoken,
|
entitlementtoken: played.entitlementToken || auth.entitlementtoken,
|
||||||
userAgent: DEFAULT_UA,
|
userAgent: DEFAULT_UA,
|
||||||
});
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
|
|
@ -141,6 +144,7 @@ export async function resolveF1tvPlay(
|
||||||
channelId: feed.channelId,
|
channelId: feed.channelId,
|
||||||
feedCount: played.feeds.length,
|
feedCount: played.feeds.length,
|
||||||
keys,
|
keys,
|
||||||
|
drmScheme,
|
||||||
session,
|
session,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1030,7 +1030,9 @@ export function registerViewerRoutes(
|
||||||
format: played.format,
|
format: played.format,
|
||||||
fallbackStreamUrl: null,
|
fallbackStreamUrl: null,
|
||||||
fallbackFormat: null,
|
fallbackFormat: null,
|
||||||
drm: played.keys.length ? { type: "clearkey", keys: played.keys } : null,
|
drm: played.keys.length
|
||||||
|
? { type: "clearkey", keys: played.keys, scheme: played.drmScheme }
|
||||||
|
: null,
|
||||||
channelId: played.channelId,
|
channelId: played.channelId,
|
||||||
feedCount: played.feedCount,
|
feedCount: played.feedCount,
|
||||||
watchSessionId,
|
watchSessionId,
|
||||||
|
|
|
||||||
|
|
@ -1101,7 +1101,9 @@ export async function getScheduleEventPlay(
|
||||||
format: played.format,
|
format: played.format,
|
||||||
fallbackStreamUrl: null,
|
fallbackStreamUrl: null,
|
||||||
fallbackFormat: null,
|
fallbackFormat: null,
|
||||||
drm: played.keys.length ? { type: "clearkey" as const, keys: played.keys } : null,
|
drm: played.keys.length
|
||||||
|
? { type: "clearkey" as const, keys: played.keys, scheme: played.drmScheme }
|
||||||
|
: null,
|
||||||
watchSessionId: await attachWatch(pub),
|
watchSessionId: await attachWatch(pub),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue