Skip to content

Commit

Permalink
Improve support for DRM key-systems and key handling
Browse files Browse the repository at this point in the history
Resolves #2833 #2737 #4538
  • Loading branch information
robwalch committed Sep 24, 2022
1 parent f6a5da8 commit 97c95e6
Show file tree
Hide file tree
Showing 36 changed files with 2,983 additions and 1,589 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ HLS.js is written in [ECMAScript6] (`*.js`) and [TypeScript] (`*.ts`) (strongly
- AES-128 decryption
- SAMPLE-AES decryption (only supported if using MPEG-2 TS container)
- Encrypted media extensions (EME) support for DRM (digital rights management)
- 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==))
- FairPlay, PlayReady, Widevine CDMs with fmp4 segments
- CEA-608/708 captions
- WebVTT subtitles
- Alternate Audio Track Rendition (Master Playlist with Alternative Audio) for VoD and Live playlists
Expand Down Expand Up @@ -120,8 +120,7 @@ For a complete list of issues, see ["Top priorities" in the Release Planning and
- `#EXT-X-GAP` filling [#2940](https://github.com/video-dev/hls.js/issues/2940)
- `#EXT-X-I-FRAME-STREAM-INF` I-frame Media Playlist files
- `SAMPLE-AES` with fmp4, aac, mp3, vtt... segments (MPEG-2 TS only)
- FairPlay DRM with MPEG-2 TS content
- 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))
- FairPlay, PlayReady, Widevine DRM with MPEG-2 TS segments
- Advanced variant selection based on runtime media capabilities (See issues labeled [`media-capabilities`](https://github.com/video-dev/hls.js/labels/media-capabilities))
- 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))

