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 16, 2022
1 parent 8c8d93b commit 3b551ea
Show file tree
Hide file tree
Showing 32 changed files with 2,705 additions and 1,491 deletions.
97 changes: 66 additions & 31 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 @@ -1478,24 +1505,31 @@ export class LevelDetails {
//
// @public (undocumented)
export class LevelKey {
constructor(method: string, uri: string, format: string, formatversions?: number[], iv?: Uint8Array | null);
// (undocumented)
static clearKeyUriToKeyIdMap(): void;
// (undocumented)
get encrypted(): boolean | "";
// (undocumented)
static fromURI(uri: string): LevelKey;
getDecryptData(sn: number | 'initSegment'): LevelKey | null;
// (undocumented)
static fromURL(baseUrl: string, relativeUrl: string): LevelKey;
get isCommonEncryption(): boolean | "";
// (undocumented)
iv: Uint8Array | null;
// (undocumented)
key: Uint8Array | null;
// (undocumented)
keyFormat: string | null;
keyFormat: string;
// (undocumented)
keyFormatVersions: string | null;
keyFormatVersions: number[];
// (undocumented)
keyID: string | null;
keyId: Uint8Array | null;
// (undocumented)
method: string | null;
method: string;
// (undocumented)
get uri(): string | null;
pssh: Uint8Array | null;
// (undocumented)
get 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 +2271,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
8 changes: 8 additions & 0 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 Down
20 changes: 16 additions & 4 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import BufferController from './controller/buffer-controller';
import { TimelineController } from './controller/timeline-controller';
import CapLevelController from './controller/cap-level-controller';
import FPSController from './controller/fps-controller';
import EMEController from './controller/eme-controller';
import EMEController, {
MediaKeySessionContext,
} from './controller/eme-controller';
import CMCDController from './controller/cmcd-controller';
import XhrLoader from './utils/xhr-loader';
import FetchLoader, { fetchSupported } from './utils/fetch-loader';
import Cues from './utils/cues';
import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
import { ILogger, logger } from './utils/logger';

import type Hls from './hls';
import type { CuesInterface } from './utils/cues';
import type { MediaKeyFunc, KeySystems } from './utils/mediakeys-helper';
import type {
Expand Down Expand Up @@ -57,6 +60,10 @@ export type CMCDControllerConfig = {
export type DRMSystemOptions = {
audioRobustness?: string;
videoRobustness?: string;
persistentState?: MediaKeysRequirement;
distinctiveIdentifier?: MediaKeysRequirement;
sessionTypes?: string[];
sessionType?: string;
};

export type DRMSystemConfiguration = {
Expand All @@ -70,16 +77,20 @@ export type DRMSystemsConfiguration = Partial<

export type EMEControllerConfig = {
licenseXhrSetup?: (
this: Hls,
xhr: XMLHttpRequest,
url: string,
keySystem: KeySystems
) => void | Promise<void>;
keyContext: MediaKeySessionContext,
licenseChallenge: Uint8Array
) => void | Promise<Uint8Array | void>;
licenseResponseCallback?: (
this: Hls,
xhr: XMLHttpRequest,
url: string,
keySystem: KeySystems
keyContext: MediaKeySessionContext
) => ArrayBuffer;
emeEnabled: boolean;
useEmeEncryptedEvent: boolean;
widevineLicenseUrl?: string;
drmSystems: DRMSystemsConfiguration;
drmSystemOptions: DRMSystemOptions;
Expand Down Expand Up @@ -300,6 +311,7 @@ export const hlsDefaultConfig: HlsConfig = {
maxLoadingDelay: 4, // used by abr-controller
minAutoBitrate: 0, // used by hls
emeEnabled: false, // used by eme-controller
useEmeEncryptedEvent: false, // used by eme-controller
widevineLicenseUrl: undefined, // used by eme-controller
drmSystems: {}, // used by eme-controller
drmSystemOptions: {}, // used by eme-controller
Expand Down
11 changes: 8 additions & 3 deletions src/controller/audio-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { fragmentWithinToleranceTest } from './fragment-finders';
import { alignMediaPlaylistByPDT } from '../utils/discontinuities';
import { ErrorDetails } from '../errors';
import type { NetworkComponentAPI } from '../types/component-api';
import type Hls from '../hls';
import type { FragmentTracker } from './fragment-tracker';
import type KeyLoader from '../loader/key-loader';
import type { TransmuxerResult } from '../types/transmuxer';
import type Hls from '../hls';
import type { LevelDetails } from '../loader/level-details';
import type { TrackSet } from '../types/track';
import type {
Expand Down Expand Up @@ -56,8 +57,12 @@ class AudioStreamController
private bufferFlushed: boolean = false;
private cachedTrackLoadedData: TrackLoadedData | null = null;

constructor(hls: Hls, fragmentTracker: FragmentTracker) {
super(hls, fragmentTracker, '[audio-stream-controller]');
constructor(
hls: Hls,
fragmentTracker: FragmentTracker,
keyLoader: KeyLoader
) {
super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]');
this._registerListeners();
}

Expand Down
29 changes: 22 additions & 7 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FragmentState } from './fragment-tracker';
import { Bufferable, BufferHelper } from '../utils/buffer-helper';
import { logger } from '../utils/logger';
import { Events } from '../events';
import { ErrorDetails } from '../errors';
import { ErrorDetails, ErrorTypes } from '../errors';
import { ChunkMetadata } from '../types/transmuxer';
import { appendUint8Array } from '../utils/mp4-tools';
import { alignStream } from '../utils/discontinuities';
Expand Down Expand Up @@ -100,14 +100,19 @@ export default class BaseStreamController
protected log: (msg: any) => void;
protected warn: (msg: any) => void;

constructor(hls: Hls, fragmentTracker: FragmentTracker, logPrefix: string) {
constructor(
hls: Hls,
fragmentTracker: FragmentTracker,
keyLoader: KeyLoader,
logPrefix: string
) {
super();
this.logPrefix = logPrefix;
this.log = logger.log.bind(logger, `${logPrefix}:`);
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
this.hls = hls;
this.fragmentLoader = new FragmentLoader(hls.config);
this.keyLoader = new KeyLoader(hls.config);
this.keyLoader = keyLoader;
this.fragmentTracker = fragmentTracker;
this.config = hls.config;
this.decrypter = new Decrypter(hls as HlsEventEmitter, hls.config);
Expand Down Expand Up @@ -720,11 +725,21 @@ export default class BaseStreamController
);
}

private handleFragLoadError({ data }: LoadError) {
if (data && data.details === ErrorDetails.INTERNAL_ABORTED) {
this.handleFragLoadAborted(data.frag, data.part);
private handleFragLoadError(error: LoadError | Error) {
if ('data' in error) {
const data = error.data;
if (error.data && data.details === ErrorDetails.INTERNAL_ABORTED) {
this.handleFragLoadAborted(data.frag, data.part);
} else {
this.hls.trigger(Events.ERROR, data as ErrorData);
}
} else {
this.hls.trigger(Events.ERROR, data as ErrorData);
this.hls.trigger(Events.ERROR, {
type: ErrorTypes.OTHER_ERROR,
details: ErrorDetails.INTERNAL_EXCEPTION,
err: error,
fatal: true,
});
}
return null;
}
Expand Down
Loading

0 comments on commit 3b551ea

Please sign in to comment.