@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,839 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlaylistLoader - delegate for media manifest/playlist loading tasks. Takes care of parsing media to internal data-models.
|
|
3
|
+
*
|
|
4
|
+
* Once loaded, dispatches events with parsed data-models of manifest/levels/audio/subtitle tracks.
|
|
5
|
+
*
|
|
6
|
+
* Uses loader(s) set in config to do actual internal loading of resource tasks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import M3U8Parser from './m3u8-parser';
|
|
10
|
+
import { ErrorDetails, ErrorTypes } from '../errors';
|
|
11
|
+
import { Events } from '../events';
|
|
12
|
+
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
|
|
13
|
+
import { AttrList } from '../utils/attr-list';
|
|
14
|
+
import {
|
|
15
|
+
areCodecsMediaSourceSupported,
|
|
16
|
+
sampleEntryCodesISO,
|
|
17
|
+
} from '../utils/codecs';
|
|
18
|
+
import { computeReloadInterval } from '../utils/level-helper';
|
|
19
|
+
import type { LevelDetails } from './level-details';
|
|
20
|
+
import type { LoaderConfig, RetryConfig } from '../config';
|
|
21
|
+
import type Hls from '../hls';
|
|
22
|
+
import type { NetworkComponentAPI } from '../types/component-api';
|
|
23
|
+
import type {
|
|
24
|
+
ErrorData,
|
|
25
|
+
LevelLoadingData,
|
|
26
|
+
LevelsUpdatedData,
|
|
27
|
+
ManifestLoadingData,
|
|
28
|
+
TrackLoadingData,
|
|
29
|
+
} from '../types/events';
|
|
30
|
+
import type { Level, LevelParsed, VariableMap } from '../types/level';
|
|
31
|
+
import type {
|
|
32
|
+
Loader,
|
|
33
|
+
LoaderCallbacks,
|
|
34
|
+
LoaderConfiguration,
|
|
35
|
+
LoaderContext,
|
|
36
|
+
LoaderResponse,
|
|
37
|
+
LoaderStats,
|
|
38
|
+
PlaylistLoaderContext,
|
|
39
|
+
} from '../types/loader';
|
|
40
|
+
import type { MediaAttributes, MediaPlaylist } from '../types/media-playlist';
|
|
41
|
+
import type { NullableNetworkDetails } from '../types/network-details';
|
|
42
|
+
|
|
43
|
+
function mapContextToLevelType(
|
|
44
|
+
context: PlaylistLoaderContext,
|
|
45
|
+
): PlaylistLevelType {
|
|
46
|
+
const { type } = context;
|
|
47
|
+
|
|
48
|
+
switch (type) {
|
|
49
|
+
case PlaylistContextType.AUDIO_TRACK:
|
|
50
|
+
return PlaylistLevelType.AUDIO;
|
|
51
|
+
case PlaylistContextType.SUBTITLE_TRACK:
|
|
52
|
+
return PlaylistLevelType.SUBTITLE;
|
|
53
|
+
default:
|
|
54
|
+
return PlaylistLevelType.MAIN;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getResponseUrl(
|
|
59
|
+
response: LoaderResponse,
|
|
60
|
+
context: PlaylistLoaderContext,
|
|
61
|
+
): string {
|
|
62
|
+
let url = response.url;
|
|
63
|
+
// responseURL not supported on some browsers (it is used to detect URL redirection)
|
|
64
|
+
// data-uri mode also not supported (but no need to detect redirection)
|
|
65
|
+
if (url === undefined || url.indexOf('data:') === 0) {
|
|
66
|
+
// fallback to initial URL
|
|
67
|
+
url = context.url;
|
|
68
|
+
}
|
|
69
|
+
return url;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class PlaylistLoader implements NetworkComponentAPI {
|
|
73
|
+
private readonly hls: Hls;
|
|
74
|
+
private readonly loaders: {
|
|
75
|
+
[key: string]: Loader<LoaderContext>;
|
|
76
|
+
} = Object.create(null);
|
|
77
|
+
private variableList: VariableMap | null = null;
|
|
78
|
+
public onManifestLoaded = this.checkAutostartLoad;
|
|
79
|
+
|
|
80
|
+
constructor(hls: Hls) {
|
|
81
|
+
this.hls = hls;
|
|
82
|
+
this.registerListeners();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public startLoad(startPosition: number): void {}
|
|
86
|
+
|
|
87
|
+
public stopLoad(): void {
|
|
88
|
+
this.destroyInternalLoaders();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private registerListeners() {
|
|
92
|
+
const { hls } = this;
|
|
93
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
94
|
+
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
|
95
|
+
hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
|
|
96
|
+
hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
|
|
97
|
+
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private unregisterListeners() {
|
|
101
|
+
const { hls } = this;
|
|
102
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
103
|
+
hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
|
104
|
+
hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
|
|
105
|
+
hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
|
|
106
|
+
hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns defaults or configured loader-type overloads (pLoader and loader config params)
|
|
111
|
+
*/
|
|
112
|
+
private createInternalLoader(
|
|
113
|
+
context: PlaylistLoaderContext,
|
|
114
|
+
): Loader<LoaderContext> {
|
|
115
|
+
const config = this.hls.config;
|
|
116
|
+
const PLoader = config.pLoader;
|
|
117
|
+
const Loader = config.loader;
|
|
118
|
+
const InternalLoader = PLoader || Loader;
|
|
119
|
+
const loader = new InternalLoader(config) as Loader<PlaylistLoaderContext>;
|
|
120
|
+
|
|
121
|
+
this.loaders[context.type] = loader;
|
|
122
|
+
return loader;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private getInternalLoader(
|
|
126
|
+
context: PlaylistLoaderContext,
|
|
127
|
+
): Loader<LoaderContext> | undefined {
|
|
128
|
+
return this.loaders[context.type];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private resetInternalLoader(contextType: PlaylistContextType): void {
|
|
132
|
+
if (this.loaders[contextType]) {
|
|
133
|
+
delete this.loaders[contextType];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Call `destroy` on all internal loader instances mapped (one per context type)
|
|
139
|
+
*/
|
|
140
|
+
private destroyInternalLoaders(): void {
|
|
141
|
+
for (const contextType in this.loaders) {
|
|
142
|
+
const loader = this.loaders[contextType];
|
|
143
|
+
if (loader) {
|
|
144
|
+
loader.destroy();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.resetInternalLoader(contextType as PlaylistContextType);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public destroy(): void {
|
|
152
|
+
this.variableList = null;
|
|
153
|
+
this.unregisterListeners();
|
|
154
|
+
this.destroyInternalLoaders();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private onManifestLoading(
|
|
158
|
+
event: Events.MANIFEST_LOADING,
|
|
159
|
+
data: ManifestLoadingData,
|
|
160
|
+
) {
|
|
161
|
+
const { url } = data;
|
|
162
|
+
this.variableList = null;
|
|
163
|
+
this.load({
|
|
164
|
+
id: null,
|
|
165
|
+
level: 0,
|
|
166
|
+
responseType: 'text',
|
|
167
|
+
type: PlaylistContextType.MANIFEST,
|
|
168
|
+
url,
|
|
169
|
+
deliveryDirectives: null,
|
|
170
|
+
levelOrTrack: null,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private onLevelLoading(event: Events.LEVEL_LOADING, data: LevelLoadingData) {
|
|
175
|
+
const { id, level, pathwayId, url, deliveryDirectives, levelInfo } = data;
|
|
176
|
+
this.load({
|
|
177
|
+
id,
|
|
178
|
+
level,
|
|
179
|
+
pathwayId,
|
|
180
|
+
responseType: 'text',
|
|
181
|
+
type: PlaylistContextType.LEVEL,
|
|
182
|
+
url,
|
|
183
|
+
deliveryDirectives,
|
|
184
|
+
levelOrTrack: levelInfo,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private onAudioTrackLoading(
|
|
189
|
+
event: Events.AUDIO_TRACK_LOADING,
|
|
190
|
+
data: TrackLoadingData,
|
|
191
|
+
) {
|
|
192
|
+
const { id, groupId, url, deliveryDirectives, track } = data;
|
|
193
|
+
this.load({
|
|
194
|
+
id,
|
|
195
|
+
groupId,
|
|
196
|
+
level: null,
|
|
197
|
+
responseType: 'text',
|
|
198
|
+
type: PlaylistContextType.AUDIO_TRACK,
|
|
199
|
+
url,
|
|
200
|
+
deliveryDirectives,
|
|
201
|
+
levelOrTrack: track,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private onSubtitleTrackLoading(
|
|
206
|
+
event: Events.SUBTITLE_TRACK_LOADING,
|
|
207
|
+
data: TrackLoadingData,
|
|
208
|
+
) {
|
|
209
|
+
const { id, groupId, url, deliveryDirectives, track } = data;
|
|
210
|
+
this.load({
|
|
211
|
+
id,
|
|
212
|
+
groupId,
|
|
213
|
+
level: null,
|
|
214
|
+
responseType: 'text',
|
|
215
|
+
type: PlaylistContextType.SUBTITLE_TRACK,
|
|
216
|
+
url,
|
|
217
|
+
deliveryDirectives,
|
|
218
|
+
levelOrTrack: track,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private onLevelsUpdated(
|
|
223
|
+
event: Events.LEVELS_UPDATED,
|
|
224
|
+
data: LevelsUpdatedData,
|
|
225
|
+
) {
|
|
226
|
+
// abort and delete loader of removed levels
|
|
227
|
+
const loader = this.loaders[PlaylistContextType.LEVEL];
|
|
228
|
+
if (loader) {
|
|
229
|
+
const context = loader.context;
|
|
230
|
+
if (
|
|
231
|
+
context &&
|
|
232
|
+
!data.levels.some(
|
|
233
|
+
(lvl) => lvl === (context as PlaylistLoaderContext).levelOrTrack,
|
|
234
|
+
)
|
|
235
|
+
) {
|
|
236
|
+
loader.abort();
|
|
237
|
+
delete this.loaders[PlaylistContextType.LEVEL];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private load(context: PlaylistLoaderContext): void {
|
|
243
|
+
const config = this.hls.config;
|
|
244
|
+
|
|
245
|
+
// logger.debug(`[playlist-loader]: Loading playlist of type ${context.type}, level: ${context.level}, id: ${context.id}`);
|
|
246
|
+
|
|
247
|
+
// Check if a loader for this context already exists
|
|
248
|
+
let loader = this.getInternalLoader(context);
|
|
249
|
+
if (loader) {
|
|
250
|
+
const logger = this.hls.logger;
|
|
251
|
+
const loaderContext = loader.context as PlaylistLoaderContext;
|
|
252
|
+
if (
|
|
253
|
+
loaderContext &&
|
|
254
|
+
loaderContext.levelOrTrack === context.levelOrTrack &&
|
|
255
|
+
(loaderContext.url === context.url ||
|
|
256
|
+
(loaderContext.deliveryDirectives && !context.deliveryDirectives))
|
|
257
|
+
) {
|
|
258
|
+
// same URL can't overlap, or wait for blocking request
|
|
259
|
+
if (loaderContext.url === context.url) {
|
|
260
|
+
logger.log(
|
|
261
|
+
`[playlist-loader]: ignore ${context.url} ongoing request`,
|
|
262
|
+
);
|
|
263
|
+
} else {
|
|
264
|
+
logger.log(
|
|
265
|
+
`[playlist-loader]: ignore ${context.url} in favor of ${loaderContext.url}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
logger.log(
|
|
271
|
+
`[playlist-loader]: aborting previous loader for type: ${context.type}`,
|
|
272
|
+
);
|
|
273
|
+
loader.abort();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// apply different configs for retries depending on
|
|
277
|
+
// context (manifest, level, audio/subs playlist)
|
|
278
|
+
let loadPolicy: LoaderConfig;
|
|
279
|
+
if (context.type === PlaylistContextType.MANIFEST) {
|
|
280
|
+
loadPolicy = config.manifestLoadPolicy.default;
|
|
281
|
+
} else {
|
|
282
|
+
loadPolicy = Object.assign({}, config.playlistLoadPolicy.default, {
|
|
283
|
+
timeoutRetry: null,
|
|
284
|
+
errorRetry: null,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
loader = this.createInternalLoader(context);
|
|
288
|
+
|
|
289
|
+
// Override level/track timeout for LL-HLS requests
|
|
290
|
+
// (the default of 10000ms is counter productive to blocking playlist reload requests)
|
|
291
|
+
if (Number.isFinite(context.deliveryDirectives?.part)) {
|
|
292
|
+
let levelDetails: LevelDetails | undefined;
|
|
293
|
+
if (
|
|
294
|
+
context.type === PlaylistContextType.LEVEL &&
|
|
295
|
+
context.level !== null
|
|
296
|
+
) {
|
|
297
|
+
levelDetails = this.hls.levels[context.level].details;
|
|
298
|
+
} else if (
|
|
299
|
+
context.type === PlaylistContextType.AUDIO_TRACK &&
|
|
300
|
+
context.id !== null
|
|
301
|
+
) {
|
|
302
|
+
levelDetails = this.hls.audioTracks[context.id].details;
|
|
303
|
+
} else if (
|
|
304
|
+
context.type === PlaylistContextType.SUBTITLE_TRACK &&
|
|
305
|
+
context.id !== null
|
|
306
|
+
) {
|
|
307
|
+
levelDetails = this.hls.subtitleTracks[context.id].details;
|
|
308
|
+
}
|
|
309
|
+
if (levelDetails) {
|
|
310
|
+
const partTarget = levelDetails.partTarget;
|
|
311
|
+
const targetDuration = levelDetails.targetduration;
|
|
312
|
+
if (partTarget && targetDuration) {
|
|
313
|
+
const maxLowLatencyPlaylistRefresh =
|
|
314
|
+
Math.max(partTarget * 3, targetDuration * 0.8) * 1000;
|
|
315
|
+
loadPolicy = Object.assign({}, loadPolicy, {
|
|
316
|
+
maxTimeToFirstByteMs: Math.min(
|
|
317
|
+
maxLowLatencyPlaylistRefresh,
|
|
318
|
+
loadPolicy.maxTimeToFirstByteMs,
|
|
319
|
+
),
|
|
320
|
+
maxLoadTimeMs: Math.min(
|
|
321
|
+
maxLowLatencyPlaylistRefresh,
|
|
322
|
+
loadPolicy.maxTimeToFirstByteMs,
|
|
323
|
+
),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const legacyRetryCompatibility: RetryConfig | Record<string, void> =
|
|
330
|
+
loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
|
|
331
|
+
const loaderConfig: LoaderConfiguration = {
|
|
332
|
+
loadPolicy,
|
|
333
|
+
timeout: loadPolicy.maxLoadTimeMs,
|
|
334
|
+
maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
|
|
335
|
+
retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
|
|
336
|
+
maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const loaderCallbacks: LoaderCallbacks<PlaylistLoaderContext> = {
|
|
340
|
+
onSuccess: (response, stats, context, networkDetails) => {
|
|
341
|
+
const loader = this.getInternalLoader(context) as
|
|
342
|
+
| Loader<PlaylistLoaderContext>
|
|
343
|
+
| undefined;
|
|
344
|
+
this.resetInternalLoader(context.type);
|
|
345
|
+
|
|
346
|
+
const string = response.data as string;
|
|
347
|
+
|
|
348
|
+
stats.parsing.start = performance.now();
|
|
349
|
+
if (
|
|
350
|
+
M3U8Parser.isMediaPlaylist(string) ||
|
|
351
|
+
context.type !== PlaylistContextType.MANIFEST
|
|
352
|
+
) {
|
|
353
|
+
this.handleTrackOrLevelPlaylist(
|
|
354
|
+
response,
|
|
355
|
+
stats,
|
|
356
|
+
context,
|
|
357
|
+
networkDetails || null,
|
|
358
|
+
loader,
|
|
359
|
+
);
|
|
360
|
+
} else {
|
|
361
|
+
this.handleMasterPlaylist(response, stats, context, networkDetails);
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
onError: (response, context, networkDetails, stats) => {
|
|
365
|
+
this.handleNetworkError(
|
|
366
|
+
context,
|
|
367
|
+
networkDetails,
|
|
368
|
+
false,
|
|
369
|
+
response,
|
|
370
|
+
stats,
|
|
371
|
+
);
|
|
372
|
+
},
|
|
373
|
+
onTimeout: (stats, context, networkDetails) => {
|
|
374
|
+
this.handleNetworkError(
|
|
375
|
+
context,
|
|
376
|
+
networkDetails,
|
|
377
|
+
true,
|
|
378
|
+
undefined,
|
|
379
|
+
stats,
|
|
380
|
+
);
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// logger.debug(`[playlist-loader]: Calling internal loader delegate for URL: ${context.url}`);
|
|
385
|
+
|
|
386
|
+
loader.load(context, loaderConfig, loaderCallbacks);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private checkAutostartLoad() {
|
|
390
|
+
if (!this.hls) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const {
|
|
394
|
+
config: { autoStartLoad, startPosition },
|
|
395
|
+
forceStartLoad,
|
|
396
|
+
} = this.hls;
|
|
397
|
+
if (autoStartLoad || forceStartLoad) {
|
|
398
|
+
this.hls.logger.log(
|
|
399
|
+
`${autoStartLoad ? 'auto' : 'force'} startLoad with configured startPosition ${startPosition}`,
|
|
400
|
+
);
|
|
401
|
+
this.hls.startLoad(startPosition);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private handleMasterPlaylist(
|
|
406
|
+
response: LoaderResponse,
|
|
407
|
+
stats: LoaderStats,
|
|
408
|
+
context: PlaylistLoaderContext,
|
|
409
|
+
networkDetails: NullableNetworkDetails,
|
|
410
|
+
): void {
|
|
411
|
+
const hls = this.hls;
|
|
412
|
+
const string = response.data as string;
|
|
413
|
+
|
|
414
|
+
const url = getResponseUrl(response, context);
|
|
415
|
+
|
|
416
|
+
const parsedResult = M3U8Parser.parseMasterPlaylist(string, url);
|
|
417
|
+
|
|
418
|
+
if (parsedResult.playlistParsingError) {
|
|
419
|
+
stats.parsing.end = performance.now();
|
|
420
|
+
this.handleManifestParsingError(
|
|
421
|
+
response,
|
|
422
|
+
context,
|
|
423
|
+
parsedResult.playlistParsingError,
|
|
424
|
+
networkDetails,
|
|
425
|
+
stats,
|
|
426
|
+
);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const {
|
|
431
|
+
contentSteering,
|
|
432
|
+
levels,
|
|
433
|
+
sessionData,
|
|
434
|
+
sessionKeys,
|
|
435
|
+
startTimeOffset,
|
|
436
|
+
variableList,
|
|
437
|
+
} = parsedResult;
|
|
438
|
+
|
|
439
|
+
this.variableList = variableList;
|
|
440
|
+
|
|
441
|
+
// Treat unknown codec as audio or video codec based on passing `isTypeSupported` check
|
|
442
|
+
// (allows for playback of any supported codec even if not indexed in utils/codecs)
|
|
443
|
+
levels.forEach((levelParsed: LevelParsed) => {
|
|
444
|
+
const { unknownCodecs } = levelParsed;
|
|
445
|
+
if (unknownCodecs) {
|
|
446
|
+
const { preferManagedMediaSource } = this.hls.config;
|
|
447
|
+
let { audioCodec, videoCodec } = levelParsed;
|
|
448
|
+
for (let i = unknownCodecs.length; i--; ) {
|
|
449
|
+
const unknownCodec = unknownCodecs[i];
|
|
450
|
+
if (
|
|
451
|
+
areCodecsMediaSourceSupported(
|
|
452
|
+
unknownCodec,
|
|
453
|
+
'audio',
|
|
454
|
+
preferManagedMediaSource,
|
|
455
|
+
)
|
|
456
|
+
) {
|
|
457
|
+
levelParsed.audioCodec = audioCodec = audioCodec
|
|
458
|
+
? `${audioCodec},${unknownCodec}`
|
|
459
|
+
: unknownCodec;
|
|
460
|
+
sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2;
|
|
461
|
+
unknownCodecs.splice(i, 1);
|
|
462
|
+
} else if (
|
|
463
|
+
areCodecsMediaSourceSupported(
|
|
464
|
+
unknownCodec,
|
|
465
|
+
'video',
|
|
466
|
+
preferManagedMediaSource,
|
|
467
|
+
)
|
|
468
|
+
) {
|
|
469
|
+
levelParsed.videoCodec = videoCodec = videoCodec
|
|
470
|
+
? `${videoCodec},${unknownCodec}`
|
|
471
|
+
: unknownCodec;
|
|
472
|
+
sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2;
|
|
473
|
+
unknownCodecs.splice(i, 1);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const {
|
|
480
|
+
AUDIO: audioTracks = [],
|
|
481
|
+
SUBTITLES: subtitles,
|
|
482
|
+
'CLOSED-CAPTIONS': captions,
|
|
483
|
+
} = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult);
|
|
484
|
+
|
|
485
|
+
if (audioTracks.length) {
|
|
486
|
+
// check if we have found an audio track embedded in main playlist (audio track without URI attribute)
|
|
487
|
+
const embeddedAudioFound: boolean = audioTracks.some(
|
|
488
|
+
(audioTrack) => !audioTrack.url,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
// if no embedded audio track defined, but audio codec signaled in quality level,
|
|
492
|
+
// we need to signal this main audio track this could happen with playlists with
|
|
493
|
+
// alt audio rendition in which quality levels (main)
|
|
494
|
+
// contains both audio+video. but with mixed audio track not signaled
|
|
495
|
+
if (
|
|
496
|
+
!embeddedAudioFound &&
|
|
497
|
+
levels[0].audioCodec &&
|
|
498
|
+
!levels[0].attrs.AUDIO
|
|
499
|
+
) {
|
|
500
|
+
this.hls.logger.log(
|
|
501
|
+
'[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one',
|
|
502
|
+
);
|
|
503
|
+
audioTracks.unshift({
|
|
504
|
+
type: 'main',
|
|
505
|
+
name: 'main',
|
|
506
|
+
groupId: 'main',
|
|
507
|
+
default: false,
|
|
508
|
+
autoselect: false,
|
|
509
|
+
forced: false,
|
|
510
|
+
id: -1,
|
|
511
|
+
attrs: new AttrList({}) as MediaAttributes,
|
|
512
|
+
bitrate: 0,
|
|
513
|
+
url: '',
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
hls.trigger(Events.MANIFEST_LOADED, {
|
|
519
|
+
levels,
|
|
520
|
+
audioTracks,
|
|
521
|
+
subtitles,
|
|
522
|
+
captions,
|
|
523
|
+
contentSteering,
|
|
524
|
+
url,
|
|
525
|
+
stats,
|
|
526
|
+
networkDetails,
|
|
527
|
+
sessionData,
|
|
528
|
+
sessionKeys,
|
|
529
|
+
startTimeOffset,
|
|
530
|
+
variableList,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private handleTrackOrLevelPlaylist(
|
|
535
|
+
response: LoaderResponse,
|
|
536
|
+
stats: LoaderStats,
|
|
537
|
+
context: PlaylistLoaderContext,
|
|
538
|
+
networkDetails: NullableNetworkDetails,
|
|
539
|
+
loader: Loader<PlaylistLoaderContext> | undefined,
|
|
540
|
+
): void {
|
|
541
|
+
const hls = this.hls;
|
|
542
|
+
const { id, level, type } = context;
|
|
543
|
+
|
|
544
|
+
const url = getResponseUrl(response, context);
|
|
545
|
+
const levelId = Number.isFinite(level as number)
|
|
546
|
+
? (level as number)
|
|
547
|
+
: Number.isFinite(id as number)
|
|
548
|
+
? (id as number)
|
|
549
|
+
: 0;
|
|
550
|
+
const levelType = mapContextToLevelType(context);
|
|
551
|
+
// 仅在启用算法数据加载时跳过算法分片
|
|
552
|
+
const algoSegmentPattern = this.hls.config.algoDataEnabled
|
|
553
|
+
? this.hls.config.algoSegmentPattern
|
|
554
|
+
: null;
|
|
555
|
+
const levelDetails = M3U8Parser.parseLevelPlaylist(
|
|
556
|
+
response.data as string,
|
|
557
|
+
url,
|
|
558
|
+
levelId,
|
|
559
|
+
levelType,
|
|
560
|
+
0,
|
|
561
|
+
this.variableList,
|
|
562
|
+
algoSegmentPattern,
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// We have done our first request (Manifest-type) and receive
|
|
566
|
+
// not a master playlist but a chunk-list (track/level)
|
|
567
|
+
// We fire the manifest-loaded event anyway with the parsed level-details
|
|
568
|
+
// by creating a single-level structure for it.
|
|
569
|
+
if (type === PlaylistContextType.MANIFEST) {
|
|
570
|
+
const singleLevel: LevelParsed = {
|
|
571
|
+
attrs: new AttrList({}),
|
|
572
|
+
bitrate: 0,
|
|
573
|
+
details: levelDetails,
|
|
574
|
+
name: '',
|
|
575
|
+
url,
|
|
576
|
+
};
|
|
577
|
+
levelDetails.requestScheduled =
|
|
578
|
+
stats.loading.start + computeReloadInterval(levelDetails, 0);
|
|
579
|
+
|
|
580
|
+
hls.trigger(Events.MANIFEST_LOADED, {
|
|
581
|
+
levels: [singleLevel],
|
|
582
|
+
audioTracks: [],
|
|
583
|
+
url,
|
|
584
|
+
stats,
|
|
585
|
+
networkDetails,
|
|
586
|
+
sessionData: null,
|
|
587
|
+
sessionKeys: null,
|
|
588
|
+
contentSteering: null,
|
|
589
|
+
startTimeOffset: null,
|
|
590
|
+
variableList: null,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// save parsing time
|
|
595
|
+
stats.parsing.end = performance.now();
|
|
596
|
+
|
|
597
|
+
// extend the context with the new levelDetails property
|
|
598
|
+
context.levelDetails = levelDetails;
|
|
599
|
+
|
|
600
|
+
this.handlePlaylistLoaded(
|
|
601
|
+
levelDetails,
|
|
602
|
+
response,
|
|
603
|
+
stats,
|
|
604
|
+
context,
|
|
605
|
+
networkDetails,
|
|
606
|
+
loader,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private handleManifestParsingError(
|
|
611
|
+
response: LoaderResponse,
|
|
612
|
+
context: PlaylistLoaderContext,
|
|
613
|
+
error: Error,
|
|
614
|
+
networkDetails: NullableNetworkDetails,
|
|
615
|
+
stats: LoaderStats,
|
|
616
|
+
): void {
|
|
617
|
+
this.hls.trigger(Events.ERROR, {
|
|
618
|
+
type: ErrorTypes.NETWORK_ERROR,
|
|
619
|
+
details: ErrorDetails.MANIFEST_PARSING_ERROR,
|
|
620
|
+
fatal: context.type === PlaylistContextType.MANIFEST,
|
|
621
|
+
url: response.url,
|
|
622
|
+
err: error,
|
|
623
|
+
error,
|
|
624
|
+
reason: error.message,
|
|
625
|
+
response,
|
|
626
|
+
context,
|
|
627
|
+
networkDetails,
|
|
628
|
+
stats,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private handleNetworkError(
|
|
633
|
+
context: PlaylistLoaderContext,
|
|
634
|
+
networkDetails: NullableNetworkDetails,
|
|
635
|
+
timeout = false,
|
|
636
|
+
response: { code: number; text: string } | undefined,
|
|
637
|
+
stats: LoaderStats,
|
|
638
|
+
): void {
|
|
639
|
+
let message = `A network ${
|
|
640
|
+
timeout
|
|
641
|
+
? 'timeout'
|
|
642
|
+
: 'error' + (response ? ' (status ' + response.code + ')' : '')
|
|
643
|
+
} occurred while loading ${context.type}`;
|
|
644
|
+
if (context.type === PlaylistContextType.LEVEL) {
|
|
645
|
+
message += `: ${context.level} id: ${context.id}`;
|
|
646
|
+
} else if (
|
|
647
|
+
context.type === PlaylistContextType.AUDIO_TRACK ||
|
|
648
|
+
context.type === PlaylistContextType.SUBTITLE_TRACK
|
|
649
|
+
) {
|
|
650
|
+
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
|
651
|
+
}
|
|
652
|
+
const error = new Error(message);
|
|
653
|
+
this.hls.logger.warn(`[playlist-loader]: ${message}`);
|
|
654
|
+
let details = ErrorDetails.UNKNOWN;
|
|
655
|
+
let fatal = false;
|
|
656
|
+
|
|
657
|
+
const loader = this.getInternalLoader(context);
|
|
658
|
+
|
|
659
|
+
switch (context.type) {
|
|
660
|
+
case PlaylistContextType.MANIFEST:
|
|
661
|
+
details = timeout
|
|
662
|
+
? ErrorDetails.MANIFEST_LOAD_TIMEOUT
|
|
663
|
+
: ErrorDetails.MANIFEST_LOAD_ERROR;
|
|
664
|
+
fatal = true;
|
|
665
|
+
break;
|
|
666
|
+
case PlaylistContextType.LEVEL:
|
|
667
|
+
details = timeout
|
|
668
|
+
? ErrorDetails.LEVEL_LOAD_TIMEOUT
|
|
669
|
+
: ErrorDetails.LEVEL_LOAD_ERROR;
|
|
670
|
+
fatal = false;
|
|
671
|
+
break;
|
|
672
|
+
case PlaylistContextType.AUDIO_TRACK:
|
|
673
|
+
details = timeout
|
|
674
|
+
? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT
|
|
675
|
+
: ErrorDetails.AUDIO_TRACK_LOAD_ERROR;
|
|
676
|
+
fatal = false;
|
|
677
|
+
break;
|
|
678
|
+
case PlaylistContextType.SUBTITLE_TRACK:
|
|
679
|
+
details = timeout
|
|
680
|
+
? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT
|
|
681
|
+
: ErrorDetails.SUBTITLE_LOAD_ERROR;
|
|
682
|
+
fatal = false;
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (loader) {
|
|
687
|
+
this.resetInternalLoader(context.type);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const errorData: ErrorData = {
|
|
691
|
+
type: ErrorTypes.NETWORK_ERROR,
|
|
692
|
+
details,
|
|
693
|
+
fatal,
|
|
694
|
+
url: context.url,
|
|
695
|
+
loader,
|
|
696
|
+
context,
|
|
697
|
+
error,
|
|
698
|
+
networkDetails,
|
|
699
|
+
stats,
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
if (response) {
|
|
703
|
+
let url = context.url;
|
|
704
|
+
if (networkDetails && 'url' in networkDetails) {
|
|
705
|
+
url = networkDetails.url;
|
|
706
|
+
}
|
|
707
|
+
errorData.response = { url, data: undefined as any, ...response };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
this.hls.trigger(Events.ERROR, errorData);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private handlePlaylistLoaded(
|
|
714
|
+
levelDetails: LevelDetails,
|
|
715
|
+
response: LoaderResponse,
|
|
716
|
+
stats: LoaderStats,
|
|
717
|
+
context: PlaylistLoaderContext,
|
|
718
|
+
networkDetails: NullableNetworkDetails,
|
|
719
|
+
loader: Loader<PlaylistLoaderContext> | undefined,
|
|
720
|
+
): void {
|
|
721
|
+
const hls = this.hls;
|
|
722
|
+
const { type, level, levelOrTrack, id, groupId, deliveryDirectives } =
|
|
723
|
+
context;
|
|
724
|
+
const url = getResponseUrl(response, context);
|
|
725
|
+
const parent = mapContextToLevelType(context);
|
|
726
|
+
let levelIndex =
|
|
727
|
+
typeof context.level === 'number' && parent === PlaylistLevelType.MAIN
|
|
728
|
+
? (level as number)
|
|
729
|
+
: undefined;
|
|
730
|
+
const error = levelDetails.playlistParsingError;
|
|
731
|
+
if (error) {
|
|
732
|
+
this.hls.logger.warn(`${error} ${levelDetails.url}`);
|
|
733
|
+
if (!hls.config.ignorePlaylistParsingErrors) {
|
|
734
|
+
hls.trigger(Events.ERROR, {
|
|
735
|
+
type: ErrorTypes.NETWORK_ERROR,
|
|
736
|
+
details: ErrorDetails.LEVEL_PARSING_ERROR,
|
|
737
|
+
fatal: false,
|
|
738
|
+
url,
|
|
739
|
+
error,
|
|
740
|
+
reason: error.message,
|
|
741
|
+
response,
|
|
742
|
+
context,
|
|
743
|
+
level: levelIndex,
|
|
744
|
+
parent,
|
|
745
|
+
networkDetails,
|
|
746
|
+
stats,
|
|
747
|
+
});
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
levelDetails.playlistParsingError = null;
|
|
751
|
+
}
|
|
752
|
+
if (!levelDetails.fragments.length) {
|
|
753
|
+
const error = (levelDetails.playlistParsingError = new Error(
|
|
754
|
+
'No Segments found in Playlist',
|
|
755
|
+
));
|
|
756
|
+
hls.trigger(Events.ERROR, {
|
|
757
|
+
type: ErrorTypes.NETWORK_ERROR,
|
|
758
|
+
details: ErrorDetails.LEVEL_EMPTY_ERROR,
|
|
759
|
+
fatal: false,
|
|
760
|
+
url,
|
|
761
|
+
error,
|
|
762
|
+
reason: error.message,
|
|
763
|
+
response,
|
|
764
|
+
context,
|
|
765
|
+
level: levelIndex,
|
|
766
|
+
parent,
|
|
767
|
+
networkDetails,
|
|
768
|
+
stats,
|
|
769
|
+
});
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (levelDetails.live && loader) {
|
|
774
|
+
if (loader.getCacheAge) {
|
|
775
|
+
levelDetails.ageHeader = loader.getCacheAge() || 0;
|
|
776
|
+
}
|
|
777
|
+
if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) {
|
|
778
|
+
levelDetails.ageHeader = 0;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
switch (type) {
|
|
783
|
+
case PlaylistContextType.MANIFEST:
|
|
784
|
+
case PlaylistContextType.LEVEL:
|
|
785
|
+
if (levelIndex) {
|
|
786
|
+
if (!levelOrTrack) {
|
|
787
|
+
// fall-through to hls.levels[0]
|
|
788
|
+
levelIndex = 0;
|
|
789
|
+
} else {
|
|
790
|
+
if (levelOrTrack !== hls.levels[levelIndex]) {
|
|
791
|
+
// correct levelIndex when lower levels were removed from hls.levels
|
|
792
|
+
const updatedIndex = hls.levels.indexOf(levelOrTrack as Level);
|
|
793
|
+
if (updatedIndex > -1) {
|
|
794
|
+
levelIndex = updatedIndex;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
hls.trigger(Events.LEVEL_LOADED, {
|
|
800
|
+
details: levelDetails,
|
|
801
|
+
levelInfo: (levelOrTrack as Level | null) || hls.levels[0],
|
|
802
|
+
level: levelIndex || 0,
|
|
803
|
+
id: id || 0,
|
|
804
|
+
stats,
|
|
805
|
+
networkDetails,
|
|
806
|
+
deliveryDirectives,
|
|
807
|
+
withoutMultiVariant: type === PlaylistContextType.MANIFEST,
|
|
808
|
+
context,
|
|
809
|
+
});
|
|
810
|
+
break;
|
|
811
|
+
case PlaylistContextType.AUDIO_TRACK:
|
|
812
|
+
hls.trigger(Events.AUDIO_TRACK_LOADED, {
|
|
813
|
+
details: levelDetails,
|
|
814
|
+
track: levelOrTrack as MediaPlaylist,
|
|
815
|
+
id: id || 0,
|
|
816
|
+
groupId: groupId || '',
|
|
817
|
+
stats,
|
|
818
|
+
networkDetails,
|
|
819
|
+
deliveryDirectives,
|
|
820
|
+
context,
|
|
821
|
+
});
|
|
822
|
+
break;
|
|
823
|
+
case PlaylistContextType.SUBTITLE_TRACK:
|
|
824
|
+
hls.trigger(Events.SUBTITLE_TRACK_LOADED, {
|
|
825
|
+
details: levelDetails,
|
|
826
|
+
track: levelOrTrack as MediaPlaylist,
|
|
827
|
+
id: id || 0,
|
|
828
|
+
groupId: groupId || '',
|
|
829
|
+
stats,
|
|
830
|
+
networkDetails,
|
|
831
|
+
deliveryDirectives,
|
|
832
|
+
context,
|
|
833
|
+
});
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export default PlaylistLoader;
|