Skip to content

Commit

Permalink
Implement maxHdcpLevel HDCP-LEVEL capping and EME error handling (v…
Browse files Browse the repository at this point in the history
  • Loading branch information
robwalch authored Jan 11, 2023
1 parent e003590 commit 311ac64
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 10 deletions.
23 changes: 23 additions & 0 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,16 @@ export interface FragParsingUserdataData {
samples: UserdataSample[];
}

// Warning: (ae-missing-release-tag) "HdcpLevel" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type HdcpLevel = typeof HdcpLevels[number];

// Warning: (ae-missing-release-tag) "HdcpLevels" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const HdcpLevels: readonly ["NONE", "TYPE-0", "TYPE-1", "TYPE-2", null];

// Warning: (ae-missing-release-tag) "Hls" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
Expand Down Expand Up @@ -959,6 +969,9 @@ class Hls implements HlsEventEmitter {
get mainForwardBufferInfo(): BufferInfo | null;
get manualLevel(): number;
get maxAutoLevel(): number;
// (undocumented)
get maxHdcpLevel(): HdcpLevel;
set maxHdcpLevel(value: HdcpLevel);
get maxLatency(): number;
// (undocumented)
get media(): HTMLMediaElement | null;
Expand Down Expand Up @@ -1356,15 +1369,23 @@ export class Level {
//
// @public (undocumented)
export interface LevelAttributes extends AttrList {
// (undocumented)
'ALLOWED-CPC'?: string;
// (undocumented)
'AVERAGE-BANDWIDTH'?: string;
// (undocumented)
'CLOSED-CAPTIONS'?: string;
// (undocumented)
'FRAME-RATE'?: string;
// (undocumented)
'HDCP-LEVEL'?: string;
// (undocumented)
'PATHWAY-ID'?: string;
// (undocumented)
'PROGRAM-ID'?: string;
// (undocumented)
'VIDEO-RANGE'?: string;
// (undocumented)
AUDIO?: string;
// (undocumented)
AUTOSELECT?: string;
Expand All @@ -1387,6 +1408,8 @@ export interface LevelAttributes extends AttrList {
// (undocumented)
RESOLUTION?: string;
// (undocumented)
SCORE?: string;
// (undocumented)
SUBTITLES?: string;
// (undocumented)
TYPE?: string;
Expand Down
9 changes: 8 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
- [`hls.startLevel`](#hlsstartlevel)
- [`hls.autoLevelEnabled`](#hlsautolevelenabled)
- [`hls.autoLevelCapping`](#hlsautolevelcapping)
- [`hls.maxHdcpLevel`](#hlsmaxhdcplevel)
- [`hls.capLevelToPlayerSize`](#hlscapleveltoplayersize)
- [`hls.bandwidthEstimate`](#hlsbandwidthestimate)
- [`hls.removeLevel(levelIndex, urlId)`](#hlsremoveLevel)
Expand Down Expand Up @@ -1419,6 +1420,12 @@ Default value is `hls.firstLevel`.
Default value is `-1` (no level capping).
### `hls.maxHdcpLevel`
- get/set: The maximum HDCP-LEVEL allowed to be selected by auto level selection. Must be a valid HDCP-LEVEL value ('NONE', 'TYPE-0', 'TYPE-1', 'TYPE-2'), or null (default). `hls.maxHdcpLevel` is automatically set to the next lowest value when a `KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED` error occurs. To prevent manual selection of levels with specific HDCP-LEVEL attribute values, use `hls.removeLevel()` on `MANIFEST_LOADED` or on error.
Default value is null (no level capping based on HDCP-LEVEL)
### `hls.capLevelToPlayerSize`
- get: Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called.
Expand All @@ -1432,7 +1439,7 @@ get: Returns the current bandwidth estimate in bits/s, if available. Otherwise,
### `hls.removeLevel(levelIndex, urlId)`
Remove a loaded level from the list of levels, or a level url in from a list of redundant level urls.
Remove a loaded level from the list of levels, or a url from a level's list of redundant urls.
This can be used to remove a rendition or playlist url that errors frequently from the list of levels that a user
or hls.js can choose from.
Expand Down
39 changes: 35 additions & 4 deletions src/controller/level-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ErrorData,
LevelSwitchingData,
} from '../types/events';
import { Level } from '../types/level';
import { HdcpLevel, HdcpLevels, Level } from '../types/level';
import { Events } from '../events';
import { ErrorTypes, ErrorDetails } from '../errors';
import { isCodecSupportedInMp4 } from '../utils/codecs';
Expand Down Expand Up @@ -162,8 +162,27 @@ export default class LevelController extends BasePlaylistController {
if (levels.length > 0) {
// start bitrate is the first bitrate of the manifest
bitrateStart = levels[0].bitrate;
// sort level on bitrate
levels.sort((a, b) => a.bitrate - b.bitrate);
// sort levels from lowest to highest
levels.sort((a, b) => {
if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) {
return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '')
? 1
: -1;
}
if (a.bitrate !== b.bitrate) {
return a.bitrate - b.bitrate;
}
if (a.attrs.SCORE !== b.attrs.SCORE) {
return (
a.attrs.decimalFloatingPoint('SCORE') -
b.attrs.decimalFloatingPoint('SCORE')
);
}
if (resolutionFound && a.height !== b.height) {
return a.height - b.height;
}
return 0;
});
this._levels = levels;
// find index of first level in sorted levels
for (let i = 0; i < levels.length; i++) {
Expand Down Expand Up @@ -364,9 +383,21 @@ export default class LevelController extends BasePlaylistController {
}
}
break;
case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED: {
const restrictedHdcpLevel = level.attrs['HDCP-LEVEL'];
if (restrictedHdcpLevel) {
this.hls.maxHdcpLevel =
HdcpLevels[
HdcpLevels.indexOf(restrictedHdcpLevel as HdcpLevel) - 1
];
this.warn(
`Restricting playback to HDCP-LEVEL of "${this.hls.maxHdcpLevel}" or lower`
);
}
}
// eslint-disable-next-line no-fallthrough
case ErrorDetails.FRAG_PARSING_ERROR:
case ErrorDetails.KEY_SYSTEM_NO_SESSION:
case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:
levelIndex =
data.frag?.type === PlaylistLevelType.MAIN
? data.frag.level
Expand Down
32 changes: 27 additions & 5 deletions src/hls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type SubtitleTrackController from './controller/subtitle-track-controller
import type { ComponentAPI, NetworkComponentAPI } from './types/component-api';
import type { MediaPlaylist } from './types/media-playlist';
import type { HlsConfig } from './config';
import type { Level } from './types/level';
import { HdcpLevel, HdcpLevels, Level } from './types/level';
import type { Fragment } from './loader/fragment';
import { BufferInfo } from './utils/buffer-helper';

Expand All @@ -43,6 +43,7 @@ export default class Hls implements HlsEventEmitter {

private _emitter: HlsEventEmitter = new EventEmitter();
private _autoLevelCapping: number;
private _maxHdcpLevel: HdcpLevel = null;
private abrController: AbrController;
private bufferController: BufferController;
private capLevelController: CapLevelController;
Expand Down Expand Up @@ -592,6 +593,16 @@ export default class Hls implements HlsEventEmitter {
}
}

get maxHdcpLevel(): HdcpLevel {
return this._maxHdcpLevel;
}

set maxHdcpLevel(value: HdcpLevel) {
if (HdcpLevels.indexOf(value) > -1) {
this._maxHdcpLevel = value;
}
}

/**
* True when automatic level selection enabled
* @type {boolean}
Expand Down Expand Up @@ -634,7 +645,7 @@ export default class Hls implements HlsEventEmitter {
* @type {number}
*/
get maxAutoLevel(): number {
const { levels, autoLevelCapping } = this;
const { levels, autoLevelCapping, maxHdcpLevel } = this;

let maxAutoLevel;
if (autoLevelCapping === -1 && levels && levels.length) {
Expand All @@ -643,6 +654,15 @@ export default class Hls implements HlsEventEmitter {
maxAutoLevel = autoLevelCapping;
}

if (maxHdcpLevel) {
for (let i = maxAutoLevel; i--; ) {
const hdcpLevel = levels[i].attrs['HDCP-LEVEL'];
if (hdcpLevel && hdcpLevel <= maxHdcpLevel) {
return i;
}
}
}

return maxAutoLevel;
}

Expand Down Expand Up @@ -883,10 +903,12 @@ export type {
UserdataSample,
} from './types/demuxer';
export type {
LevelParsed,
LevelAttributes,
HlsUrlParameters,
HdcpLevel,
HdcpLevels,
HlsSkip,
HlsUrlParameters,
LevelAttributes,
LevelParsed,
} from './types/level';
export type {
PlaylistLevelType,
Expand Down
8 changes: 8 additions & 0 deletions src/types/level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface LevelParsed {
}

export interface LevelAttributes extends AttrList {
'ALLOWED-CPC'?: string;
AUDIO?: string;
AUTOSELECT?: string;
'AVERAGE-BANDWIDTH'?: string;
Expand All @@ -29,15 +30,22 @@ export interface LevelAttributes extends AttrList {
DEFAULT?: string;
FORCED?: string;
'FRAME-RATE'?: string;
'HDCP-LEVEL'?: string;
LANGUAGE?: string;
NAME?: string;
'PATHWAY-ID'?: string;
'PROGRAM-ID'?: string;
RESOLUTION?: string;
SCORE?: string;
SUBTITLES?: string;
TYPE?: string;
URI?: string;
'VIDEO-RANGE'?: string;
}

export const HdcpLevels = ['NONE', 'TYPE-0', 'TYPE-1', 'TYPE-2', null] as const;
export type HdcpLevel = typeof HdcpLevels[number];

export enum HlsSkip {
No = '',
Yes = 'YES',
Expand Down

0 comments on commit 311ac64

Please sign in to comment.