Skip to content

Commit 14c84cf

Browse files
committed
Add support for EXT-X-SESSION-KEY tags (for key-system access on manifest loaded)
#4927
1 parent 0ac5902 commit 14c84cf

File tree

11 files changed

+267
-132
lines changed

11 files changed

+267
-132
lines changed

api-extractor/report/hls.js.api.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ export class DateRange {
313313
export type DRMSystemOptions = {
314314
audioRobustness?: string;
315315
videoRobustness?: string;
316+
audioEncryptionScheme?: string | null;
317+
videoEncryptionScheme?: string | null;
316318
persistentState?: MediaKeysRequirement;
317319
distinctiveIdentifier?: MediaKeysRequirement;
318320
sessionTypes?: string[];
@@ -1525,6 +1527,8 @@ export class LevelKey implements DecryptData {
15251527
// (undocumented)
15261528
readonly isCommonEncryption: boolean;
15271529
// (undocumented)
1530+
isSupported(): boolean;
1531+
// (undocumented)
15281532
iv: Uint8Array | null;
15291533
// (undocumented)
15301534
key: Uint8Array | null;
@@ -1839,6 +1843,8 @@ export interface ManifestLoadedData {
18391843
// (undocumented)
18401844
sessionData: Record<string, AttrList> | null;
18411845
// (undocumented)
1846+
sessionKeys: LevelKey[] | null;
1847+
// (undocumented)
18421848
stats: LoaderStats;
18431849
// (undocumented)
18441850
subtitles?: MediaPlaylist[];
@@ -1869,6 +1875,10 @@ export interface ManifestParsedData {
18691875
// (undocumented)
18701876
levels: Level[];
18711877
// (undocumented)
1878+
sessionData: Record<string, AttrList> | null;
1879+
// (undocumented)
1880+
sessionKeys: LevelKey[] | null;
1881+
// (undocumented)
18721882
stats: LoaderStats;
18731883
// (undocumented)
18741884
subtitleTracks: MediaPlaylist[];
@@ -2283,20 +2293,20 @@ export interface UserdataSample {
22832293

22842294
// Warnings were encountered during analysis:
22852295
//
2286-
// src/config.ts:79:3 - (ae-forgotten-export) The symbol "MediaKeySessionContext" needs to be exported by the entry point hls.d.ts
2287-
// src/config.ts:94:3 - (ae-forgotten-export) The symbol "DRMSystemsConfiguration" needs to be exported by the entry point hls.d.ts
2288-
// src/config.ts:197:3 - (ae-forgotten-export) The symbol "ILogger" needs to be exported by the entry point hls.d.ts
2289-
// src/config.ts:207:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts
2290-
// src/config.ts:208:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts
2291-
// src/config.ts:210:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts
2292-
// src/config.ts:211:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts
2293-
// src/config.ts:212:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts
2294-
// src/config.ts:214:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts
2295-
// src/config.ts:217:3 - (ae-forgotten-export) The symbol "CMCDController" needs to be exported by the entry point hls.d.ts
2296-
// src/config.ts:219:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts
2297-
// src/config.ts:220:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts
2298-
// src/config.ts:221:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts
2299-
// src/config.ts:222:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts
2296+
// src/config.ts:81:3 - (ae-forgotten-export) The symbol "MediaKeySessionContext" needs to be exported by the entry point hls.d.ts
2297+
// src/config.ts:96:3 - (ae-forgotten-export) The symbol "DRMSystemsConfiguration" needs to be exported by the entry point hls.d.ts
2298+
// src/config.ts:199:3 - (ae-forgotten-export) The symbol "ILogger" needs to be exported by the entry point hls.d.ts
2299+
// src/config.ts:209:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts
2300+
// src/config.ts:210:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts
2301+
// src/config.ts:212:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts
2302+
// src/config.ts:213:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts
2303+
// src/config.ts:214:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts
2304+
// src/config.ts:216:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts
2305+
// src/config.ts:219:3 - (ae-forgotten-export) The symbol "CMCDController" needs to be exported by the entry point hls.d.ts
2306+
// src/config.ts:221:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts
2307+
// src/config.ts:222:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts
2308+
// src/config.ts:223:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts
2309+
// src/config.ts:224:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts
23002310

23012311
// (No @packageDocumentation comment for this package)
23022312

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export type CMCDControllerConfig = {
6060
export type DRMSystemOptions = {
6161
audioRobustness?: string;
6262
videoRobustness?: string;
63+
audioEncryptionScheme?: string | null;
64+
videoEncryptionScheme?: string | null;
6365
persistentState?: MediaKeysRequirement;
6466
distinctiveIdentifier?: MediaKeysRequirement;
6567
sessionTypes?: string[];

src/controller/eme-controller.ts

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
MediaAttachedData,
3030
KeyLoadedData,
3131
ErrorData,
32+
ManifestLoadedData,
3233
} from '../types/events';
3334
import type { EMEControllerConfig } from '../config';
3435
import type { Fragment } from '../loader/fragment';
@@ -105,11 +106,13 @@ class EMEController implements ComponentAPI {
105106
private registerListeners() {
106107
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
107108
this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
109+
this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
108110
}
109111

110112
private unregisterListeners() {
111113
this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
112114
this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
115+
this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
113116
}
114117

115118
private getLicenseServerUrl(keySystem: KeySystems): string | never {
@@ -384,27 +387,31 @@ class EMEController implements ComponentAPI {
384387
frag.level
385388
}) key formats ${keyFormats.join(', ')}`
386389
);
387-
this.keyFormatPromise = new Promise((resolve, reject) => {
388-
const keySystemsToAttempt = keyFormats
389-
.map(keySystemFormatToKeySystemDomain)
390-
.filter((value) => !!value) as any as KeySystems[];
391-
return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(
392-
({ keySystem }) => {
393-
const keySystemFormat = keySystemToKeySystemFormat(keySystem);
394-
if (keySystemFormat) {
395-
resolve(keySystemFormat);
396-
} else {
397-
reject(
398-
new Error(`Unable to find format for key-system "${keySystem}"`)
399-
);
400-
}
401-
}
402-
);
403-
});
390+
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
404391
}
405392
return this.keyFormatPromise;
406393
}
407394

395+
private getKeyFormatPromise(keyFormats: string[]): Promise<KeySystemFormats> {
396+
return new Promise((resolve, reject) => {
397+
const keySystemsToAttempt = keyFormats
398+
.map(keySystemFormatToKeySystemDomain)
399+
.filter((value) => !!value) as any as KeySystems[];
400+
return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(
401+
({ keySystem }) => {
402+
const keySystemFormat = keySystemToKeySystemFormat(keySystem);
403+
if (keySystemFormat) {
404+
resolve(keySystemFormat);
405+
} else {
406+
reject(
407+
new Error(`Unable to find format for key-system "${keySystem}"`)
408+
);
409+
}
410+
}
411+
);
412+
});
413+
}
414+
408415
public loadKey(data: KeyLoadedData): Promise<MediaKeySessionContext> {
409416
const decryptdata = data.keyInfo.decryptdata;
410417

@@ -890,7 +897,11 @@ class EMEController implements ComponentAPI {
890897
licenseChallenge
891898
);
892899
})
893-
.catch(() => {
900+
.catch((error: Error) => {
901+
if (!keysListItem.decryptdata) {
902+
// Key session removed. Cancel license request.
903+
throw error;
904+
}
894905
// let's try to open before running setup
895906
xhr.open('POST', url, true);
896907

@@ -1102,7 +1113,31 @@ class EMEController implements ComponentAPI {
11021113
});
11031114
}
11041115

1105-
removeSession(
1116+
private onManifestLoaded(
1117+
event: Events.MANIFEST_LOADED,
1118+
{ sessionKeys }: ManifestLoadedData
1119+
) {
1120+
if (!sessionKeys || !this.config.emeEnabled) {
1121+
return;
1122+
}
1123+
if (!this.keyFormatPromise) {
1124+
const keyFormats = sessionKeys.reduce(
1125+
(formats: string[], sessionKey: LevelKey) => {
1126+
if (formats.indexOf(sessionKey.keyFormat) === -1) {
1127+
formats.push(sessionKey.keyFormat);
1128+
}
1129+
return formats;
1130+
},
1131+
[]
1132+
);
1133+
this.log(
1134+
`Selecting key-system from session-keys ${keyFormats.join(', ')}`
1135+
);
1136+
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
1137+
}
1138+
}
1139+
1140+
private removeSession(
11061141
mediaKeySessionContext: MediaKeySessionContext
11071142
): Promise<void> | void {
11081143
const { mediaKeysSession, licenseXhr } = mediaKeySessionContext;

src/controller/level-controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ export default class LevelController extends BasePlaylistController {
183183
levels,
184184
audioTracks,
185185
subtitleTracks,
186+
sessionData: data.sessionData,
187+
sessionKeys: data.sessionKeys,
186188
firstLevel: this._firstLevel,
187189
stats: data.stats,
188190
audio: audioCodecFound,

src/loader/level-key.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,33 @@ export class LevelKey implements DecryptData {
5555
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
5656
}
5757

58+
public isSupported(): boolean {
59+
// If it's Segment encryption or No encryption, just select that key system
60+
if (this.method) {
61+
if (this.method === 'AES-128' || this.method === 'NONE') {
62+
return true;
63+
}
64+
switch (this.keyFormat) {
65+
case 'identity':
66+
// Maintain support for clear SAMPLE-AES with MPEG-3 TS
67+
return this.method === 'SAMPLE-AES';
68+
case KeySystemFormats.FAIRPLAY:
69+
case KeySystemFormats.WIDEVINE:
70+
case KeySystemFormats.PLAYREADY:
71+
case KeySystemFormats.CLEARKEY:
72+
return (
73+
[
74+
'ISO-23001-7',
75+
'SAMPLE-AES',
76+
'SAMPLE-AES-CENC',
77+
'SAMPLE-AES-CTR',
78+
].indexOf(this.method) !== -1
79+
);
80+
}
81+
}
82+
return false;
83+
}
84+
5885
public getDecryptData(sn: number | 'initSegment'): LevelKey | null {
5986
if (!this.encrypted || !this.uri) {
6087
return null;

0 commit comments

Comments
 (0)