Align Viaplay play-live and clips with website HAR.
This commit is contained in:
parent
d1c3176ffe
commit
672d3597c6
5 changed files with 147 additions and 73 deletions
|
|
@ -766,12 +766,55 @@ function extractProducts(json: unknown): ViaplayEpgProduct[] {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** HAR: Don't go-placeholder (live soft-fail). Clips mogen dit pad wél hebben. */
|
||||||
|
export const VIAPLAY_DONT_GO_MEDIA_RE = /\/7c4cd64\/88000100-1778484912807\.ism\b/i;
|
||||||
|
|
||||||
|
export function isDontGoPlaceholder(mpdUrl: string | null | undefined): boolean {
|
||||||
|
return !!mpdUrl && VIAPLAY_DONT_GO_MEDIA_RE.test(mpdUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLiveDashMpd(mpdUrl: string | null | undefined): boolean {
|
||||||
|
return !!mpdUrl && /live-dash/i.test(mpdUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sportContentUrls(
|
||||||
|
pageUrl: string | null | undefined,
|
||||||
|
deviceKey: string,
|
||||||
|
mediaGuid: string
|
||||||
|
): { productUrl: string; contextualUrl: string; contextualv2Url: string } {
|
||||||
|
const guidPath = mediaGuid.replace(/-NL$/i, "").toLowerCase();
|
||||||
|
let clean = "";
|
||||||
|
const raw = (pageUrl || "").trim();
|
||||||
|
if (raw) {
|
||||||
|
try {
|
||||||
|
const u = new URL(raw);
|
||||||
|
u.search = "";
|
||||||
|
clean = u.toString().replace(/\/$/, "");
|
||||||
|
} catch {
|
||||||
|
clean = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!clean) {
|
||||||
|
clean = `https://content.viaplay.com/${deviceKey}/sport/${guidPath}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
productUrl: `${clean}?partial=true`,
|
||||||
|
contextualUrl: `${clean}?contextual=true`,
|
||||||
|
contextualv2Url: `${clean}?contextualV2=true&productType=sport`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function playByMediaGuid(
|
export async function playByMediaGuid(
|
||||||
creds: ViaplayAccountCreds,
|
creds: ViaplayAccountCreds,
|
||||||
mediaGuidRaw: string,
|
mediaGuidRaw: string,
|
||||||
opts: { deviceKey: string; timeoutMs: number }
|
opts: { deviceKey: string; timeoutMs: number; pageUrl?: string | null }
|
||||||
): Promise<ViaplayPlayResult> {
|
): Promise<ViaplayPlayResult> {
|
||||||
const mediaGuid = toMediaGuid(mediaGuidRaw);
|
const mediaGuid = toMediaGuid(mediaGuidRaw);
|
||||||
|
const pages = sportContentUrls(opts.pageUrl, opts.deviceKey, mediaGuid);
|
||||||
|
const returnUrl = new URL(`https://content.viaplay.com/${opts.deviceKey}`);
|
||||||
|
returnUrl.searchParams.set("language", "nl");
|
||||||
|
if (creds.profileId) returnUrl.searchParams.set("profileId", creds.profileId);
|
||||||
|
|
||||||
const u = new URL("https://play-live.viaplay.com/api/stream/bymediaguid");
|
const u = new URL("https://play-live.viaplay.com/api/stream/bymediaguid");
|
||||||
u.searchParams.set("deviceId", creds.deviceId || randomUUID());
|
u.searchParams.set("deviceId", creds.deviceId || randomUUID());
|
||||||
u.searchParams.set("deviceName", "web");
|
u.searchParams.set("deviceName", "web");
|
||||||
|
|
@ -779,7 +822,19 @@ export async function playByMediaGuid(
|
||||||
u.searchParams.set("userAgent", VIAPLAY_USER_AGENT);
|
u.searchParams.set("userAgent", VIAPLAY_USER_AGENT);
|
||||||
u.searchParams.set("deviceKey", opts.deviceKey);
|
u.searchParams.set("deviceKey", opts.deviceKey);
|
||||||
u.searchParams.set("mediaGuid", mediaGuid);
|
u.searchParams.set("mediaGuid", mediaGuid);
|
||||||
|
u.searchParams.set("cse", "true");
|
||||||
u.searchParams.set("nocc", "autoplayer");
|
u.searchParams.set("nocc", "autoplayer");
|
||||||
|
u.searchParams.set(
|
||||||
|
"splitIOFlagIds",
|
||||||
|
"all_sports_beyondLiveBeacon_on,web_player_pause_ads_on"
|
||||||
|
);
|
||||||
|
u.searchParams.set("returnurl", returnUrl.toString());
|
||||||
|
u.searchParams.set("producturl", pages.productUrl);
|
||||||
|
u.searchParams.set("contextualurl", pages.contextualUrl);
|
||||||
|
u.searchParams.set("contextualv2url", pages.contextualv2Url);
|
||||||
|
u.searchParams.set("sectionPath", "/sport");
|
||||||
|
u.searchParams.set("defaultAvailabilityContext", "svod");
|
||||||
|
u.searchParams.set("win10edge", "true");
|
||||||
if (creds.profileId) u.searchParams.set("profileId", creds.profileId);
|
if (creds.profileId) u.searchParams.set("profileId", creds.profileId);
|
||||||
|
|
||||||
const res = await fetchJson(u.toString(), {
|
const res = await fetchJson(u.toString(), {
|
||||||
|
|
@ -793,7 +848,13 @@ export async function playByMediaGuid(
|
||||||
`play-live mislukt (${res.status})`;
|
`play-live mislukt (${res.status})`;
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
return parsePlayStreamBody(body, mediaGuid);
|
const played = parsePlayStreamBody(body, mediaGuid);
|
||||||
|
if (isDontGoPlaceholder(played.mpdUrl)) {
|
||||||
|
throw new Error(
|
||||||
|
"Viaplay play-live gaf Don't go-placeholder i.p.v. de wedstrijdstream"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return played;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePlayStreamBody(
|
function parsePlayStreamBody(
|
||||||
|
|
@ -907,42 +968,9 @@ function playBodyGuid(body: Record<string, unknown>): string | null {
|
||||||
return typeof system?.guid === "string" ? system.guid.trim() : null;
|
return typeof system?.guid === "string" ? system.guid.trim() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bekende Don't go soft-fail CDN-asset (titel/guid kloppen, media niet). */
|
|
||||||
const VIAPLAY_DONT_GO_MEDIA_RE = /\/7c4cd64\/88000100-1778484912807\.ism\b/i;
|
|
||||||
|
|
||||||
function cseMediaGuid(body: Record<string, unknown>): string | null {
|
|
||||||
try {
|
|
||||||
const cse = asRecord(body.cseReporting);
|
|
||||||
const link = asRecord(asRecord(cse?.link)?.reportingUrl);
|
|
||||||
const href = typeof link?.href === "string" ? link.href : "";
|
|
||||||
const m = href.match(/\/viaplay\/([^/$]+)/);
|
|
||||||
if (!m?.[1]) return null;
|
|
||||||
const payload = JSON.parse(
|
|
||||||
Buffer.from(m[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString()
|
|
||||||
) as { mediaGuid?: string };
|
|
||||||
return typeof payload.mediaGuid === "string" ? payload.mediaGuid : null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mediaHrefFromPlayBody(body: Record<string, unknown>): string | null {
|
|
||||||
const links = asRecord(body._links);
|
|
||||||
for (const key of [
|
|
||||||
"viaplay:media",
|
|
||||||
"viaplay:playlist",
|
|
||||||
"viaplay:encryptedPlaylist",
|
|
||||||
] as const) {
|
|
||||||
const href = asRecord(links?.[key])?.href;
|
|
||||||
if (typeof href === "string" && href.trim()) return href.trim();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HAR-success: guid + titel + product-link + echte media (geen Don't go soft-fail).
|
* HAR afspelen.har: web speelt clip als guid+titel kloppen, ook zonder DRM
|
||||||
* Soft-fail: 200 met juiste product-metadata maar placeholder-MPD “Don't go”
|
* en met cse mediaGuid=unknown. Alleen een promo-titel is een echte soft-fail.
|
||||||
* (vaak bij max devices / geen stream-recht).
|
|
||||||
*/
|
*/
|
||||||
function assertVodPlayMatchesGuid(
|
function assertVodPlayMatchesGuid(
|
||||||
body: Record<string, unknown>,
|
body: Record<string, unknown>,
|
||||||
|
|
@ -964,19 +992,6 @@ function assertVodPlayMatchesGuid(
|
||||||
throw new Error(`Viaplay gaf promo-stream (“${played.title}”)`);
|
throw new Error(`Viaplay gaf promo-stream (“${played.title}”)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaHref = mediaHrefFromPlayBody(body) || played.mpdUrl;
|
|
||||||
if (VIAPLAY_DONT_GO_MEDIA_RE.test(mediaHref)) {
|
|
||||||
throw new Error(
|
|
||||||
"Viaplay soft-fail: Don't go-placeholder i.p.v. echte stream (mediaGuid ontbreekt — geen speelrecht of account/stream-fout)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const streamMediaGuid = cseMediaGuid(body);
|
|
||||||
if (streamMediaGuid === "unknown") {
|
|
||||||
throw new Error(
|
|
||||||
"Viaplay soft-fail: mediaGuid=unknown (geen echte stream voor dit account)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const links = asRecord(body._links);
|
const links = asRecord(body._links);
|
||||||
const productHref = asRecord(links?.["viaplay:product"])?.href;
|
const productHref = asRecord(links?.["viaplay:product"])?.href;
|
||||||
if (typeof productHref !== "string" || !productHref.trim()) {
|
if (typeof productHref !== "string" || !productHref.trim()) {
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,11 @@ export async function getViaplayScheduleEvent(
|
||||||
if (!mediaGuid) return null;
|
if (!mediaGuid) return null;
|
||||||
const config = await loadViaplayConfigOnly();
|
const config = await loadViaplayConfigOnly();
|
||||||
const event = config?.events.find(
|
const event = config?.events.find(
|
||||||
(e) => e.mediaGuid === mediaGuid || e.guid === mediaGuid
|
(e) =>
|
||||||
|
e.mediaGuid === mediaGuid ||
|
||||||
|
e.guid === mediaGuid ||
|
||||||
|
e.mediaGuid === `${mediaGuid}-NL` ||
|
||||||
|
e.mediaGuid.replace(/-NL$/i, "") === mediaGuid.replace(/-NL$/i, "")
|
||||||
);
|
);
|
||||||
if (!event) return null;
|
if (!event) return null;
|
||||||
return eventToPublic(event, nowMs);
|
return eventToPublic(event, nowMs);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import {
|
||||||
fetchMpdXml,
|
fetchMpdXml,
|
||||||
extractPsshFromMpd,
|
extractPsshFromMpd,
|
||||||
fetchViaplayBootstrapPssh,
|
fetchViaplayBootstrapPssh,
|
||||||
|
isDontGoPlaceholder,
|
||||||
|
isLiveDashMpd,
|
||||||
VIAPLAY_USER_AGENT,
|
VIAPLAY_USER_AGENT,
|
||||||
} from "./client";
|
} from "./client";
|
||||||
import {
|
import {
|
||||||
|
|
@ -49,7 +51,7 @@ function recordVodPlay( partial: Omit<LastVodPlayDiag, "at" | "resolver"> ) {
|
||||||
lastVodPlay = {
|
lastVodPlay = {
|
||||||
...partial,
|
...partial,
|
||||||
at: new Date().toISOString(),
|
at: new Date().toISOString(),
|
||||||
resolver: "viaplay-byguid-har-v7",
|
resolver: "viaplay-byguid-har-v8",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,6 +140,8 @@ export type ResolveViaplayPlayResult = {
|
||||||
mediaGuid: string;
|
mediaGuid: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
|
/** false = catch-up/VOD — client mag niet naar live-edge zoeken. */
|
||||||
|
live: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function accountOrder(
|
function accountOrder(
|
||||||
|
|
@ -205,24 +209,31 @@ export async function ensureViaplayEventReady(
|
||||||
!!event.mpdUrl &&
|
!!event.mpdUrl &&
|
||||||
isMpdCacheFresh(event.mpdCachedAt, tweaks.mpdCacheTtlMinutes);
|
isMpdCacheFresh(event.mpdCachedAt, tweaks.mpdCacheTtlMinutes);
|
||||||
|
|
||||||
if (cacheFresh && cachedKeys.length) {
|
if (cacheFresh && cachedKeys.length && !isDontGoPlaceholder(event.mpdUrl)) {
|
||||||
return {
|
return {
|
||||||
streamUrl: event.mpdUrl!,
|
streamUrl: event.mpdUrl!,
|
||||||
format: "dash",
|
format: "dash",
|
||||||
keys: cachedKeys,
|
keys: cachedKeys,
|
||||||
fromCache: true,
|
fromCache: true,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
|
live: !ended,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terugkijken: zelfde pad als het werkende script — KAP op zender-MPD, geen play-live.
|
// Terugkijken: KAP alleen op echte live-dash zender-MPD (niet op VOD/Don't go).
|
||||||
if (ended && cachedMpd && !opts?.forceRefresh) {
|
if (
|
||||||
|
ended &&
|
||||||
|
cachedMpd &&
|
||||||
|
!opts?.forceRefresh &&
|
||||||
|
isLiveDashMpd(cachedMpd) &&
|
||||||
|
!isDontGoPlaceholder(cachedMpd)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const kap = await resolveKapMpd(
|
const kap = await resolveKapMpd(
|
||||||
cachedMpd,
|
cachedMpd,
|
||||||
event.startMs,
|
event.startMs,
|
||||||
event.endMs,
|
event.endMs,
|
||||||
tweaks.httpTimeoutMs
|
Math.min(tweaks.httpTimeoutMs, 12_000)
|
||||||
);
|
);
|
||||||
if (!cachedKeys.length) {
|
if (!cachedKeys.length) {
|
||||||
cachedKeys = await keysForChannelPath(sessionSecret, kap.mpdUrl);
|
cachedKeys = await keysForChannelPath(sessionSecret, kap.mpdUrl);
|
||||||
|
|
@ -242,6 +253,7 @@ export async function ensureViaplayEventReady(
|
||||||
keys: cachedKeys,
|
keys: cachedKeys,
|
||||||
fromCache: true,
|
fromCache: true,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
|
live: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -250,13 +262,19 @@ export async function ensureViaplayEventReady(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 &&
|
||||||
|
!isDontGoPlaceholder(event.mpdUrl) &&
|
||||||
|
!(opts?.fetchKeys ?? tweaks.fetchKeysIfMissing)
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
streamUrl: event.mpdUrl,
|
streamUrl: event.mpdUrl,
|
||||||
format: "dash",
|
format: "dash",
|
||||||
keys: cachedKeys,
|
keys: cachedKeys,
|
||||||
fromCache: true,
|
fromCache: true,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
|
live: !ended,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,13 +307,21 @@ export async function ensureViaplayEventReady(
|
||||||
const play = await playByMediaGuid(creds, mediaGuid, {
|
const play = await playByMediaGuid(creds, mediaGuid, {
|
||||||
deviceKey: tweaks.deviceKey,
|
deviceKey: tweaks.deviceKey,
|
||||||
timeoutMs: tweaks.httpTimeoutMs,
|
timeoutMs: tweaks.httpTimeoutMs,
|
||||||
|
pageUrl: event.pageUrl,
|
||||||
});
|
});
|
||||||
const kap = await resolveKapMpd(
|
const kap = isLiveDashMpd(play.mpdUrl)
|
||||||
|
? await resolveKapMpd(
|
||||||
play.mpdUrl,
|
play.mpdUrl,
|
||||||
event.startMs,
|
event.startMs,
|
||||||
event.endMs,
|
event.endMs,
|
||||||
tweaks.httpTimeoutMs
|
Math.min(tweaks.httpTimeoutMs, ended ? 12_000 : tweaks.httpTimeoutMs)
|
||||||
);
|
)
|
||||||
|
: {
|
||||||
|
mpdUrl: play.mpdUrl,
|
||||||
|
xml: "",
|
||||||
|
hasPssh: false,
|
||||||
|
pssh: null as string | null,
|
||||||
|
};
|
||||||
|
|
||||||
let keys = cachedKeys;
|
let keys = cachedKeys;
|
||||||
if (!keys.length) {
|
if (!keys.length) {
|
||||||
|
|
@ -362,6 +388,7 @@ export async function ensureViaplayEventReady(
|
||||||
fromCache: false,
|
fromCache: false,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
|
live: !ended,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
lastError = err instanceof Error ? err : new Error(String(err));
|
lastError = err instanceof Error ? err : new Error(String(err));
|
||||||
|
|
@ -383,6 +410,7 @@ export async function resolveViaplayPlay(
|
||||||
sessionSecret: string,
|
sessionSecret: string,
|
||||||
eventId: string
|
eventId: string
|
||||||
): Promise<ResolveViaplayPlayResult> {
|
): Promise<ResolveViaplayPlayResult> {
|
||||||
|
try {
|
||||||
const settings = await loadViaplaySettings(sessionSecret);
|
const settings = await loadViaplaySettings(sessionSecret);
|
||||||
const event = findViaplayEvent(settings.config, eventId);
|
const event = findViaplayEvent(settings.config, eventId);
|
||||||
if (!event) throw new AppError("NOT_FOUND", "Viaplay-event niet gevonden", 404);
|
if (!event) throw new AppError("NOT_FOUND", "Viaplay-event niet gevonden", 404);
|
||||||
|
|
@ -392,8 +420,15 @@ export async function resolveViaplayPlay(
|
||||||
if (now > event.endMs + VIAPLAY_CATCHUP_MS) {
|
if (now > event.endMs + VIAPLAY_CATCHUP_MS) {
|
||||||
throw new AppError("GONE", "Dit event is afgelopen", 410);
|
throw new AppError("GONE", "Dit event is afgelopen", 410);
|
||||||
}
|
}
|
||||||
// Pre-live: wel MPD/keys toestaan binnen preLiveMinutes; viewer play blijft live-only
|
return await ensureViaplayEventReady(sessionSecret, event);
|
||||||
return ensureViaplayEventReady(sessionSecret, event);
|
} catch (err) {
|
||||||
|
if (err instanceof AppError) throw err;
|
||||||
|
throw new AppError(
|
||||||
|
"UPSTREAM",
|
||||||
|
err instanceof Error ? err.message : "Viaplay play mislukt",
|
||||||
|
502
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -403,6 +438,23 @@ export async function resolveViaplayGuidPlay(
|
||||||
sessionSecret: string,
|
sessionSecret: string,
|
||||||
guidRaw: string,
|
guidRaw: string,
|
||||||
opts?: { productUrl?: string | null }
|
opts?: { productUrl?: string | null }
|
||||||
|
): Promise<ResolveViaplayPlayResult> {
|
||||||
|
try {
|
||||||
|
return await resolveViaplayGuidPlayInner(sessionSecret, guidRaw, opts);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof AppError) throw err;
|
||||||
|
throw new AppError(
|
||||||
|
"UPSTREAM",
|
||||||
|
err instanceof Error ? err.message : "Viaplay VOD-play mislukt",
|
||||||
|
502
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveViaplayGuidPlayInner(
|
||||||
|
sessionSecret: string,
|
||||||
|
guidRaw: string,
|
||||||
|
opts?: { productUrl?: string | null }
|
||||||
): Promise<ResolveViaplayPlayResult> {
|
): Promise<ResolveViaplayPlayResult> {
|
||||||
const guid = String(guidRaw || "").trim();
|
const guid = String(guidRaw || "").trim();
|
||||||
if (!guid) throw new AppError("BAD_REQUEST", "Geen Viaplay guid", 400);
|
if (!guid) throw new AppError("BAD_REQUEST", "Geen Viaplay guid", 400);
|
||||||
|
|
@ -544,6 +596,7 @@ export async function resolveViaplayGuidPlay(
|
||||||
mediaGuid: guid,
|
mediaGuid: guid,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
title: play.title,
|
title: play.title,
|
||||||
|
live: false,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
lastError = err instanceof Error ? err : new Error(String(err));
|
lastError = err instanceof Error ? err : new Error(String(err));
|
||||||
|
|
|
||||||
|
|
@ -1094,8 +1094,9 @@ export function registerViewerRoutes(
|
||||||
format: played.format,
|
format: played.format,
|
||||||
fallbackStreamUrl: null,
|
fallbackStreamUrl: null,
|
||||||
fallbackFormat: null,
|
fallbackFormat: null,
|
||||||
|
live: false,
|
||||||
/** Deploy/debug: bevestigt VOD-resolver revisie na Dokploy. */
|
/** Deploy/debug: bevestigt VOD-resolver revisie na Dokploy. */
|
||||||
resolver: "viaplay-byguid-har-v7",
|
resolver: "viaplay-byguid-har-v8",
|
||||||
mediaPath: (() => {
|
mediaPath: (() => {
|
||||||
try {
|
try {
|
||||||
const u = new URL(played.streamUrl);
|
const u = new URL(played.streamUrl);
|
||||||
|
|
|
||||||
|
|
@ -1134,6 +1134,7 @@ export async function getScheduleEventPlay(
|
||||||
format: played.format,
|
format: played.format,
|
||||||
fallbackStreamUrl: null,
|
fallbackStreamUrl: null,
|
||||||
fallbackFormat: null,
|
fallbackFormat: null,
|
||||||
|
live: played.live,
|
||||||
drm: played.keys.length
|
drm: played.keys.length
|
||||||
? { type: "clearkey" as const, keys: played.keys }
|
? { type: "clearkey" as const, keys: played.keys }
|
||||||
: null,
|
: null,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue