@zenvor/hls.js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +472 -0
- package/dist/hls-demo.js +26995 -0
- package/dist/hls-demo.js.map +1 -0
- package/dist/hls.d.mts +4204 -0
- package/dist/hls.d.ts +4204 -0
- package/dist/hls.js +40050 -0
- package/dist/hls.js.d.ts +4204 -0
- package/dist/hls.js.map +1 -0
- package/dist/hls.light.js +27145 -0
- package/dist/hls.light.js.map +1 -0
- package/dist/hls.light.min.js +2 -0
- package/dist/hls.light.min.js.map +1 -0
- package/dist/hls.light.mjs +26392 -0
- package/dist/hls.light.mjs.map +1 -0
- package/dist/hls.min.js +2 -0
- package/dist/hls.min.js.map +1 -0
- package/dist/hls.mjs +38956 -0
- package/dist/hls.mjs.map +1 -0
- package/dist/hls.worker.js +2 -0
- package/dist/hls.worker.js.map +1 -0
- package/package.json +143 -0
- package/src/config.ts +794 -0
- package/src/controller/abr-controller.ts +1019 -0
- package/src/controller/algo-data-controller.ts +794 -0
- package/src/controller/audio-stream-controller.ts +1099 -0
- package/src/controller/audio-track-controller.ts +454 -0
- package/src/controller/base-playlist-controller.ts +438 -0
- package/src/controller/base-stream-controller.ts +2526 -0
- package/src/controller/buffer-controller.ts +2015 -0
- package/src/controller/buffer-operation-queue.ts +159 -0
- package/src/controller/cap-level-controller.ts +367 -0
- package/src/controller/cmcd-controller.ts +422 -0
- package/src/controller/content-steering-controller.ts +622 -0
- package/src/controller/eme-controller.ts +1617 -0
- package/src/controller/error-controller.ts +627 -0
- package/src/controller/fps-controller.ts +146 -0
- package/src/controller/fragment-finders.ts +256 -0
- package/src/controller/fragment-tracker.ts +567 -0
- package/src/controller/gap-controller.ts +719 -0
- package/src/controller/id3-track-controller.ts +488 -0
- package/src/controller/interstitial-player.ts +302 -0
- package/src/controller/interstitials-controller.ts +2895 -0
- package/src/controller/interstitials-schedule.ts +698 -0
- package/src/controller/latency-controller.ts +294 -0
- package/src/controller/level-controller.ts +776 -0
- package/src/controller/stream-controller.ts +1597 -0
- package/src/controller/subtitle-stream-controller.ts +508 -0
- package/src/controller/subtitle-track-controller.ts +617 -0
- package/src/controller/timeline-controller.ts +677 -0
- package/src/crypt/aes-crypto.ts +36 -0
- package/src/crypt/aes-decryptor.ts +339 -0
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +225 -0
- package/src/crypt/fast-aes-key.ts +39 -0
- package/src/define-plugin.d.ts +17 -0
- package/src/demux/audio/aacdemuxer.ts +126 -0
- package/src/demux/audio/ac3-demuxer.ts +170 -0
- package/src/demux/audio/adts.ts +249 -0
- package/src/demux/audio/base-audio-demuxer.ts +205 -0
- package/src/demux/audio/dolby.ts +21 -0
- package/src/demux/audio/mp3demuxer.ts +85 -0
- package/src/demux/audio/mpegaudio.ts +177 -0
- package/src/demux/chunk-cache.ts +42 -0
- package/src/demux/dummy-demuxed-track.ts +13 -0
- package/src/demux/inject-worker.ts +75 -0
- package/src/demux/mp4demuxer.ts +234 -0
- package/src/demux/sample-aes.ts +198 -0
- package/src/demux/transmuxer-interface.ts +449 -0
- package/src/demux/transmuxer-worker.ts +221 -0
- package/src/demux/transmuxer.ts +560 -0
- package/src/demux/tsdemuxer.ts +1256 -0
- package/src/demux/video/avc-video-parser.ts +401 -0
- package/src/demux/video/base-video-parser.ts +198 -0
- package/src/demux/video/exp-golomb.ts +153 -0
- package/src/demux/video/hevc-video-parser.ts +736 -0
- package/src/empty-es.js +5 -0
- package/src/empty.js +3 -0
- package/src/errors.ts +107 -0
- package/src/events.ts +548 -0
- package/src/exports-default.ts +3 -0
- package/src/exports-named.ts +81 -0
- package/src/hls.ts +1613 -0
- package/src/is-supported.ts +54 -0
- package/src/loader/date-range.ts +207 -0
- package/src/loader/fragment-loader.ts +403 -0
- package/src/loader/fragment.ts +487 -0
- package/src/loader/interstitial-asset-list.ts +162 -0
- package/src/loader/interstitial-event.ts +337 -0
- package/src/loader/key-loader.ts +439 -0
- package/src/loader/level-details.ts +203 -0
- package/src/loader/level-key.ts +259 -0
- package/src/loader/load-stats.ts +17 -0
- package/src/loader/m3u8-parser.ts +1072 -0
- package/src/loader/playlist-loader.ts +839 -0
- package/src/polyfills/number.ts +15 -0
- package/src/remux/aac-helper.ts +81 -0
- package/src/remux/mp4-generator.ts +1380 -0
- package/src/remux/mp4-remuxer.ts +1261 -0
- package/src/remux/passthrough-remuxer.ts +434 -0
- package/src/task-loop.ts +130 -0
- package/src/types/algo.ts +44 -0
- package/src/types/buffer.ts +105 -0
- package/src/types/component-api.ts +20 -0
- package/src/types/demuxer.ts +208 -0
- package/src/types/events.ts +574 -0
- package/src/types/fragment-tracker.ts +23 -0
- package/src/types/level.ts +268 -0
- package/src/types/loader.ts +198 -0
- package/src/types/media-playlist.ts +92 -0
- package/src/types/network-details.ts +3 -0
- package/src/types/remuxer.ts +104 -0
- package/src/types/track.ts +12 -0
- package/src/types/transmuxer.ts +46 -0
- package/src/types/tuples.ts +6 -0
- package/src/types/vtt.ts +11 -0
- package/src/utils/arrays.ts +22 -0
- package/src/utils/attr-list.ts +192 -0
- package/src/utils/binary-search.ts +46 -0
- package/src/utils/buffer-helper.ts +173 -0
- package/src/utils/cea-608-parser.ts +1413 -0
- package/src/utils/chunker.ts +41 -0
- package/src/utils/codecs.ts +314 -0
- package/src/utils/cues.ts +96 -0
- package/src/utils/discontinuities.ts +174 -0
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/error-helper.ts +95 -0
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/ewma-bandwidth-estimator.ts +97 -0
- package/src/utils/ewma.ts +43 -0
- package/src/utils/fetch-loader.ts +331 -0
- package/src/utils/global.ts +2 -0
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +67 -0
- package/src/utils/hex.ts +32 -0
- package/src/utils/imsc1-ttml-parser.ts +261 -0
- package/src/utils/keysystem-util.ts +45 -0
- package/src/utils/level-helper.ts +629 -0
- package/src/utils/logger.ts +120 -0
- package/src/utils/media-option-attributes.ts +49 -0
- package/src/utils/mediacapabilities-helper.ts +301 -0
- package/src/utils/mediakeys-helper.ts +210 -0
- package/src/utils/mediasource-helper.ts +37 -0
- package/src/utils/mp4-tools.ts +1473 -0
- package/src/utils/number.ts +3 -0
- package/src/utils/numeric-encoding-utils.ts +26 -0
- package/src/utils/output-filter.ts +46 -0
- package/src/utils/rendition-helper.ts +505 -0
- package/src/utils/safe-json-stringify.ts +22 -0
- package/src/utils/texttrack-utils.ts +164 -0
- package/src/utils/time-ranges.ts +17 -0
- package/src/utils/timescale-conversion.ts +46 -0
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +105 -0
- package/src/utils/vttcue.ts +384 -0
- package/src/utils/vttparser.ts +497 -0
- package/src/utils/webvtt-parser.ts +166 -0
- package/src/utils/xhr-loader.ts +337 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
import BasePlaylistController from './base-playlist-controller';
|
|
2
|
+
import { ErrorDetails, ErrorTypes } from '../errors';
|
|
3
|
+
import { Events } from '../events';
|
|
4
|
+
import { isVideoRange, Level, VideoRangeValues } from '../types/level';
|
|
5
|
+
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
|
|
6
|
+
import {
|
|
7
|
+
areCodecsMediaSourceSupported,
|
|
8
|
+
codecsSetSelectionPreferenceValue,
|
|
9
|
+
convertAVC1ToAVCOTI,
|
|
10
|
+
getCodecCompatibleName,
|
|
11
|
+
videoCodecPreferenceValue,
|
|
12
|
+
} from '../utils/codecs';
|
|
13
|
+
import { reassignFragmentLevelIndexes } from '../utils/level-helper';
|
|
14
|
+
import { getUnsupportedResult } from '../utils/mediacapabilities-helper';
|
|
15
|
+
import { stringify } from '../utils/safe-json-stringify';
|
|
16
|
+
import type ContentSteeringController from './content-steering-controller';
|
|
17
|
+
import type Hls from '../hls';
|
|
18
|
+
import type {
|
|
19
|
+
ErrorData,
|
|
20
|
+
FragBufferedData,
|
|
21
|
+
LevelLoadedData,
|
|
22
|
+
LevelsUpdatedData,
|
|
23
|
+
LevelSwitchingData,
|
|
24
|
+
ManifestLoadedData,
|
|
25
|
+
ManifestLoadingData,
|
|
26
|
+
ManifestParsedData,
|
|
27
|
+
} from '../types/events';
|
|
28
|
+
import type { HlsUrlParameters, LevelParsed } from '../types/level';
|
|
29
|
+
import type { MediaPlaylist } from '../types/media-playlist';
|
|
30
|
+
|
|
31
|
+
export default class LevelController extends BasePlaylistController {
|
|
32
|
+
private _levels: Level[] = [];
|
|
33
|
+
private _firstLevel: number = -1;
|
|
34
|
+
private _maxAutoLevel: number = -1;
|
|
35
|
+
private _startLevel?: number;
|
|
36
|
+
private currentLevel: Level | null = null;
|
|
37
|
+
private currentLevelIndex: number = -1;
|
|
38
|
+
private manualLevelIndex: number = -1;
|
|
39
|
+
private steering: ContentSteeringController | null;
|
|
40
|
+
private lastABRSwitchTime: number = -1;
|
|
41
|
+
|
|
42
|
+
public onParsedComplete!: Function;
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
hls: Hls,
|
|
46
|
+
contentSteeringController: ContentSteeringController | null,
|
|
47
|
+
) {
|
|
48
|
+
super(hls, 'level-controller');
|
|
49
|
+
this.steering = contentSteeringController;
|
|
50
|
+
this._registerListeners();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private _registerListeners() {
|
|
54
|
+
const { hls } = this;
|
|
55
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
56
|
+
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
|
57
|
+
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
|
58
|
+
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
|
59
|
+
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
|
60
|
+
hls.on(Events.ERROR, this.onError, this);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private _unregisterListeners() {
|
|
64
|
+
const { hls } = this;
|
|
65
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
66
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
|
67
|
+
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
|
68
|
+
hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
|
69
|
+
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
|
70
|
+
hls.off(Events.ERROR, this.onError, this);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public destroy() {
|
|
74
|
+
this._unregisterListeners();
|
|
75
|
+
this.steering = null;
|
|
76
|
+
this.resetLevels();
|
|
77
|
+
super.destroy();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public stopLoad(): void {
|
|
81
|
+
const levels = this._levels;
|
|
82
|
+
|
|
83
|
+
// clean up live level details to force reload them, and reset load errors
|
|
84
|
+
levels.forEach((level) => {
|
|
85
|
+
level.loadError = 0;
|
|
86
|
+
level.fragmentError = 0;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
super.stopLoad();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private resetLevels() {
|
|
93
|
+
this._startLevel = undefined;
|
|
94
|
+
this.manualLevelIndex = -1;
|
|
95
|
+
this.currentLevelIndex = -1;
|
|
96
|
+
this.currentLevel = null;
|
|
97
|
+
this._levels = [];
|
|
98
|
+
this._maxAutoLevel = -1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private onManifestLoading(
|
|
102
|
+
event: Events.MANIFEST_LOADING,
|
|
103
|
+
data: ManifestLoadingData,
|
|
104
|
+
) {
|
|
105
|
+
this.resetLevels();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected onManifestLoaded(
|
|
109
|
+
event: Events.MANIFEST_LOADED,
|
|
110
|
+
data: ManifestLoadedData,
|
|
111
|
+
) {
|
|
112
|
+
const preferManagedMediaSource = this.hls.config.preferManagedMediaSource;
|
|
113
|
+
const levels: Level[] = [];
|
|
114
|
+
const redundantSet: { [key: string]: Level } = {};
|
|
115
|
+
const generatePathwaySet: { [key: string]: number } = {};
|
|
116
|
+
let resolutionFound = false;
|
|
117
|
+
let videoCodecFound = false;
|
|
118
|
+
let audioCodecFound = false;
|
|
119
|
+
|
|
120
|
+
data.levels.forEach((levelParsed: LevelParsed) => {
|
|
121
|
+
const attributes = levelParsed.attrs;
|
|
122
|
+
let { audioCodec, videoCodec } = levelParsed;
|
|
123
|
+
if (audioCodec) {
|
|
124
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
|
125
|
+
levelParsed.audioCodec = audioCodec =
|
|
126
|
+
getCodecCompatibleName(audioCodec, preferManagedMediaSource) ||
|
|
127
|
+
undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (videoCodec) {
|
|
131
|
+
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// only keep levels with supported audio/video codecs
|
|
135
|
+
const { width, height, unknownCodecs } = levelParsed;
|
|
136
|
+
const unknownUnsupportedCodecCount = unknownCodecs?.length || 0;
|
|
137
|
+
|
|
138
|
+
resolutionFound ||= !!(width && height);
|
|
139
|
+
videoCodecFound ||= !!videoCodec;
|
|
140
|
+
audioCodecFound ||= !!audioCodec;
|
|
141
|
+
if (
|
|
142
|
+
unknownUnsupportedCodecCount ||
|
|
143
|
+
(audioCodec && !this.isAudioSupported(audioCodec)) ||
|
|
144
|
+
(videoCodec && !this.isVideoSupported(videoCodec))
|
|
145
|
+
) {
|
|
146
|
+
this.log(`Some or all CODECS not supported "${attributes.CODECS}"`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const {
|
|
151
|
+
CODECS,
|
|
152
|
+
'FRAME-RATE': FRAMERATE,
|
|
153
|
+
'HDCP-LEVEL': HDCP,
|
|
154
|
+
'PATHWAY-ID': PATHWAY,
|
|
155
|
+
RESOLUTION,
|
|
156
|
+
'VIDEO-RANGE': VIDEO_RANGE,
|
|
157
|
+
} = attributes;
|
|
158
|
+
const contentSteeringPrefix = `${PATHWAY || '.'}-`;
|
|
159
|
+
const levelKey = `${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}-${VIDEO_RANGE}-${HDCP}`;
|
|
160
|
+
|
|
161
|
+
if (!redundantSet[levelKey]) {
|
|
162
|
+
const level = this.createLevel(levelParsed);
|
|
163
|
+
redundantSet[levelKey] = level;
|
|
164
|
+
generatePathwaySet[levelKey] = 1;
|
|
165
|
+
levels.push(level);
|
|
166
|
+
} else if (
|
|
167
|
+
redundantSet[levelKey].uri !== levelParsed.url &&
|
|
168
|
+
!levelParsed.attrs['PATHWAY-ID']
|
|
169
|
+
) {
|
|
170
|
+
// Assign Pathway IDs to Redundant Streams (default Pathways is ".". Redundant Streams "..", "...", and so on.)
|
|
171
|
+
// Content Steering controller to handles Pathway fallback on error
|
|
172
|
+
const pathwayCount = (generatePathwaySet[levelKey] += 1);
|
|
173
|
+
levelParsed.attrs['PATHWAY-ID'] = new Array(pathwayCount + 1).join('.');
|
|
174
|
+
const level = this.createLevel(levelParsed);
|
|
175
|
+
redundantSet[levelKey] = level;
|
|
176
|
+
levels.push(level);
|
|
177
|
+
} else {
|
|
178
|
+
redundantSet[levelKey].addGroupId('audio', attributes.AUDIO);
|
|
179
|
+
redundantSet[levelKey].addGroupId('text', attributes.SUBTITLES);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
this.filterAndSortMediaOptions(
|
|
184
|
+
levels,
|
|
185
|
+
data,
|
|
186
|
+
resolutionFound,
|
|
187
|
+
videoCodecFound,
|
|
188
|
+
audioCodecFound,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private createLevel(levelParsed: LevelParsed): Level {
|
|
193
|
+
const level = new Level(levelParsed);
|
|
194
|
+
const supplemental = levelParsed.supplemental;
|
|
195
|
+
if (
|
|
196
|
+
supplemental?.videoCodec &&
|
|
197
|
+
!this.isVideoSupported(supplemental.videoCodec)
|
|
198
|
+
) {
|
|
199
|
+
const error = new Error(
|
|
200
|
+
`SUPPLEMENTAL-CODECS not supported "${supplemental.videoCodec}"`,
|
|
201
|
+
);
|
|
202
|
+
this.log(error.message);
|
|
203
|
+
level.supportedResult = getUnsupportedResult(error, []);
|
|
204
|
+
}
|
|
205
|
+
return level;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private isAudioSupported(codec: string): boolean {
|
|
209
|
+
return areCodecsMediaSourceSupported(
|
|
210
|
+
codec,
|
|
211
|
+
'audio',
|
|
212
|
+
this.hls.config.preferManagedMediaSource,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private isVideoSupported(codec: string): boolean {
|
|
217
|
+
return areCodecsMediaSourceSupported(
|
|
218
|
+
codec,
|
|
219
|
+
'video',
|
|
220
|
+
this.hls.config.preferManagedMediaSource,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private filterAndSortMediaOptions(
|
|
225
|
+
filteredLevels: Level[],
|
|
226
|
+
data: ManifestLoadedData,
|
|
227
|
+
resolutionFound: boolean,
|
|
228
|
+
videoCodecFound: boolean,
|
|
229
|
+
audioCodecFound: boolean,
|
|
230
|
+
) {
|
|
231
|
+
let audioTracks: MediaPlaylist[] = [];
|
|
232
|
+
let subtitleTracks: MediaPlaylist[] = [];
|
|
233
|
+
let levels = filteredLevels;
|
|
234
|
+
const statsParsing = data.stats?.parsing || {};
|
|
235
|
+
|
|
236
|
+
// remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled
|
|
237
|
+
if ((resolutionFound || videoCodecFound) && audioCodecFound) {
|
|
238
|
+
levels = levels.filter(
|
|
239
|
+
({ videoCodec, videoRange, width, height }) =>
|
|
240
|
+
(!!videoCodec || !!(width && height)) && isVideoRange(videoRange),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (levels.length === 0) {
|
|
245
|
+
// Dispatch error after MANIFEST_LOADED is done propagating
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
247
|
+
Promise.resolve().then(() => {
|
|
248
|
+
if (this.hls) {
|
|
249
|
+
let message = 'no level with compatible codecs found in manifest';
|
|
250
|
+
let reason = message;
|
|
251
|
+
if (data.levels.length) {
|
|
252
|
+
reason = `one or more CODECS in variant not supported: ${stringify(
|
|
253
|
+
data.levels
|
|
254
|
+
.map((level) => level.attrs.CODECS)
|
|
255
|
+
.filter(
|
|
256
|
+
(value, index, array) => array.indexOf(value) === index,
|
|
257
|
+
),
|
|
258
|
+
)}`;
|
|
259
|
+
this.warn(reason);
|
|
260
|
+
message += ` (${reason})`;
|
|
261
|
+
}
|
|
262
|
+
const error = new Error(message);
|
|
263
|
+
this.hls.trigger(Events.ERROR, {
|
|
264
|
+
type: ErrorTypes.MEDIA_ERROR,
|
|
265
|
+
details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR,
|
|
266
|
+
fatal: true,
|
|
267
|
+
url: data.url,
|
|
268
|
+
error,
|
|
269
|
+
reason,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
statsParsing.end = performance.now();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (data.audioTracks) {
|
|
278
|
+
audioTracks = data.audioTracks.filter(
|
|
279
|
+
(track) => !track.audioCodec || this.isAudioSupported(track.audioCodec),
|
|
280
|
+
);
|
|
281
|
+
// Assign ids after filtering as array indices by group-id
|
|
282
|
+
assignTrackIdsByGroup(audioTracks);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (data.subtitles) {
|
|
286
|
+
subtitleTracks = data.subtitles;
|
|
287
|
+
assignTrackIdsByGroup(subtitleTracks);
|
|
288
|
+
}
|
|
289
|
+
// start bitrate is the first bitrate of the manifest
|
|
290
|
+
const unsortedLevels = levels.slice(0);
|
|
291
|
+
// sort levels from lowest to highest
|
|
292
|
+
levels.sort((a, b) => {
|
|
293
|
+
if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) {
|
|
294
|
+
return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '')
|
|
295
|
+
? 1
|
|
296
|
+
: -1;
|
|
297
|
+
}
|
|
298
|
+
// sort on height before bitrate for cap-level-controller
|
|
299
|
+
if (resolutionFound && a.height !== b.height) {
|
|
300
|
+
return a.height - b.height;
|
|
301
|
+
}
|
|
302
|
+
if (a.frameRate !== b.frameRate) {
|
|
303
|
+
return a.frameRate - b.frameRate;
|
|
304
|
+
}
|
|
305
|
+
if (a.videoRange !== b.videoRange) {
|
|
306
|
+
return (
|
|
307
|
+
VideoRangeValues.indexOf(a.videoRange) -
|
|
308
|
+
VideoRangeValues.indexOf(b.videoRange)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (a.videoCodec !== b.videoCodec) {
|
|
312
|
+
const valueA = videoCodecPreferenceValue(a.videoCodec);
|
|
313
|
+
const valueB = videoCodecPreferenceValue(b.videoCodec);
|
|
314
|
+
if (valueA !== valueB) {
|
|
315
|
+
return valueB - valueA;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (a.uri === b.uri && a.codecSet !== b.codecSet) {
|
|
319
|
+
const valueA = codecsSetSelectionPreferenceValue(a.codecSet);
|
|
320
|
+
const valueB = codecsSetSelectionPreferenceValue(b.codecSet);
|
|
321
|
+
if (valueA !== valueB) {
|
|
322
|
+
return valueB - valueA;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (a.averageBitrate !== b.averageBitrate) {
|
|
326
|
+
return a.averageBitrate - b.averageBitrate;
|
|
327
|
+
}
|
|
328
|
+
return 0;
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
let firstLevelInPlaylist = unsortedLevels[0];
|
|
332
|
+
if (this.steering) {
|
|
333
|
+
levels = this.steering.filterParsedLevels(levels);
|
|
334
|
+
if (levels.length !== unsortedLevels.length) {
|
|
335
|
+
for (let i = 0; i < unsortedLevels.length; i++) {
|
|
336
|
+
if (unsortedLevels[i].pathwayId === levels[0].pathwayId) {
|
|
337
|
+
firstLevelInPlaylist = unsortedLevels[i];
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this._levels = levels;
|
|
345
|
+
|
|
346
|
+
// find index of first level in sorted levels
|
|
347
|
+
for (let i = 0; i < levels.length; i++) {
|
|
348
|
+
if (levels[i] === firstLevelInPlaylist) {
|
|
349
|
+
this._firstLevel = i;
|
|
350
|
+
const firstLevelBitrate = firstLevelInPlaylist.bitrate;
|
|
351
|
+
const bandwidthEstimate = this.hls.bandwidthEstimate;
|
|
352
|
+
this.log(
|
|
353
|
+
`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`,
|
|
354
|
+
);
|
|
355
|
+
// Update default bwe to first variant bitrate as long it has not been configured or set
|
|
356
|
+
if (this.hls.userConfig?.abrEwmaDefaultEstimate === undefined) {
|
|
357
|
+
const startingBwEstimate = Math.min(
|
|
358
|
+
firstLevelBitrate,
|
|
359
|
+
this.hls.config.abrEwmaDefaultEstimateMax,
|
|
360
|
+
);
|
|
361
|
+
if (
|
|
362
|
+
startingBwEstimate > bandwidthEstimate &&
|
|
363
|
+
bandwidthEstimate === this.hls.abrEwmaDefaultEstimate
|
|
364
|
+
) {
|
|
365
|
+
this.hls.bandwidthEstimate = startingBwEstimate;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Audio is only alternate if manifest include a URI along with the audio group tag,
|
|
373
|
+
// and this is not an audio-only stream where levels contain audio-only
|
|
374
|
+
const audioOnly = audioCodecFound && !videoCodecFound;
|
|
375
|
+
const config = this.hls.config;
|
|
376
|
+
const altAudioEnabled = !!(
|
|
377
|
+
config.audioStreamController && config.audioTrackController
|
|
378
|
+
);
|
|
379
|
+
const edata: ManifestParsedData = {
|
|
380
|
+
levels,
|
|
381
|
+
audioTracks,
|
|
382
|
+
subtitleTracks,
|
|
383
|
+
sessionData: data.sessionData,
|
|
384
|
+
sessionKeys: data.sessionKeys,
|
|
385
|
+
firstLevel: this._firstLevel,
|
|
386
|
+
stats: data.stats,
|
|
387
|
+
audio: audioCodecFound,
|
|
388
|
+
video: videoCodecFound,
|
|
389
|
+
altAudio:
|
|
390
|
+
altAudioEnabled && !audioOnly && audioTracks.some((t) => !!t.url),
|
|
391
|
+
};
|
|
392
|
+
statsParsing.end = performance.now();
|
|
393
|
+
this.hls.trigger(Events.MANIFEST_PARSED, edata);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
get levels(): Level[] | null {
|
|
397
|
+
if (this._levels.length === 0) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
return this._levels;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
get loadLevelObj(): Level | null {
|
|
404
|
+
return this.currentLevel;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
get level(): number {
|
|
408
|
+
return this.currentLevelIndex;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
set level(newLevel: number) {
|
|
412
|
+
const levels = this._levels;
|
|
413
|
+
if (levels.length === 0) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
// check if level idx is valid
|
|
417
|
+
if (newLevel < 0 || newLevel >= levels.length) {
|
|
418
|
+
// invalid level id given, trigger error
|
|
419
|
+
const error = new Error('invalid level idx');
|
|
420
|
+
const fatal = newLevel < 0;
|
|
421
|
+
this.hls.trigger(Events.ERROR, {
|
|
422
|
+
type: ErrorTypes.OTHER_ERROR,
|
|
423
|
+
details: ErrorDetails.LEVEL_SWITCH_ERROR,
|
|
424
|
+
level: newLevel,
|
|
425
|
+
fatal,
|
|
426
|
+
error,
|
|
427
|
+
reason: error.message,
|
|
428
|
+
});
|
|
429
|
+
if (fatal) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
newLevel = Math.min(newLevel, levels.length - 1);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const lastLevelIndex = this.currentLevelIndex;
|
|
436
|
+
const lastLevel = this.currentLevel;
|
|
437
|
+
const lastPathwayId = lastLevel ? lastLevel.attrs['PATHWAY-ID'] : undefined;
|
|
438
|
+
const level = levels[newLevel];
|
|
439
|
+
const pathwayId = level.attrs['PATHWAY-ID'];
|
|
440
|
+
this.currentLevelIndex = newLevel;
|
|
441
|
+
this.currentLevel = level;
|
|
442
|
+
|
|
443
|
+
if (
|
|
444
|
+
lastLevelIndex === newLevel &&
|
|
445
|
+
lastLevel &&
|
|
446
|
+
lastPathwayId === pathwayId
|
|
447
|
+
) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
this.log(
|
|
452
|
+
`Switching to level ${newLevel} (${
|
|
453
|
+
level.height ? level.height + 'p ' : ''
|
|
454
|
+
}${level.videoRange ? level.videoRange + ' ' : ''}${
|
|
455
|
+
level.codecSet ? level.codecSet + ' ' : ''
|
|
456
|
+
}@${level.bitrate})${
|
|
457
|
+
pathwayId ? ' with Pathway ' + pathwayId : ''
|
|
458
|
+
} from level ${lastLevelIndex}${
|
|
459
|
+
lastPathwayId ? ' with Pathway ' + lastPathwayId : ''
|
|
460
|
+
}`,
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const levelSwitchingData: LevelSwitchingData = {
|
|
464
|
+
level: newLevel,
|
|
465
|
+
attrs: level.attrs,
|
|
466
|
+
details: level.details,
|
|
467
|
+
bitrate: level.bitrate,
|
|
468
|
+
averageBitrate: level.averageBitrate,
|
|
469
|
+
maxBitrate: level.maxBitrate,
|
|
470
|
+
realBitrate: level.realBitrate,
|
|
471
|
+
width: level.width,
|
|
472
|
+
height: level.height,
|
|
473
|
+
codecSet: level.codecSet,
|
|
474
|
+
audioCodec: level.audioCodec,
|
|
475
|
+
videoCodec: level.videoCodec,
|
|
476
|
+
audioGroups: level.audioGroups,
|
|
477
|
+
subtitleGroups: level.subtitleGroups,
|
|
478
|
+
loaded: level.loaded,
|
|
479
|
+
loadError: level.loadError,
|
|
480
|
+
fragmentError: level.fragmentError,
|
|
481
|
+
name: level.name,
|
|
482
|
+
id: level.id,
|
|
483
|
+
uri: level.uri,
|
|
484
|
+
url: level.url,
|
|
485
|
+
urlId: 0,
|
|
486
|
+
audioGroupIds: level.audioGroupIds,
|
|
487
|
+
textGroupIds: level.textGroupIds,
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData);
|
|
491
|
+
// check if we need to load playlist for this level
|
|
492
|
+
const levelDetails = level.details;
|
|
493
|
+
if (!levelDetails || levelDetails.live) {
|
|
494
|
+
// level not retrieved yet, or live playlist we need to (re)load it
|
|
495
|
+
const hlsUrlParameters = this.switchParams(
|
|
496
|
+
level.uri,
|
|
497
|
+
lastLevel?.details,
|
|
498
|
+
levelDetails,
|
|
499
|
+
);
|
|
500
|
+
this.loadPlaylist(hlsUrlParameters);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
get manualLevel(): number {
|
|
505
|
+
return this.manualLevelIndex;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
set manualLevel(newLevel) {
|
|
509
|
+
this.manualLevelIndex = newLevel;
|
|
510
|
+
if (this._startLevel === undefined) {
|
|
511
|
+
this._startLevel = newLevel;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (newLevel !== -1) {
|
|
515
|
+
this.level = newLevel;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
get firstLevel(): number {
|
|
520
|
+
return this._firstLevel;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
set firstLevel(newLevel) {
|
|
524
|
+
this._firstLevel = newLevel;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
get startLevel(): number {
|
|
528
|
+
// Setting hls.startLevel (this._startLevel) overrides config.startLevel
|
|
529
|
+
if (this._startLevel === undefined) {
|
|
530
|
+
const configStartLevel = this.hls.config.startLevel;
|
|
531
|
+
if (configStartLevel !== undefined) {
|
|
532
|
+
return configStartLevel;
|
|
533
|
+
}
|
|
534
|
+
return this.hls.firstAutoLevel;
|
|
535
|
+
}
|
|
536
|
+
return this._startLevel;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
set startLevel(newLevel: number) {
|
|
540
|
+
this._startLevel = newLevel;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
get pathways(): string[] {
|
|
544
|
+
if (this.steering) {
|
|
545
|
+
return this.steering.pathways();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return [];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
get pathwayPriority(): string[] | null {
|
|
552
|
+
if (this.steering) {
|
|
553
|
+
return this.steering.pathwayPriority;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
set pathwayPriority(pathwayPriority: string[]) {
|
|
560
|
+
if (this.steering) {
|
|
561
|
+
const pathwaysList = this.steering.pathways();
|
|
562
|
+
const filteredPathwayPriority = pathwayPriority.filter((pathwayId) => {
|
|
563
|
+
return pathwaysList.indexOf(pathwayId) !== -1;
|
|
564
|
+
});
|
|
565
|
+
if (pathwayPriority.length < 1) {
|
|
566
|
+
this.warn(
|
|
567
|
+
`pathwayPriority ${pathwayPriority} should contain at least one pathway from list: ${pathwaysList}`,
|
|
568
|
+
);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
this.steering.pathwayPriority = filteredPathwayPriority;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
|
576
|
+
if (data.fatal || !data.context) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (
|
|
581
|
+
data.context.type === PlaylistContextType.LEVEL &&
|
|
582
|
+
data.context.level === this.level
|
|
583
|
+
) {
|
|
584
|
+
this.checkRetry(data);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// reset errors on the successful load of a fragment
|
|
589
|
+
protected onFragBuffered(
|
|
590
|
+
event: Events.FRAG_BUFFERED,
|
|
591
|
+
{ frag }: FragBufferedData,
|
|
592
|
+
) {
|
|
593
|
+
if (frag !== undefined && frag.type === PlaylistLevelType.MAIN) {
|
|
594
|
+
const el = frag.elementaryStreams;
|
|
595
|
+
if (!Object.keys(el).some((type) => !!el[type])) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const level = this._levels[frag.level];
|
|
599
|
+
if (level?.loadError) {
|
|
600
|
+
this.log(
|
|
601
|
+
`Resetting level error count of ${level.loadError} on frag buffered`,
|
|
602
|
+
);
|
|
603
|
+
level.loadError = 0;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
protected onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
|
609
|
+
const { level, details } = data;
|
|
610
|
+
const curLevel = data.levelInfo;
|
|
611
|
+
|
|
612
|
+
if (!curLevel) {
|
|
613
|
+
this.warn(`Invalid level index ${level}`);
|
|
614
|
+
if (data.deliveryDirectives?.skip) {
|
|
615
|
+
details.deltaUpdateFailed = true;
|
|
616
|
+
}
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// only process level loaded events matching with expected level or prior to switch when media playlist is loaded directly
|
|
621
|
+
if (curLevel === this.currentLevel || data.withoutMultiVariant) {
|
|
622
|
+
// reset level load error counter on successful level loaded only if there is no issues with fragments
|
|
623
|
+
if (curLevel.fragmentError === 0) {
|
|
624
|
+
curLevel.loadError = 0;
|
|
625
|
+
}
|
|
626
|
+
// Ignore matching details populated by loading a Media Playlist directly
|
|
627
|
+
let previousDetails = curLevel.details;
|
|
628
|
+
if (previousDetails === data.details && previousDetails.advanced) {
|
|
629
|
+
previousDetails = undefined;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
this.playlistLoaded(level, data, previousDetails);
|
|
633
|
+
} else if (data.deliveryDirectives?.skip) {
|
|
634
|
+
// received a delta playlist update that cannot be merged
|
|
635
|
+
details.deltaUpdateFailed = true;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
protected loadPlaylist(hlsUrlParameters?: HlsUrlParameters) {
|
|
640
|
+
super.loadPlaylist();
|
|
641
|
+
if (this.shouldLoadPlaylist(this.currentLevel)) {
|
|
642
|
+
this.scheduleLoading(this.currentLevel, hlsUrlParameters);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
protected loadingPlaylist(
|
|
647
|
+
currentLevel: Level,
|
|
648
|
+
hlsUrlParameters: HlsUrlParameters | undefined,
|
|
649
|
+
) {
|
|
650
|
+
super.loadingPlaylist(currentLevel, hlsUrlParameters);
|
|
651
|
+
const url = this.getUrlWithDirectives(currentLevel.uri, hlsUrlParameters);
|
|
652
|
+
const currentLevelIndex = this.currentLevelIndex;
|
|
653
|
+
const pathwayId = currentLevel.attrs['PATHWAY-ID'];
|
|
654
|
+
const details = currentLevel.details;
|
|
655
|
+
const age = details?.age;
|
|
656
|
+
this.log(
|
|
657
|
+
`Loading level index ${currentLevelIndex}${
|
|
658
|
+
hlsUrlParameters?.msn !== undefined
|
|
659
|
+
? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part
|
|
660
|
+
: ''
|
|
661
|
+
}${pathwayId ? ' Pathway ' + pathwayId : ''}${age && details.live ? ' age ' + age.toFixed(1) + (details.type ? ' ' + details.type || '' : '') : ''} ${url}`,
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
this.hls.trigger(Events.LEVEL_LOADING, {
|
|
665
|
+
url,
|
|
666
|
+
level: currentLevelIndex,
|
|
667
|
+
levelInfo: currentLevel,
|
|
668
|
+
pathwayId: currentLevel.attrs['PATHWAY-ID'],
|
|
669
|
+
id: 0, // Deprecated Level urlId
|
|
670
|
+
deliveryDirectives: hlsUrlParameters || null,
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
get nextLoadLevel() {
|
|
675
|
+
if (this.manualLevelIndex !== -1) {
|
|
676
|
+
return this.manualLevelIndex;
|
|
677
|
+
} else {
|
|
678
|
+
return this.hls.nextAutoLevel;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
set nextLoadLevel(nextLevel) {
|
|
683
|
+
const currentLevel = this.currentLevelIndex;
|
|
684
|
+
const isABRSwitch = this.manualLevelIndex === -1;
|
|
685
|
+
if (isABRSwitch && nextLevel !== currentLevel && nextLevel !== -1) {
|
|
686
|
+
const abrSwitchInterval = this.hls.config.abrSwitchInterval;
|
|
687
|
+
if (abrSwitchInterval > 0) {
|
|
688
|
+
const now = performance.now();
|
|
689
|
+
const delta = now - this.lastABRSwitchTime;
|
|
690
|
+
const intervalMs = abrSwitchInterval * 1000;
|
|
691
|
+
if (this.lastABRSwitchTime > -1 && delta < intervalMs) {
|
|
692
|
+
this.warn(
|
|
693
|
+
`Preventing ABR level switch: ${currentLevel} -> ${nextLevel} (${Math.round(delta)}ms < ${intervalMs}ms / ${abrSwitchInterval}s)`,
|
|
694
|
+
);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
this.lastABRSwitchTime = now;
|
|
699
|
+
this.log(
|
|
700
|
+
`Allowing ABR level switch: ${currentLevel} -> ${nextLevel} (${Math.round(delta)}ms >= ${intervalMs}ms / ${abrSwitchInterval}s)`,
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
this.level = nextLevel;
|
|
706
|
+
if (this.manualLevelIndex === -1) {
|
|
707
|
+
this.hls.nextAutoLevel = nextLevel;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
removeLevel(levelIndex: number) {
|
|
712
|
+
if (this._levels.length === 1) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const levels = this._levels.filter((level, index) => {
|
|
716
|
+
if (index !== levelIndex) {
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
if (this.steering) {
|
|
720
|
+
this.steering.removeLevel(level);
|
|
721
|
+
}
|
|
722
|
+
if (level === this.currentLevel) {
|
|
723
|
+
this.currentLevel = null;
|
|
724
|
+
this.currentLevelIndex = -1;
|
|
725
|
+
if (level.details) {
|
|
726
|
+
level.details.fragments.forEach((f) => (f.level = -1));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return false;
|
|
730
|
+
});
|
|
731
|
+
reassignFragmentLevelIndexes(levels);
|
|
732
|
+
this._levels = levels;
|
|
733
|
+
if (this.currentLevelIndex > -1 && this.currentLevel?.details) {
|
|
734
|
+
this.currentLevelIndex = this.currentLevel.details.fragments[0].level;
|
|
735
|
+
}
|
|
736
|
+
if (this.manualLevelIndex > -1) {
|
|
737
|
+
this.manualLevelIndex = this.currentLevelIndex;
|
|
738
|
+
}
|
|
739
|
+
const maxLevel = levels.length - 1;
|
|
740
|
+
this._firstLevel = Math.min(this._firstLevel, maxLevel);
|
|
741
|
+
if (this._startLevel) {
|
|
742
|
+
this._startLevel = Math.min(this._startLevel, maxLevel);
|
|
743
|
+
}
|
|
744
|
+
this.hls.trigger(Events.LEVELS_UPDATED, { levels });
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private onLevelsUpdated(
|
|
748
|
+
event: Events.LEVELS_UPDATED,
|
|
749
|
+
{ levels }: LevelsUpdatedData,
|
|
750
|
+
) {
|
|
751
|
+
this._levels = levels;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
public checkMaxAutoUpdated() {
|
|
755
|
+
const { autoLevelCapping, maxAutoLevel, maxHdcpLevel } = this.hls;
|
|
756
|
+
if (this._maxAutoLevel !== maxAutoLevel) {
|
|
757
|
+
this._maxAutoLevel = maxAutoLevel;
|
|
758
|
+
this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, {
|
|
759
|
+
autoLevelCapping,
|
|
760
|
+
levels: this.levels,
|
|
761
|
+
maxAutoLevel,
|
|
762
|
+
minAutoLevel: this.hls.minAutoLevel,
|
|
763
|
+
maxHdcpLevel,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function assignTrackIdsByGroup(tracks: MediaPlaylist[]): void {
|
|
770
|
+
const groups = {};
|
|
771
|
+
tracks.forEach((track) => {
|
|
772
|
+
const groupId = track.groupId || '';
|
|
773
|
+
track.id = groups[groupId] = groups[groupId] || 0;
|
|
774
|
+
groups[groupId]++;
|
|
775
|
+
});
|
|
776
|
+
}
|