diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index 7c588abd838..e0b49016893 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -1137,8 +1137,14 @@ export class EMEController extends Logger implements ComponentAPI { // (undocumented) destroy(): void; // (undocumented) + getKeySystemAccess(keySystemsToAttempt: KeySystems[]): Promise; + // (undocumented) + getSelectedKeySystemFormats(): KeySystemFormats[]; + // (undocumented) loadKey(data: KeyLoadedData): Promise; // (undocumented) + selectKeySystem(keySystemsToAttempt: KeySystems[]): Promise; + // (undocumented) selectKeySystemFormat(frag: Fragment): Promise; } @@ -1153,6 +1159,7 @@ export type EMEControllerConfig = { drmSystems: DRMSystemsConfiguration; drmSystemOptions: DRMSystemOptions; requestMediaKeySystemAccessFunc: MediaKeyFunc | null; + requireKeySystemAccessOnStart: boolean; }; // Warning: (ae-missing-release-tag) "ErrorActionFlags" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2917,7 +2924,7 @@ export class KeyLoader implements ComponentAPI { // (undocumented) load(frag: Fragment): Promise; // (undocumented) - loadClear(loadingFrag: Fragment, encryptedFragments: Fragment[]): void | Promise; + loadClear(loadingFrag: Fragment, encryptedFragments: Fragment[]): null | Promise; // (undocumented) loadInternal(frag: Fragment, keySystemFormat?: KeySystemFormats): Promise; // (undocumented) diff --git a/src/config.ts b/src/config.ts index 65ee3f57c97..e6ee30409bd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -122,6 +122,7 @@ export type EMEControllerConfig = { drmSystems: DRMSystemsConfiguration; drmSystemOptions: DRMSystemOptions; requestMediaKeySystemAccessFunc: MediaKeyFunc | null; + requireKeySystemAccessOnStart: boolean; }; export interface FragmentLoaderConstructor { @@ -432,6 +433,7 @@ export const hlsDefaultConfig: HlsConfig = { requestMediaKeySystemAccessFunc: __USE_EME_DRM__ ? requestMediaKeySystemAccess : null, // used by eme-controller + requireKeySystemAccessOnStart: false, // used by eme-controller testBandwidth: true, progressive: false, lowLatencyMode: true, diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index e12c676564b..98db989587b 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -31,6 +31,7 @@ import { getPartWith, updateFragPTSDTS, } from '../utils/level-helper'; +import { getKeySystemsForConfig } from '../utils/mediakeys-helper'; import { appendUint8Array } from '../utils/mp4-tools'; import TimeRanges from '../utils/time-ranges'; import type { FragmentTracker } from './fragment-tracker'; @@ -837,8 +838,14 @@ export default class BaseStreamController new Error(`frag load aborted, context changed in KEY_LOADING`), ); } - } else if (!frag.encrypted && details.encryptedFragments.length) { - this.keyLoader.loadClear(frag, details.encryptedFragments); + } else if (!frag.encrypted) { + keyLoadingPromise = this.keyLoader.loadClear( + frag, + details.encryptedFragments, + ); + if (keyLoadingPromise) { + this.log(`[eme] blocking frag load until media-keys acquired`); + } } const fragPrevious = this.fragPrevious; diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index 7a4e3b5d77b..6ffdfca6f19 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -51,6 +51,7 @@ interface KeySystemAccessPromises { keySystemAccess: Promise; mediaKeys?: Promise; certificate?: Promise; + hasMediaKeys?: boolean; } export interface MediaKeySessionContext { @@ -284,6 +285,7 @@ class EMEController extends Logger implements ComponentAPI { .createMediaKeys() .then((mediaKeys) => { this.log(`Media-keys created for "${keySystem}"`); + keySystemAccessPromises.hasMediaKeys = true; return certificateRequest.then((certificate) => { if (certificate) { return this.setMediaKeysServerCertificate( @@ -383,29 +385,29 @@ class EMEController extends Logger implements ComponentAPI { return keySession.update(data); } - public selectKeySystemFormat(frag: Fragment): Promise { - const keyFormats = Object.keys(frag.levelkeys || {}) as KeySystemFormats[]; - if (!this.keyFormatPromise) { - this.log( - `Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${ - frag.level - }) key formats ${keyFormats.join(', ')}`, - ); - this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); - } - return this.keyFormatPromise; + public getSelectedKeySystemFormats(): KeySystemFormats[] { + return (Object.keys(this.keySystemAccessPromises) as KeySystems[]) + .map((keySystem) => ({ + keySystem, + hasMediaKeys: this.keySystemAccessPromises[keySystem].hasMediaKeys, + })) + .filter(({ hasMediaKeys }) => !!hasMediaKeys) + .map(({ keySystem }) => keySystemToKeySystemFormat(keySystem)) + .filter((keySystem): keySystem is KeySystemFormats => !!keySystem); } - private getKeyFormatPromise( - keyFormats: KeySystemFormats[], + public getKeySystemAccess(keySystemsToAttempt: KeySystems[]): Promise { + return this.getKeySystemSelectionPromise(keySystemsToAttempt).then( + ({ keySystem, mediaKeys }) => { + return this.attemptSetMediaKeys(keySystem, mediaKeys); + }, + ); + } + + public selectKeySystem( + keySystemsToAttempt: KeySystems[], ): Promise { return new Promise((resolve, reject) => { - const keySystemsInConfig = getKeySystemsForConfig(this.config); - const keySystemsToAttempt = keyFormats - .map(keySystemFormatToKeySystemDomain) - .filter( - (value) => !!value && keySystemsInConfig.indexOf(value) !== -1, - ) as any as KeySystems[]; return this.getKeySystemSelectionPromise(keySystemsToAttempt) .then(({ keySystem }) => { const keySystemFormat = keySystemToKeySystemFormat(keySystem); @@ -421,6 +423,32 @@ class EMEController extends Logger implements ComponentAPI { }); } + public selectKeySystemFormat(frag: Fragment): Promise { + const keyFormats = Object.keys(frag.levelkeys || {}) as KeySystemFormats[]; + if (!this.keyFormatPromise) { + this.log( + `Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${ + frag.level + }) key formats ${keyFormats.join(', ')}`, + ); + this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); + } + return this.keyFormatPromise; + } + + private getKeyFormatPromise( + keyFormats: KeySystemFormats[], + ): Promise { + const keySystemsInConfig = getKeySystemsForConfig(this.config); + const keySystemsToAttempt = keyFormats + .map(keySystemFormatToKeySystemDomain) + .filter( + (value) => !!value && keySystemsInConfig.indexOf(value) !== -1, + ) as any as KeySystems[]; + + return this.selectKeySystem(keySystemsToAttempt); + } + public loadKey(data: KeyLoadedData): Promise { const decryptdata = data.keyInfo.decryptdata; diff --git a/src/loader/key-loader.ts b/src/loader/key-loader.ts index 5042a944d52..de0b1b1315c 100644 --- a/src/loader/key-loader.ts +++ b/src/loader/key-loader.ts @@ -1,7 +1,11 @@ import { LoadError } from './fragment-loader'; import { ErrorDetails, ErrorTypes } from '../errors'; -import type { HlsConfig } from '../config'; +import { + getKeySystemsForConfig, + keySystemFormatToKeySystemDomain, +} from '../utils/mediakeys-helper'; import type { LevelKey } from './level-key'; +import type { HlsConfig } from '../config'; import type EMEController from '../controller/eme-controller'; import type { MediaKeySessionContext } from '../controller/eme-controller'; import type { Fragment } from '../loader/fragment'; @@ -90,25 +94,46 @@ export default class KeyLoader implements ComponentAPI { loadClear( loadingFrag: Fragment, encryptedFragments: Fragment[], - ): void | Promise { - if (this.emeController && this.config.emeEnabled) { - // access key-system with nearest key on start (loaidng frag is unencrypted) - const { sn, cc } = loadingFrag; - for (let i = 0; i < encryptedFragments.length; i++) { - const frag = encryptedFragments[i]; - if ( - cc <= frag.cc && - (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn) - ) { - this.emeController - .selectKeySystemFormat(frag) - .then((keySystemFormat) => { - frag.setKeyFormat(keySystemFormat); - }); - break; + ): null | Promise { + if ( + this.emeController && + this.config.emeEnabled && + !this.emeController.getSelectedKeySystemFormats().length + ) { + // access key-system with nearest key on start (loading frag is unencrypted) + if (encryptedFragments.length) { + const { sn, cc } = loadingFrag; + for (let i = 0; i < encryptedFragments.length; i++) { + const frag = encryptedFragments[i]; + if ( + cc <= frag.cc && + (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn) + ) { + return this.emeController + .selectKeySystemFormat(frag) + .then((keySystemFormat) => { + frag.setKeyFormat(keySystemFormat); + if ( + this.emeController && + this.config.requireKeySystemAccessOnStart + ) { + const keySystem = + keySystemFormatToKeySystemDomain(keySystemFormat); + if (keySystem) { + return this.emeController.getKeySystemAccess([keySystem]); + } + } + }); + } + } + } else if (this.config.requireKeySystemAccessOnStart) { + const keySystemsInConfig = getKeySystemsForConfig(this.config); + if (keySystemsInConfig.length) { + return this.emeController.getKeySystemAccess(keySystemsInConfig); } } } + return null; } load(frag: Fragment): Promise {