Skip to content

Commit 025ef18

Browse files
committed
Improve support for DRM key-systems and key handling
Resolves #2833 #2737 #4318 #4538
1 parent f6a5da8 commit 025ef18

36 files changed

+2983
-1589
lines changed

README.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ HLS.js is written in [ECMAScript6] (`*.js`) and [TypeScript] (`*.ts`) (strongly
4545
- AES-128 decryption
4646
- SAMPLE-AES decryption (only supported if using MPEG-2 TS container)
4747
- Encrypted media extensions (EME) support for DRM (digital rights management)
48-
- Widevine CDM (only tested with [shaka-packager](https://github.com/google/shaka-packager) test-stream on [the demo page](https://hls-js.netlify.app/demo/?src=https%3A%2F%2Fstorage.googleapis.com%2Fshaka-demo-assets%2Fangel-one-widevine-hls%2Fhls.m3u8&demoConfig=eyJlbmFibGVTdHJlYW1pbmciOnRydWUsImF1dG9SZWNvdmVyRXJyb3IiOnRydWUsInN0b3BPblN0YWxsIjpmYWxzZSwiZHVtcGZNUDQiOmZhbHNlLCJsZXZlbENhcHBpbmciOi0xLCJsaW1pdE1ldHJpY3MiOi0xfQ==))
48+
- FairPlay, PlayReady, Widevine CDMs with fmp4 segments
4949
- CEA-608/708 captions
5050
- WebVTT subtitles
5151
- Alternate Audio Track Rendition (Master Playlist with Alternative Audio) for VoD and Live playlists
@@ -120,8 +120,7 @@ For a complete list of issues, see ["Top priorities" in the Release Planning and
120120
- `#EXT-X-GAP` filling [#2940](https://github.com/video-dev/hls.js/issues/2940)
121121
- `#EXT-X-I-FRAME-STREAM-INF` I-frame Media Playlist files
122122
- `SAMPLE-AES` with fmp4, aac, mp3, vtt... segments (MPEG-2 TS only)
123-
- FairPlay DRM with MPEG-2 TS content
124-
- PlayReady (See [#3779](https://github.com/video-dev/hls.js/issues/3779) and [issues labeled DRM](https://github.com/video-dev/hls.js/issues?q=is%3Aissue+is%3Aopen+label%3ADRM))
123+
- FairPlay, PlayReady, Widevine DRM with MPEG-2 TS segments
125124
- Advanced variant selection based on runtime media capabilities (See issues labeled [`media-capabilities`](https://github.com/video-dev/hls.js/labels/media-capabilities))
126125
- MP3 elementary stream audio in IE and Edge (<=18) on Windows 10 (See [#1641](https://github.com/video-dev/hls.js/issues/1641) and [Microsoft answers forum](https://answers.microsoft.com/en-us/ie/forum/all/ie11-on-windows-10-cannot-play-hls-with-mp3/2da994b5-8dec-4ae9-9201-7d138ede49d9))
127126

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

+69-33
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ export class DateRange {
313313
export type DRMSystemOptions = {
314314
audioRobustness?: string;
315315
videoRobustness?: string;
316+
persistentState?: MediaKeysRequirement;
317+
distinctiveIdentifier?: MediaKeysRequirement;
318+
sessionTypes?: string[];
319+
sessionType?: string;
316320
};
317321

318322
// Warning: (ae-missing-release-tag) "ElementaryStreamInfo" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -352,9 +356,10 @@ export enum ElementaryStreamTypes {
352356
//
353357
// @public (undocumented)
354358
export type EMEControllerConfig = {
355-
licenseXhrSetup?: (xhr: XMLHttpRequest, url: string, keySystem: KeySystems) => void | Promise<void>;
356-
licenseResponseCallback?: (xhr: XMLHttpRequest, url: string, keySystem: KeySystems) => ArrayBuffer;
359+
licenseXhrSetup?: (this: Hls, xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext, licenseChallenge: Uint8Array) => void | Promise<Uint8Array | void>;
360+
licenseResponseCallback?: (this: Hls, xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext) => ArrayBuffer;
357361
emeEnabled: boolean;
362+
useEmeEncryptedEvent: boolean;
358363
widevineLicenseUrl?: string;
359364
drmSystems: DRMSystemsConfiguration;
360365
drmSystemOptions: DRMSystemOptions;
@@ -464,6 +469,10 @@ export enum ErrorDetails {
464469
// (undocumented)
465470
KEY_SYSTEM_SESSION_UPDATE_FAILED = "keySystemSessionUpdateFailed",
466471
// (undocumented)
472+
KEY_SYSTEM_STATUS_INTERNAL_ERROR = "keySystemStatusInternalError",
473+
// (undocumented)
474+
KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED = "keySystemStatusOutputRestricted",
475+
// (undocumented)
467476
LEVEL_EMPTY_ERROR = "levelEmptyError",
468477
// (undocumented)
469478
LEVEL_LOAD_ERROR = "levelLoadError",
@@ -740,7 +749,6 @@ export class Fragment extends BaseSegment {
740749
cc: number;
741750
// (undocumented)
742751
clearElementaryStreamInfo(): void;
743-
createInitializationVector(segmentNumber: number): Uint8Array;
744752
// (undocumented)
745753
data?: Uint8Array;
746754
// (undocumented)
@@ -761,12 +769,16 @@ export class Fragment extends BaseSegment {
761769
endPTS?: number;
762770
// (undocumented)
763771
initSegment: Fragment | null;
772+
// Warning: (ae-forgotten-export) The symbol "KeyLoaderContext" needs to be exported by the entry point hls.d.ts
773+
//
764774
// (undocumented)
765775
keyLoader: Loader<KeyLoaderContext> | null;
766776
// (undocumented)
767777
level: number;
768778
// (undocumented)
769-
levelkey?: LevelKey;
779+
levelkeys?: {
780+
[key: string]: LevelKey;
781+
};
770782
// (undocumented)
771783
loader: Loader<FragmentLoaderContext> | null;
772784
// (undocumented)
@@ -777,10 +789,11 @@ export class Fragment extends BaseSegment {
777789
programDateTime: number | null;
778790
// (undocumented)
779791
rawProgramDateTime: string | null;
780-
setDecryptDataFromLevelKey(levelkey: LevelKey, segmentNumber: number): LevelKey;
781792
// (undocumented)
782793
setElementaryStreamInfo(type: ElementaryStreamTypes, startPTS: number, endPTS: number, startDTS: number, endDTS: number, partial?: boolean): void;
783794
// (undocumented)
795+
setKeyFormat(keyFormat: KeySystemFormats): void;
796+
// (undocumented)
784797
sn: number | 'initSegment';
785798
// (undocumented)
786799
start: number;
@@ -894,7 +907,7 @@ class Hls implements HlsEventEmitter {
894907
// (undocumented)
895908
readonly config: HlsConfig;
896909
// (undocumented)
897-
createController(ControllerClass: any, fragmentTracker: any, components: any): any;
910+
createController(ControllerClass: any, components: any): any;
898911
get currentLevel(): number;
899912
// Warning: (ae-setter-with-docs) The doc comment for the property "currentLevel" must appear on the getter, not the setter.
900913
set currentLevel(newLevel: number);
@@ -1222,26 +1235,40 @@ export interface InitPTSFoundData {
12221235
export interface KeyLoadedData {
12231236
// (undocumented)
12241237
frag: Fragment;
1238+
// Warning: (ae-forgotten-export) The symbol "KeyLoaderInfo" needs to be exported by the entry point hls.d.ts
1239+
//
1240+
// (undocumented)
1241+
keyInfo: KeyLoaderInfo;
12251242
}
12261243

1227-
// Warning: (ae-missing-release-tag) "KeyLoaderContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
1244+
// Warning: (ae-missing-release-tag) "KeyLoadingData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
12281245
//
12291246
// @public (undocumented)
1230-
export interface KeyLoaderContext extends FragmentLoaderContext {
1247+
export interface KeyLoadingData {
1248+
// (undocumented)
1249+
frag: Fragment;
12311250
}
12321251

1233-
// Warning: (ae-missing-release-tag) "KeyLoadingData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
1252+
// Warning: (ae-missing-release-tag) "KeySystemFormats" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
12341253
//
12351254
// @public (undocumented)
1236-
export interface KeyLoadingData {
1255+
export enum KeySystemFormats {
12371256
// (undocumented)
1238-
frag: Fragment;
1257+
CLEARKEY = "org.w3.clearkey",
1258+
// (undocumented)
1259+
FAIRPLAY = "com.apple.streamingkeydelivery",
1260+
// (undocumented)
1261+
PLAYREADY = "com.microsoft.playready",
1262+
// (undocumented)
1263+
WIDEVINE = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
12391264
}
12401265

12411266
// Warning: (ae-missing-release-tag) "KeySystems" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
12421267
//
12431268
// @public (undocumented)
12441269
export enum KeySystems {
1270+
// (undocumented)
1271+
CLEARKEY = "org.w3.clearkey",
12451272
// (undocumented)
12461273
FAIRPLAY = "com.apple.fps",
12471274
// (undocumented)
@@ -1474,28 +1501,36 @@ export class LevelDetails {
14741501
version: number | null;
14751502
}
14761503

1504+
// Warning: (ae-forgotten-export) The symbol "DecryptData" needs to be exported by the entry point hls.d.ts
14771505
// Warning: (ae-missing-release-tag) "LevelKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
14781506
//
14791507
// @public (undocumented)
1480-
export class LevelKey {
1508+
export class LevelKey implements DecryptData {
1509+
constructor(method: string, uri: string, format: string, formatversions?: number[], iv?: Uint8Array | null);
1510+
// (undocumented)
1511+
static clearKeyUriToKeyIdMap(): void;
14811512
// (undocumented)
1482-
static fromURI(uri: string): LevelKey;
1513+
readonly encrypted: boolean;
14831514
// (undocumented)
1484-
static fromURL(baseUrl: string, relativeUrl: string): LevelKey;
1515+
getDecryptData(sn: number | 'initSegment'): LevelKey | null;
14851516
// (undocumented)
1486-
iv: Uint8Array | null;
1517+
readonly isCommonEncryption: boolean;
1518+
// (undocumented)
1519+
readonly iv: Uint8Array | null;
14871520
// (undocumented)
14881521
key: Uint8Array | null;
14891522
// (undocumented)
1490-
keyFormat: string | null;
1523+
readonly keyFormat: string;
1524+
// (undocumented)
1525+
readonly keyFormatVersions: number[];
14911526
// (undocumented)
1492-
keyFormatVersions: string | null;
1527+
keyId: Uint8Array | null;
14931528
// (undocumented)
1494-
keyID: string | null;
1529+
readonly method: string;
14951530
// (undocumented)
1496-
method: string | null;
1531+
pssh: Uint8Array | null;
14971532
// (undocumented)
1498-
get uri(): string | null;
1533+
readonly uri: string;
14991534
}
15001535

15011536
// Warning: (ae-missing-release-tag) "LevelLoadedData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -2237,19 +2272,20 @@ export interface UserdataSample {
22372272

22382273
// Warnings were encountered during analysis:
22392274
//
2240-
// src/config.ts:84:3 - (ae-forgotten-export) The symbol "DRMSystemsConfiguration" needs to be exported by the entry point hls.d.ts
2241-
// src/config.ts:187:3 - (ae-forgotten-export) The symbol "ILogger" needs to be exported by the entry point hls.d.ts
2242-
// src/config.ts:197:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts
2243-
// src/config.ts:198:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts
2244-
// src/config.ts:200:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts
2245-
// src/config.ts:201:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts
2246-
// src/config.ts:202:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts
2247-
// src/config.ts:204:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts
2248-
// src/config.ts:207:3 - (ae-forgotten-export) The symbol "CMCDController" needs to be exported by the entry point hls.d.ts
2249-
// src/config.ts:209:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts
2250-
// src/config.ts:210:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts
2251-
// src/config.ts:211:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts
2252-
// src/config.ts:212:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts
2275+
// src/config.ts:79:3 - (ae-forgotten-export) The symbol "MediaKeySessionContext" needs to be exported by the entry point hls.d.ts
2276+
// src/config.ts:95:3 - (ae-forgotten-export) The symbol "DRMSystemsConfiguration" needs to be exported by the entry point hls.d.ts
2277+
// src/config.ts:198:3 - (ae-forgotten-export) The symbol "ILogger" needs to be exported by the entry point hls.d.ts
2278+
// src/config.ts:208:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts
2279+
// src/config.ts:209:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts
2280+
// src/config.ts:211:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts
2281+
// src/config.ts:212:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts
2282+
// src/config.ts:213:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts
2283+
// src/config.ts:215:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts
2284+
// src/config.ts:218:3 - (ae-forgotten-export) The symbol "CMCDController" needs to be exported by the entry point hls.d.ts
2285+
// src/config.ts:220:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts
2286+
// src/config.ts:221:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts
2287+
// src/config.ts:222:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts
2288+
// src/config.ts:223:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts
22532289

22542290
// (No @packageDocumentation comment for this package)
22552291

docs/API.md

+70-14
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
- [`abrMaxWithRealBitrate`](#abrmaxwithrealbitrate)
9595
- [`minAutoBitrate`](#minautobitrate)
9696
- [`emeEnabled`](#emeEnabled)
97+
- [`useEmeEncryptedEvent`](#useEmeEncryptedEvent)
9798
- [`widevineLicenseUrl`](#widevineLicenseUrl)
9899
- [`licenseXhrSetup`](#licenseXhrSetup)
99100
- [`licenseResponseCallback`](#licenseResponseCallback)
@@ -399,6 +400,7 @@ var config = {
399400
maxLoadingDelay: 4,
400401
minAutoBitrate: 0,
401402
emeEnabled: false,
403+
useEmeEncryptedEvent: false,
402404
widevineLicenseUrl: undefined,
403405
licenseXhrSetup: undefined,
404406
drmSystems: {},
@@ -1192,6 +1194,12 @@ Useful when browser or tab of the browser is not in the focus and bandwidth drop
11921194

11931195
Set to `true` to enable DRM key system access and license retrieval.
11941196

1197+
### `useEmeEncryptedEvent`
1198+
1199+
(default: `false`)
1200+
1201+
Set to `true` to use media "encrypted" event initData and ignore manifest DRM keys.
1202+
11951203
### `widevineLicenseUrl`
11961204

11971205
(default: `undefined`)
@@ -1200,45 +1208,82 @@ The Widevine license server URL.
12001208

12011209
### `licenseXhrSetup`
12021210

1203-
(default: `undefined`, type `(xhr: XMLHttpRequest, url: string) => void`)
1211+
(default: `undefined`, type `(xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext, licenseChallenge: Uint8Array) => void`)
12041212

1205-
A pre-processor function for modifying the `XMLHttpRequest` and request url (using `xhr.open`) prior to sending the license request.
1213+
A pre-processor function for modifying license requests. The license request URL, request headers, and payload can all be modified prior to sending the license request, based on operating conditions, the current key-session, and key-system.
12061214

12071215
```js
12081216
var config = {
1209-
licenseXhrSetup: function (xhr, url) {
1210-
xhr.withCredentials = true; // do send cookies
1211-
if (!xhr.readyState) {
1212-
// Call open to change the method (default is POST) or modify the url
1213-
xhr.open('GET', url, true);
1214-
// Append headers after opening
1217+
licenseXhrSetup: function (xhr, url, keyContext, licenseChallenge) {
1218+
let payload = licenseChallenge;
1219+
1220+
// Send cookies with request
1221+
xhr.withCredentials = true;
1222+
1223+
// Call open to change the method (default is POST), modify the url, or set request headers
1224+
xhr.open('POST', url, true);
1225+
1226+
// call xhr.setRequestHeader after xhr.open otherwise licenseXhrSetup will throw and be called a second time after HLS.js call xhr.open
1227+
if (keyContext.keySystem === 'com.apple.fps') {
1228+
xhr.setRequestHeader('Content-Type', 'application/json');
1229+
payload = JSON.stringify({
1230+
keyData: base64Encode(keyContext.decryptdata?.keyId),
1231+
licenseChallenge: base64Encode(licenseChallenge),
1232+
});
1233+
} else {
12151234
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
12161235
}
1236+
1237+
// Return the desired payload or a Promise<Uint8Array|void>
1238+
// return Promise.resolve(payload);
1239+
return payload;
12171240
},
12181241
};
12191242
```
12201243
12211244
### `licenseResponseCallback`
12221245
1223-
(default: `undefined`, type `(xhr: XMLHttpRequest, url: string) => data: ArrayBuffer`)
1246+
(default: `undefined`, type `(xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext) => data: ArrayBuffer`)
12241247
12251248
A post-processor function for modifying the license response before passing it to the key-session (`MediaKeySession.update`).
12261249
1250+
```js
1251+
var config = {
1252+
licenseResponseCallback: function (xhr, url, keyContext) {
1253+
const keySystem = keyContext.keySystem;
1254+
const response = xhr.response;
1255+
if (keyContext.keySystem === 'com.apple.fps') {
1256+
try {
1257+
const responseObject = JSON.parse(
1258+
new TextDecoder().decode(response).trim();
1259+
);
1260+
const keyResponse = responseObject['fairplay-streaming-response']['streaming-keys'][0];
1261+
return base64Decode(keyResponse.ckc);
1262+
} catch (error) {
1263+
console.error(error);
1264+
}
1265+
}
1266+
return response;
1267+
}
1268+
```
1269+
12271270
### `drmSystems`
12281271
12291272
(default: `{}`)
12301273
1231-
Set `licenseUrl` and `serverCertificateUrl` for a given keySystem to your own DRM provider. `serverCertificateUrl` is not mandatory. Ex:
1274+
Set `licenseUrl` and `serverCertificateUrl` for a given key-system to your own DRM provider. `serverCertificateUrl` is not mandatory. Ex:
12321275
12331276
```js
1234-
{
1277+
drmSystems: {
12351278
'com.widevine.alpha': {
1236-
licenseUrl: 'https://proxy.uat.widevine.com/proxy',
1237-
serverCertificateUrl: 'https://storage.googleapis.com/wvmedia/cert/cert_license_widevine_com_uat.bin'
1279+
licenseUrl: 'https://your-widevine-license-server/path',
1280+
serverCertificateUrl: 'https://optional-server-certificate/path/cert.bin'
12381281
}
12391282
}
12401283
```
12411284
1285+
Supported key-systems include 'com.apple.fps', 'com.microsoft.playready', 'com.widevine.alpha', and 'org.w3.clearkey'. Mapping to other values in key-system access requests can be done by customizing [`requestMediaKeySystemAccessFunc`](#requestMediaKeySystemAccessFunc).
1286+
12421287
### `drmSystemOptions`
12431288
12441289
(default: `{}`)
@@ -1258,7 +1303,18 @@ With the default argument, `''` will be specified for each option (_i.e. no spec
12581303
12591304
(default: A function that returns the result of `window.navigator.requestMediaKeySystemAccess.bind(window.navigator)` or `null`)
12601305
1261-
Allows for the customization of `window.navigator.requestMediaKeySystemAccess`.
1306+
Allows for the customization of `window.navigator.requestMediaKeySystemAccess`. This can be used to map key-system access request to from a supported value to a custom one:
1307+
1308+
```js
1309+
var hls new Hls({
1310+
requestMediaKeySystemAccessFunc: (keySystem, supportedConfigurations) => {
1311+
if (keySystem === 'com.microsoft.playready') {
1312+
keySystem = 'com.microsoft.playready.recommendation';
1313+
}
1314+
return navigator.requestMediaKeySystemAccess(keySystem, supportedConfigurations);
1315+
}
1316+
});
1317+
```
12621318
12631319
### `cmcd`
12641320

0 commit comments

Comments
 (0)