From e89ba46eb89c9c09d4c5eeccccddbfcb65e29913 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Sun, 27 Sep 2026 21:36:10 +0200 Subject: [PATCH] F1TV: drm.scheme (cbcs) meesturen zodat de app zelf kan ontsleutelen. --- apps/master-api/src/f1tv/cdm.ts | 14 +++++++++----- apps/master-api/src/f1tv/play.ts | 8 ++++++-- apps/master-api/src/viewer/routes.ts | 4 +++- apps/master-api/src/viewer/schedule-events.ts | 4 +++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/master-api/src/f1tv/cdm.ts b/apps/master-api/src/f1tv/cdm.ts index b245014..9fc93da 100644 --- a/apps/master-api/src/f1tv/cdm.ts +++ b/apps/master-api/src/f1tv/cdm.ts @@ -107,21 +107,25 @@ async function fetchMasterPlaylist(url: string, userAgent: string): Promise> { +}): Promise<{ keys: Array<{ kid: string; key: string }>; scheme: "cbcs" | "cenc" | null }> { const master = await fetchMasterPlaylist(input.streamUrl, input.userAgent); 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 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({ wvdPath: await resolveWvdPath(), @@ -139,5 +143,5 @@ export async function getF1tvClearKeys(input: { .filter((k): k is { kid: string; key: string } => !!k); if (!keys.length) throw new Error("F1TV license leverde geen content-sleutels"); keyCache.set(cacheKey, { keys, at: Date.now() }); - return keys; + return { keys, scheme }; } diff --git a/apps/master-api/src/f1tv/play.ts b/apps/master-api/src/f1tv/play.ts index fd9f75d..cc2f4d0 100644 --- a/apps/master-api/src/f1tv/play.ts +++ b/apps/master-api/src/f1tv/play.ts @@ -24,6 +24,8 @@ export async function resolveF1tvPlay( feedCount: number; /** ClearKey kid/key (hex) voor Widevine-sessies; leeg bij clear streams. */ keys: Array<{ kid: string; key: string }>; + /** "cbcs" = app moet zelf ontsleutelen (Android ClearKey ondersteunt alleen AES-CTR). */ + drmScheme: "cbcs" | "cenc" | null; session: F1tvSessionSnapshot | null; }> { const { accounts, config } = await loadF1tvSettings(sessionSecret); @@ -111,18 +113,19 @@ export async function resolveF1tvPlay( } let keys: Array<{ kid: string; key: string }> = []; + let drmScheme: "cbcs" | "cenc" | null = null; if (played.drmType === "widevine") { if (!played.licenseUrl) { throw new AppError("UPSTREAM_ERROR", "F1TV-stream is versleuteld maar zonder license-URL", 502); } try { - keys = await getF1tvClearKeys({ + ({ keys, scheme: drmScheme } = await getF1tvClearKeys({ streamUrl: feed.url, licenseUrl: played.licenseUrl, ascendontoken: auth.ascendontoken, entitlementtoken: played.entitlementToken || auth.entitlementtoken, userAgent: DEFAULT_UA, - }); + })); } catch (err) { const msg = err instanceof Error ? err.message : String(err); throw new AppError( @@ -141,6 +144,7 @@ export async function resolveF1tvPlay( channelId: feed.channelId, feedCount: played.feeds.length, keys, + drmScheme, session, }; } diff --git a/apps/master-api/src/viewer/routes.ts b/apps/master-api/src/viewer/routes.ts index 0318ff0..08dced8 100644 --- a/apps/master-api/src/viewer/routes.ts +++ b/apps/master-api/src/viewer/routes.ts @@ -1030,7 +1030,9 @@ export function registerViewerRoutes( format: played.format, fallbackStreamUrl: 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, feedCount: played.feedCount, watchSessionId, diff --git a/apps/master-api/src/viewer/schedule-events.ts b/apps/master-api/src/viewer/schedule-events.ts index 04e6593..b490704 100644 --- a/apps/master-api/src/viewer/schedule-events.ts +++ b/apps/master-api/src/viewer/schedule-events.ts @@ -1101,7 +1101,9 @@ export async function getScheduleEventPlay( format: played.format, fallbackStreamUrl: 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), }; }