diff --git a/src/compat/browser_detection.ts b/src/compat/browser_detection.ts index f22fd8d85b..87dd321829 100644 --- a/src/compat/browser_detection.ts +++ b/src/compat/browser_detection.ts @@ -24,71 +24,118 @@ interface IIE11Document extends Document { documentMode? : unknown; } -// true on IE11 -// false on Edge and other IEs/browsers. -const isIE11 : boolean = - !isNode && - typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" && - typeof (document as IIE11Document).documentMode !== "undefined"; - -// true for IE / Edge -const isIEOrEdge : boolean = isNode ? - false : - navigator.appName === "Microsoft Internet Explorer" || - navigator.appName === "Netscape" && - /(Trident|Edge)\//.test(navigator.userAgent); - -const isEdgeChromium: boolean = !isNode && - navigator.userAgent.toLowerCase().indexOf("edg/") !== -1; - -const isFirefox : boolean = !isNode && - navigator.userAgent.toLowerCase().indexOf("firefox") !== -1; - -const isSamsungBrowser : boolean = !isNode && - /SamsungBrowser/.test(navigator.userAgent); - -const isTizen : boolean = !isNode && - /Tizen/.test(navigator.userAgent); - -const isWebOs : boolean = !isNode && - navigator.userAgent.indexOf("Web0S") >= 0; - -// Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/ -// Note: even that page doesn't correspond to what we've actually seen in the -// wild -const isWebOs2021 : boolean = isWebOs && - ( - /[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) || - /[Cc]hr[o0]me\/79/.test(navigator.userAgent) - ); -const isWebOs2022 : boolean = isWebOs && - ( - /[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) || - /[Cc]hr[o0]me\/87/.test(navigator.userAgent) - ); +/** Edge Chromium, regardless of the device */ +let isEdgeChromium = false; -interface ISafariWindowObject extends Window { - safari? : { pushNotification? : { toString() : string } }; -} +/** IE11, regardless of the device */ +let isIE11 = false; + +/** IE11 or Edge __Legacy__ (not Edge Chromium), regardless of the device */ +let isIEOrEdge = false; + +/** Firefox, regardless of the device */ +let isFirefox = false; /** `true` on Safari on a PC platform (i.e. not iPhone / iPad etc.) */ -const isSafariDesktop : boolean = - !isNode && ( +let isSafariDesktop = false; + +/** `true` on Safari on an iPhone, iPad & iPod platform */ +let isSafariMobile = false; + +/** Samsung's own browser application */ +let isSamsungBrowser = false; + +/** `true` on devices where Tizen is the OS (e.g. Samsung TVs). */ +let isTizen = false; + +/** `true` on devices where WebOS is the OS (e.g. LG TVs). */ +let isWebOs = false; + +/** `true` specifically for WebOS 2021 version. */ +let isWebOs2021 = false; + +/** `true` specifically for WebOS 2022 version. */ +let isWebOs2022 = false; + +/** `true` for Panasonic devices. */ +let isPanasonic = false; + +((function findCurrentBrowser() : void { + if (isNode) { + return ; + } + + // 1 - Find out browser between IE/Edge Legacy/Edge Chromium/Firefox/Safari + + if (typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" && + typeof (document as IIE11Document).documentMode !== "undefined") + { + isIE11 = true; + isIEOrEdge = true; + } else if ( + navigator.appName === "Microsoft Internet Explorer" || + navigator.appName === "Netscape" && + /(Trident|Edge)\//.test(navigator.userAgent) + ) { + isIEOrEdge = true; + } else if (navigator.userAgent.toLowerCase().indexOf("edg/") !== -1) { + isEdgeChromium = true; + } else if (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1) { + isFirefox = true; + } else if (typeof navigator.platform === "string" && + /iPad|iPhone|iPod/.test(navigator.platform)) + { + isSafariMobile = true; + } else if ( Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") >= 0 || (window as ISafariWindowObject).safari?.pushNotification?.toString() === "[object SafariRemoteNotification]" - ); + ) { + isSafariDesktop = true; + } -/** `true` on Safari on an iPhone, iPad & iPod platform */ -const isSafariMobile : boolean = !isNode && - typeof navigator.platform === "string" && - /iPad|iPhone|iPod/.test(navigator.platform); + // 2 - Find out specific device/platform information + + // Samsung browser e.g. on Android + if (/SamsungBrowser/.test(navigator.userAgent)) { + isSamsungBrowser = true; + } + + if (/Tizen/.test(navigator.userAgent)) { + isTizen = true; + + // Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/ + // Note: even that page doesn't correspond to what we've actually seen in the + // wild + } else if (/[Ww]eb[O0]S/.test(navigator.userAgent)) { + isWebOs = true; + + if ( + /[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) || + /[Cc]hr[o0]me\/87/.test(navigator.userAgent) + ) { + isWebOs2022 = true; + } else if ( + /[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) || + /[Cc]hr[o0]me\/79/.test(navigator.userAgent) + ) { + isWebOs2021 = true; + } + } else if (/[Pp]anasonic/.test(navigator.userAgent)) { + isPanasonic = true; + } +})()); + +interface ISafariWindowObject extends Window { + safari? : { pushNotification? : { toString() : string } }; +} export { isEdgeChromium, isIE11, isIEOrEdge, isFirefox, + isPanasonic, isSafariDesktop, isSafariMobile, isSamsungBrowser, diff --git a/src/compat/can_reuse_media_keys.ts b/src/compat/can_reuse_media_keys.ts index 88eaaab8b6..efa893e5b6 100644 --- a/src/compat/can_reuse_media_keys.ts +++ b/src/compat/can_reuse_media_keys.ts @@ -1,4 +1,7 @@ -import { isWebOs } from "./browser_detection"; +import { + isPanasonic, + isWebOs, +} from "./browser_detection"; /** * Returns `true` if a `MediaKeys` instance (the `Encrypted Media Extension` @@ -13,5 +16,5 @@ import { isWebOs } from "./browser_detection"; * @returns {boolean} */ export default function canReuseMediaKeys() : boolean { - return !isWebOs; + return !isWebOs && !isPanasonic; } diff --git a/src/core/decrypt/__tests__/__global__/utils.ts b/src/core/decrypt/__tests__/__global__/utils.ts index a221289cf6..1e9afde54a 100644 --- a/src/core/decrypt/__tests__/__global__/utils.ts +++ b/src/core/decrypt/__tests__/__global__/utils.ts @@ -40,48 +40,68 @@ import { import { CancellationSignal } from "../../../../utils/task_canceller"; /** Default MediaKeySystemAccess configuration used by the RxPlayer. */ -export const defaultKSConfig = [{ - audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, - { contentType: "audio/webm;codecs=opus" } ], - distinctiveIdentifier: "optional" as const, - initDataTypes: ["cenc"] as const, - persistentState: "optional" as const, - sessionTypes: ["temporary"] as const, - videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, - { contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, - { contentType: "video/webm;codecs=\"vp8\"" } ], -}]; +export const defaultKSConfig = [ + { + audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, + { contentType: "audio/webm;codecs=opus" } ], + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, + { contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, + { contentType: "video/webm;codecs=\"vp8\"" } ], + }, + { + audioCapabilities: undefined, + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: undefined, + }, +]; /** * Default "com.microsoft.playready.recommendation" MediaKeySystemAccess * configuration used by the RxPlayer. */ -export const defaultPRRecommendationKSConfig = [{ - audioCapabilities: [ { robustness: "3000", - contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, - { robustness: "3000", - contentType: "audio/webm;codecs=opus" }, - { robustness: "2000", - contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, - { robustness: "2000", - contentType: "audio/webm;codecs=opus" } ], - distinctiveIdentifier: "optional" as const, - initDataTypes: ["cenc"] as const, - persistentState: "optional" as const, - sessionTypes: ["temporary"] as const, - videoCapabilities: [ { robustness: "3000", - contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, - { robustness: "3000", - contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, - { robustness: "3000", - contentType: "video/webm;codecs=\"vp8\"" }, - { robustness: "2000", - contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, - { robustness: "2000", - contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, - { robustness: "2000", - contentType: "video/webm;codecs=\"vp8\"" } ], -}]; +export const defaultPRRecommendationKSConfig = [ + { + audioCapabilities: [ { robustness: "3000", + contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, + { robustness: "3000", + contentType: "audio/webm;codecs=opus" }, + { robustness: "2000", + contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, + { robustness: "2000", + contentType: "audio/webm;codecs=opus" } ], + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: [ { robustness: "3000", + contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, + { robustness: "3000", + contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, + { robustness: "3000", + contentType: "video/webm;codecs=\"vp8\"" }, + { robustness: "2000", + contentType: "video/mp4;codecs=\"avc1.4d401e\"" }, + { robustness: "2000", + contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, + { robustness: "2000", + contentType: "video/webm;codecs=\"vp8\"" } ], + }, + { + audioCapabilities: undefined, + distinctiveIdentifier: "optional" as const, + initDataTypes: ["cenc"] as const, + persistentState: "optional" as const, + sessionTypes: ["temporary"] as const, + videoCapabilities: undefined, + }, +]; /** Default Widevine MediaKeySystemAccess configuration used by the RxPlayer. */ export const defaultWidevineConfig = (() => { @@ -104,9 +124,10 @@ export const defaultWidevineConfig = (() => { { contentType: "audio/webm;codecs=opus", robustness } ]; }); - return defaultKSConfig.map(conf => { - return { ...conf, audioCapabilities, videoCapabilities }; - }); + return [ + { ...defaultKSConfig[0], audioCapabilities, videoCapabilities }, + defaultKSConfig[1], + ]; })(); /** diff --git a/src/core/decrypt/find_key_system.ts b/src/core/decrypt/find_key_system.ts index 2914c96eab..85bffb986f 100644 --- a/src/core/decrypt/find_key_system.ts +++ b/src/core/decrypt/find_key_system.ts @@ -155,7 +155,9 @@ function buildKeySystemConfigurations( if (keySystem.distinctiveIdentifierRequired === true) { distinctiveIdentifier = "required"; } - const { EME_DEFAULT_WIDEVINE_ROBUSTNESSES, + const { EME_DEFAULT_AUDIO_CODECS, + EME_DEFAULT_VIDEO_CODECS, + EME_DEFAULT_WIDEVINE_ROBUSTNESSES, EME_DEFAULT_PLAYREADY_ROBUSTNESSES } = config.getCurrent(); // Set robustness, in order of consideration: @@ -206,42 +208,41 @@ function buildKeySystemConfigurations( // https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent const videoCapabilities: IMediaCapability[] = - flatMap(videoRobustnesses, (robustness) => - ["video/mp4;codecs=\"avc1.4d401e\"", - "video/mp4;codecs=\"avc1.42e01e\"", - "video/webm;codecs=\"vp8\""].map(contentType => { - return robustness !== undefined ? { contentType, robustness } : - { contentType }; - })); + flatMap(videoRobustnesses, (robustness) => { + return EME_DEFAULT_VIDEO_CODECS.map(contentType => { + return robustness === undefined ? { contentType } : + { contentType, robustness }; + }); + }); const audioCapabilities: IMediaCapability[] = - flatMap(audioRobustnesses, (robustness) => - ["audio/mp4;codecs=\"mp4a.40.2\"", - "audio/webm;codecs=opus"].map(contentType => { - return robustness !== undefined ? { contentType, robustness } : - { contentType }; - })); - - // TODO Re-test with a set contentType but an undefined robustness on the - // STBs on which this problem was found. - // - // add another with no {audio,video}Capabilities for some legacy browsers. - // As of today's spec, this should return NotSupported but the first - // candidate configuration should be good, so we should have no downside - // doing that. - // initDataTypes: ["cenc"], - // videoCapabilities: undefined, - // audioCapabilities: undefined, - // distinctiveIdentifier, - // persistentState, - // sessionTypes, - - return [{ initDataTypes: ["cenc"], - videoCapabilities, - audioCapabilities, - distinctiveIdentifier, - persistentState, - sessionTypes }]; + flatMap(audioRobustnesses, (robustness) => { + return EME_DEFAULT_AUDIO_CODECS.map(contentType => { + return robustness === undefined ? { contentType } : + { contentType, robustness }; + }); + }); + + const wantedMediaKeySystemConfiguration : MediaKeySystemConfiguration = { + initDataTypes: ["cenc"], + videoCapabilities, + audioCapabilities, + distinctiveIdentifier, + persistentState, + sessionTypes, + }; + + return [ + wantedMediaKeySystemConfiguration, + + // Some legacy implementations have issues with `audioCapabilities` and + // `videoCapabilities`, so we're including a supplementary + // `MediaKeySystemConfiguration` without those properties. + { ...wantedMediaKeySystemConfiguration, + audioCapabilities: undefined , + videoCapabilities: undefined, + } as unknown as MediaKeySystemConfiguration, + ]; } /** diff --git a/src/default_config.ts b/src/default_config.ts index 98253ea88f..d198f1e0de 100644 --- a/src/default_config.ts +++ b/src/default_config.ts @@ -923,15 +923,36 @@ const DEFAULT_CONFIG = { */ MIN_CANCELABLE_PRIORITY: 3, // priority number 3 onward can be cancelled - /** - * Robustnesses used in the {audio,video}Capabilities of the - * MediaKeySystemConfiguration (DRM). - * - * Only used for widevine keysystems. - * - * Defined in order of importance (first will be tested first etc.) - * @type {Array.} - */ + /** + * Codecs used in the videoCapabilities of the MediaKeySystemConfiguration + * (DRM). + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ + EME_DEFAULT_VIDEO_CODECS: [ "video/mp4;codecs=\"avc1.4d401e\"", + "video/mp4;codecs=\"avc1.42e01e\"", + "video/webm;codecs=\"vp8\"" ], + + /** + * Codecs used in the audioCapabilities of the MediaKeySystemConfiguration + * (DRM). + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ + EME_DEFAULT_AUDIO_CODECS: [ "audio/mp4;codecs=\"mp4a.40.2\"", + "audio/webm;codecs=opus" ], + + /** + * Robustnesses used in the {audio,video}Capabilities of the + * MediaKeySystemConfiguration (DRM). + * + * Only used for widevine keysystems. + * + * Defined in order of importance (first will be tested first etc.) + * @type {Array.} + */ EME_DEFAULT_WIDEVINE_ROBUSTNESSES: [ "HW_SECURE_ALL", "HW_SECURE_DECODE", "HW_SECURE_CRYPTO",