Expand Down
102 changes: 69 additions & 33 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ export class DateRange {
export type DRMSystemOptions = {
audioRobustness?: string;
videoRobustness?: string;
persistentState?: MediaKeysRequirement;
distinctiveIdentifier?: MediaKeysRequirement;
sessionTypes?: string[];
sessionType?: string;
};

// Warning: (ae-missing-release-tag) "ElementaryStreamInfo" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down Expand Up @@ -352,9 +356,10 @@ export enum ElementaryStreamTypes {
//
// @public (undocumented)
export type EMEControllerConfig = {
licenseXhrSetup?: (xhr: XMLHttpRequest, url: string, keySystem: KeySystems) => void | Promise<void>;
licenseResponseCallback?: (xhr: XMLHttpRequest, url: string, keySystem: KeySystems) => ArrayBuffer;
licenseXhrSetup?: (this: Hls, xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext, licenseChallenge: Uint8Array) => void | Promise<Uint8Array | void>;
licenseResponseCallback?: (this: Hls, xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext) => ArrayBuffer;
emeEnabled: boolean;
useEmeEncryptedEvent: boolean;
widevineLicenseUrl?: string;
drmSystems: DRMSystemsConfiguration;
drmSystemOptions: DRMSystemOptions;
Expand Down Expand Up @@ -464,6 +469,10 @@ export enum ErrorDetails {
// (undocumented)
KEY_SYSTEM_SESSION_UPDATE_FAILED = "keySystemSessionUpdateFailed",
// (undocumented)
KEY_SYSTEM_STATUS_INTERNAL_ERROR = "keySystemStatusInternalError",
// (undocumented)
KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED = "keySystemStatusOutputRestricted",
// (undocumented)
LEVEL_EMPTY_ERROR = "levelEmptyError",
// (undocumented)
LEVEL_LOAD_ERROR = "levelLoadError",
Expand Down Expand Up @@ -740,7 +749,6 @@ export class Fragment extends BaseSegment {
cc: number;
// (undocumented)
clearElementaryStreamInfo(): void;
createInitializationVector(segmentNumber: number): Uint8Array;
// (undocumented)
data?: Uint8Array;
// (undocumented)
Expand All @@ -761,12 +769,16 @@ export class Fragment extends BaseSegment {
endPTS?: number;
// (undocumented)
initSegment: Fragment | null;
// Warning: (ae-forgotten-export) The symbol "KeyLoaderContext" needs to be exported by the entry point hls.d.ts
//
// (undocumented)
keyLoader: Loader<KeyLoaderContext> | null;
// (undocumented)
level: number;
// (undocumented)
levelkey?: LevelKey;
levelkeys?: {
[key: string]: LevelKey;
};
// (undocumented)
loader: Loader<FragmentLoaderContext> | null;
// (undocumented)
Expand All @@ -777,10 +789,11 @@ export class Fragment extends BaseSegment {
programDateTime: number | null;
// (undocumented)
rawProgramDateTime: string | null;
setDecryptDataFromLevelKey(levelkey: LevelKey, segmentNumber: number): LevelKey;
// (undocumented)
setElementaryStreamInfo(type: ElementaryStreamTypes, startPTS: number, endPTS: number, startDTS: number, endDTS: number, partial?: boolean): void;
// (undocumented)
setKeyFormat(keyFormat: KeySystemFormats): void;
// (undocumented)
sn: number | 'initSegment';
// (undocumented)
start: number;
Expand Down Expand Up @@ -894,7 +907,7 @@ class Hls implements HlsEventEmitter {
// (undocumented)
readonly config: HlsConfig;
// (undocumented)
createController(ControllerClass: any, fragmentTracker: any, components: any): any;
createController(ControllerClass: any, components: any): any;
get currentLevel(): number;
// Warning: (ae-setter-with-docs) The doc comment for the property "currentLevel" must appear on the getter, not the setter.
set currentLevel(newLevel: number);
Expand Down Expand Up @@ -1222,26 +1235,40 @@ export interface InitPTSFoundData {
export interface KeyLoadedData {
// (undocumented)
frag: Fragment;
// Warning: (ae-forgotten-export) The symbol "KeyLoaderInfo" needs to be exported by the entry point hls.d.ts
//
// (undocumented)
keyInfo: KeyLoaderInfo;
}

// Warning: (ae-missing-release-tag) "KeyLoaderContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
// Warning: (ae-missing-release-tag) "KeyLoadingData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface KeyLoaderContext extends FragmentLoaderContext {
export interface KeyLoadingData {
// (undocumented)
frag: Fragment;
}

// Warning: (ae-missing-release-tag) "KeyLoadingData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
// Warning: (ae-missing-release-tag) "KeySystemFormats" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface KeyLoadingData {
export enum KeySystemFormats {
// (undocumented)
frag: Fragment;
CLEARKEY = "org.w3.clearkey",
// (undocumented)
FAIRPLAY = "com.apple.streamingkeydelivery",
// (undocumented)
PLAYREADY = "com.microsoft.playready",
// (undocumented)
WIDEVINE = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
}

// Warning: (ae-missing-release-tag) "KeySystems" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export enum KeySystems {
// (undocumented)
CLEARKEY = "org.w3.clearkey",
// (undocumented)
FAIRPLAY = "com.apple.fps",
// (undocumented)
Expand Down Expand Up @@ -1474,28 +1501,36 @@ export class LevelDetails {
version: number | null;
}

// Warning: (ae-forgotten-export) The symbol "DecryptData" needs to be exported by the entry point hls.d.ts
// Warning: (ae-missing-release-tag) "LevelKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export class LevelKey {
export class LevelKey implements DecryptData {
constructor(method: string, uri: string, format: string, formatversions?: number[], iv?: Uint8Array | null);
// (undocumented)
static clearKeyUriToKeyIdMap(): void;
// (undocumented)
static fromURI(uri: string): LevelKey;
readonly encrypted: boolean;
// (undocumented)
static fromURL(baseUrl: string, relativeUrl: string): LevelKey;
getDecryptData(sn: number | 'initSegment'): LevelKey | null;
// (undocumented)
iv: Uint8Array | null;
readonly isCommonEncryption: boolean;
// (undocumented)
readonly iv: Uint8Array | null;
// (undocumented)
key: Uint8Array | null;
// (undocumented)
keyFormat: string | null;
readonly keyFormat: string;
// (undocumented)
readonly keyFormatVersions: number[];
// (undocumented)
keyFormatVersions: string | null;
keyId: Uint8Array | null;
// (undocumented)
keyID: string | null;
readonly method: string;
// (undocumented)
method: string | null;
pssh: Uint8Array | null;
// (undocumented)
get uri(): string | null;
readonly uri: string;
}

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

// Warnings were encountered during analysis:
//
// src/config.ts:84:3 - (ae-forgotten-export) The symbol "DRMSystemsConfiguration" needs to be exported by the entry point hls.d.ts
// src/config.ts:187:3 - (ae-forgotten-export) The symbol "ILogger" needs to be exported by the entry point hls.d.ts
// src/config.ts:197:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts
// src/config.ts:198:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts
// src/config.ts:200:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts
// src/config.ts:201:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts
// src/config.ts:202:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts
// src/config.ts:204:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts
// src/config.ts:207:3 - (ae-forgotten-export) The symbol "CMCDController" needs to be exported by the entry point hls.d.ts
// src/config.ts:209:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts
// src/config.ts:210:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts
// src/config.ts:211:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts
// src/config.ts:212:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts
// src/config.ts:79:3 - (ae-forgotten-export) The symbol "MediaKeySessionContext" needs to be exported by the entry point hls.d.ts
// src/config.ts:95:3 - (ae-forgotten-export) The symbol "DRMSystemsConfiguration" needs to be exported by the entry point hls.d.ts
// src/config.ts:198:3 - (ae-forgotten-export) The symbol "ILogger" needs to be exported by the entry point hls.d.ts
// src/config.ts:208:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts
// src/config.ts:209:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts
// src/config.ts:211:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts
// src/config.ts:212:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts
// src/config.ts:213:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts
// src/config.ts:215:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts
// src/config.ts:218:3 - (ae-forgotten-export) The symbol "CMCDController" needs to be exported by the entry point hls.d.ts
// src/config.ts:220:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts
// src/config.ts:221:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts
// src/config.ts:222:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts
// src/config.ts:223:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
84 changes: 70 additions & 14 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
- [`abrMaxWithRealBitrate`](#abrmaxwithrealbitrate)
- [`minAutoBitrate`](#minautobitrate)
- [`emeEnabled`](#emeEnabled)
- [`useEmeEncryptedEvent`](#useEmeEncryptedEvent)
- [`widevineLicenseUrl`](#widevineLicenseUrl)
- [`licenseXhrSetup`](#licenseXhrSetup)
- [`licenseResponseCallback`](#licenseResponseCallback)
Expand Down Expand Up @@ -399,6 +400,7 @@ var config = {
maxLoadingDelay: 4,
minAutoBitrate: 0,
emeEnabled: false,
useEmeEncryptedEvent: false,
widevineLicenseUrl: undefined,
licenseXhrSetup: undefined,
drmSystems: {},
Expand Down Expand Up @@ -1192,6 +1194,12 @@ Useful when browser or tab of the browser is not in the focus and bandwidth drop

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

### `useEmeEncryptedEvent`

(default: `false`)

Set to `true` to use media "encrypted" event initData and ignore manifest DRM keys.

### `widevineLicenseUrl`

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

### `licenseXhrSetup`

(default: `undefined`, type `(xhr: XMLHttpRequest, url: string) => void`)
(default: `undefined`, type `(xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext, licenseChallenge: Uint8Array) => void`)

A pre-processor function for modifying the `XMLHttpRequest` and request url (using `xhr.open`) prior to sending the license request.
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.

```js
var config = {
licenseXhrSetup: function (xhr, url) {
xhr.withCredentials = true; // do send cookies
if (!xhr.readyState) {
// Call open to change the method (default is POST) or modify the url
xhr.open('GET', url, true);
// Append headers after opening
licenseXhrSetup: function (xhr, url, keyContext, licenseChallenge) {
let payload = licenseChallenge;

// Send cookies with request
xhr.withCredentials = true;

// Call open to change the method (default is POST), modify the url, or set request headers
xhr.open('POST', url, true);

// call xhr.setRequestHeader after xhr.open otherwise licenseXhrSetup will throw and be called a second time after HLS.js call xhr.open
if (keyContext.keySystem === 'com.apple.fps') {
xhr.setRequestHeader('Content-Type', 'application/json');
payload = JSON.stringify({
keyData: base64Encode(keyContext.decryptdata?.keyId),
licenseChallenge: base64Encode(licenseChallenge),
});
} else {
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
}

// Return the desired payload or a Promise<Uint8Array|void>
// return Promise.resolve(payload);
return payload;
},
};
```
### `licenseResponseCallback`
(default: `undefined`, type `(xhr: XMLHttpRequest, url: string) => data: ArrayBuffer`)
(default: `undefined`, type `(xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext) => data: ArrayBuffer`)
A post-processor function for modifying the license response before passing it to the key-session (`MediaKeySession.update`).
```js
var config = {
licenseResponseCallback: function (xhr, url, keyContext) {
const keySystem = keyContext.keySystem;
const response = xhr.response;
if (keyContext.keySystem === 'com.apple.fps') {
try {
const responseObject = JSON.parse(
new TextDecoder().decode(response).trim();
);
const keyResponse = responseObject['fairplay-streaming-response']['streaming-keys'][0];
return base64Decode(keyResponse.ckc);
} catch (error) {
console.error(error);
}
}
return response;
}
```
### `drmSystems`
(default: `{}`)
Set `licenseUrl` and `serverCertificateUrl` for a given keySystem to your own DRM provider. `serverCertificateUrl` is not mandatory. Ex:
Set `licenseUrl` and `serverCertificateUrl` for a given key-system to your own DRM provider. `serverCertificateUrl` is not mandatory. Ex:
```js
{
drmSystems: {
'com.widevine.alpha': {
licenseUrl: 'https://proxy.uat.widevine.com/proxy',
serverCertificateUrl: 'https://storage.googleapis.com/wvmedia/cert/cert_license_widevine_com_uat.bin'
licenseUrl: 'https://your-widevine-license-server/path',
serverCertificateUrl: 'https://optional-server-certificate/path/cert.bin'
}
}
```
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).
### `drmSystemOptions`
(default: `{}`)
Expand All @@ -1258,7 +1303,18 @@ With the default argument, `''` will be specified for each option (_i.e. no spec
(default: A function that returns the result of `window.navigator.requestMediaKeySystemAccess.bind(window.navigator)` or `null`)
Allows for the customization of `window.navigator.requestMediaKeySystemAccess`.
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:
```js
var hls new Hls({
requestMediaKeySystemAccessFunc: (keySystem, supportedConfigurations) => {
if (keySystem === 'com.microsoft.playready') {
keySystem = 'com.microsoft.playready.recommendation';
}
return navigator.requestMediaKeySystemAccess(keySystem, supportedConfigurations);
}
});
```
### `cmcd`
Expand Down
Loading

0 comments on commit 97c95e6

Please sign in to comment.