hls.js 1.5.13 → 1.5.14-0.canary.10417
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/README.md +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +2569 -1639
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +3572 -2017
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +38 -38
- package/src/config.ts +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +8 -16
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
package/src/events.ts
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
import {
|
1
|
+
import type {
|
2
2
|
ManifestLoadedData,
|
3
3
|
ManifestLoadingData,
|
4
4
|
MediaAttachedData,
|
5
5
|
MediaAttachingData,
|
6
|
+
MediaEndedData,
|
6
7
|
LevelLoadingData,
|
7
8
|
LevelLoadedData,
|
8
9
|
ManifestParsedData,
|
@@ -60,6 +61,8 @@ export enum Events {
|
|
60
61
|
MEDIA_DETACHING = 'hlsMediaDetaching',
|
61
62
|
// Fired when MediaSource has been detached from media element
|
62
63
|
MEDIA_DETACHED = 'hlsMediaDetached',
|
64
|
+
// Fired when HTMLMediaElement dispatches "ended" event, or stalls at end of VOD program
|
65
|
+
MEDIA_ENDED = 'hlsMediaEnded',
|
63
66
|
// Fired when the buffer is going to be reset
|
64
67
|
BUFFER_RESET = 'hlsBufferReset',
|
65
68
|
// Fired when we know about the codecs that we need buffers for to push into - data: {tracks : { container, codec, levelCodec, initSegment, metadata }}
|
@@ -184,6 +187,10 @@ export interface HlsListeners {
|
|
184
187
|
) => void;
|
185
188
|
[Events.MEDIA_DETACHING]: (event: Events.MEDIA_DETACHING) => void;
|
186
189
|
[Events.MEDIA_DETACHED]: (event: Events.MEDIA_DETACHED) => void;
|
190
|
+
[Events.MEDIA_ENDED]: (
|
191
|
+
event: Events.MEDIA_ENDED,
|
192
|
+
data: MediaEndedData,
|
193
|
+
) => void;
|
187
194
|
[Events.BUFFER_RESET]: (event: Events.BUFFER_RESET) => void;
|
188
195
|
[Events.BUFFER_CODECS]: (
|
189
196
|
event: Events.BUFFER_CODECS,
|
package/src/exports-named.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import Hls from './hls';
|
2
2
|
import { Events } from './events';
|
3
3
|
import { ErrorTypes, ErrorDetails } from './errors';
|
4
|
-
import { Level } from './types/level';
|
4
|
+
import type { Level } from './types/level';
|
5
5
|
import AbrController from './controller/abr-controller';
|
6
6
|
import AudioTrackController from './controller/audio-track-controller';
|
7
7
|
import AudioStreamController from './controller/audio-stream-controller';
|
package/src/hls.ts
CHANGED
@@ -8,7 +8,7 @@ import KeyLoader from './loader/key-loader';
|
|
8
8
|
import StreamController from './controller/stream-controller';
|
9
9
|
import { isMSESupported, isSupported } from './is-supported';
|
10
10
|
import { getMediaSource } from './utils/mediasource-helper';
|
11
|
-
import {
|
11
|
+
import { enableLogs, type ILogger } from './utils/logger';
|
12
12
|
import { enableStreamingMode, hlsDefaultConfig, mergeConfig } from './config';
|
13
13
|
import { EventEmitter } from 'eventemitter3';
|
14
14
|
import { Events } from './events';
|
@@ -59,9 +59,13 @@ export default class Hls implements HlsEventEmitter {
|
|
59
59
|
*/
|
60
60
|
public readonly userConfig: Partial<HlsConfig>;
|
61
61
|
|
62
|
+
/**
|
63
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
64
|
+
*/
|
65
|
+
public readonly logger: ILogger;
|
66
|
+
|
62
67
|
private coreComponents: ComponentAPI[];
|
63
68
|
private networkControllers: NetworkComponentAPI[];
|
64
|
-
private started: boolean = false;
|
65
69
|
private _emitter: HlsEventEmitter = new EventEmitter();
|
66
70
|
private _autoLevelCapping: number = -1;
|
67
71
|
private _maxHdcpLevel: HdcpLevel = null;
|
@@ -76,7 +80,7 @@ export default class Hls implements HlsEventEmitter {
|
|
76
80
|
private emeController: EMEController;
|
77
81
|
private cmcdController: CMCDController;
|
78
82
|
private _media: HTMLMediaElement | null = null;
|
79
|
-
private
|
83
|
+
private _url: string | null = null;
|
80
84
|
private triggeringException?: boolean;
|
81
85
|
|
82
86
|
/**
|
@@ -142,12 +146,19 @@ export default class Hls implements HlsEventEmitter {
|
|
142
146
|
* @param userConfig - Configuration options applied over `Hls.DefaultConfig`
|
143
147
|
*/
|
144
148
|
constructor(userConfig: Partial<HlsConfig> = {}) {
|
145
|
-
|
146
|
-
|
149
|
+
const logger = (this.logger = enableLogs(
|
150
|
+
userConfig.debug || false,
|
151
|
+
'Hls instance',
|
152
|
+
));
|
153
|
+
const config = (this.config = mergeConfig(
|
154
|
+
Hls.DefaultConfig,
|
155
|
+
userConfig,
|
156
|
+
logger,
|
157
|
+
));
|
147
158
|
this.userConfig = userConfig;
|
148
159
|
|
149
160
|
if (config.progressive) {
|
150
|
-
enableStreamingMode(config);
|
161
|
+
enableStreamingMode(config, logger);
|
151
162
|
}
|
152
163
|
|
153
164
|
// core controllers and network loaders
|
@@ -160,8 +171,10 @@ export default class Hls implements HlsEventEmitter {
|
|
160
171
|
} = config;
|
161
172
|
const errorController = new ConfigErrorController(this);
|
162
173
|
const abrController = (this.abrController = new ConfigAbrController(this));
|
174
|
+
// FragmentTracker must be defined before StreamController because the order of event handling is important
|
175
|
+
const fragmentTracker = new FragmentTracker(this);
|
163
176
|
const bufferController = (this.bufferController =
|
164
|
-
new ConfigBufferController(this));
|
177
|
+
new ConfigBufferController(this, fragmentTracker));
|
165
178
|
const capLevelController = (this.capLevelController =
|
166
179
|
new ConfigCapLevelController(this));
|
167
180
|
|
@@ -170,7 +183,7 @@ export default class Hls implements HlsEventEmitter {
|
|
170
183
|
const id3TrackController = new ID3TrackController(this);
|
171
184
|
|
172
185
|
const ConfigContentSteeringController = config.contentSteeringController;
|
173
|
-
//
|
186
|
+
// ContentSteeringController is defined before LevelController to receive Multivariant Playlist events first
|
174
187
|
const contentSteering = ConfigContentSteeringController
|
175
188
|
? new ConfigContentSteeringController(this)
|
176
189
|
: null;
|
@@ -178,8 +191,6 @@ export default class Hls implements HlsEventEmitter {
|
|
178
191
|
this,
|
179
192
|
contentSteering,
|
180
193
|
));
|
181
|
-
// FragmentTracker must be defined before StreamController because the order of event handling is important
|
182
|
-
const fragmentTracker = new FragmentTracker(this);
|
183
194
|
const keyLoader = new KeyLoader(this.config);
|
184
195
|
const streamController = (this.streamController = new StreamController(
|
185
196
|
this,
|
@@ -221,7 +232,7 @@ export default class Hls implements HlsEventEmitter {
|
|
221
232
|
new AudioStreamControllerClass(this, fragmentTracker, keyLoader),
|
222
233
|
);
|
223
234
|
}
|
224
|
-
// subtitleTrackController
|
235
|
+
// Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
|
225
236
|
this.subtitleTrackController = this.createController(
|
226
237
|
config.subtitleTrackController,
|
227
238
|
networkControllers,
|
@@ -320,7 +331,7 @@ export default class Hls implements HlsEventEmitter {
|
|
320
331
|
try {
|
321
332
|
return this.emit(event, event, eventObject);
|
322
333
|
} catch (error) {
|
323
|
-
logger.error(
|
334
|
+
this.logger.error(
|
324
335
|
'An internal error happened while handling event ' +
|
325
336
|
event +
|
326
337
|
'. Error message: "' +
|
@@ -354,12 +365,12 @@ export default class Hls implements HlsEventEmitter {
|
|
354
365
|
* Dispose of the instance
|
355
366
|
*/
|
356
367
|
destroy() {
|
357
|
-
logger.log('destroy');
|
368
|
+
this.logger.log('destroy');
|
358
369
|
this.trigger(Events.DESTROYING, undefined);
|
359
370
|
this.detachMedia();
|
360
371
|
this.removeAllListeners();
|
361
372
|
this._autoLevelCapping = -1;
|
362
|
-
this.
|
373
|
+
this._url = null;
|
363
374
|
|
364
375
|
this.networkControllers.forEach((component) => component.destroy());
|
365
376
|
this.networkControllers.length = 0;
|
@@ -377,7 +388,7 @@ export default class Hls implements HlsEventEmitter {
|
|
377
388
|
* Attaches Hls.js to a media element
|
378
389
|
*/
|
379
390
|
attachMedia(media: HTMLMediaElement) {
|
380
|
-
logger.log('attachMedia');
|
391
|
+
this.logger.log('attachMedia');
|
381
392
|
this._media = media;
|
382
393
|
this.trigger(Events.MEDIA_ATTACHING, { media: media });
|
383
394
|
}
|
@@ -386,7 +397,7 @@ export default class Hls implements HlsEventEmitter {
|
|
386
397
|
* Detach Hls.js from the media
|
387
398
|
*/
|
388
399
|
detachMedia() {
|
389
|
-
logger.log('detachMedia');
|
400
|
+
this.logger.log('detachMedia');
|
390
401
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
391
402
|
this._media = null;
|
392
403
|
}
|
@@ -397,8 +408,8 @@ export default class Hls implements HlsEventEmitter {
|
|
397
408
|
loadSource(url: string) {
|
398
409
|
this.stopLoad();
|
399
410
|
const media = this.media;
|
400
|
-
const loadedSource = this.
|
401
|
-
const loadingSource = (this.
|
411
|
+
const loadedSource = this._url;
|
412
|
+
const loadingSource = (this._url = buildAbsoluteURL(
|
402
413
|
self.location.href,
|
403
414
|
url,
|
404
415
|
{
|
@@ -407,7 +418,7 @@ export default class Hls implements HlsEventEmitter {
|
|
407
418
|
));
|
408
419
|
this._autoLevelCapping = -1;
|
409
420
|
this._maxHdcpLevel = null;
|
410
|
-
logger.log(`loadSource:${loadingSource}`);
|
421
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
411
422
|
if (
|
412
423
|
media &&
|
413
424
|
loadedSource &&
|
@@ -420,6 +431,13 @@ export default class Hls implements HlsEventEmitter {
|
|
420
431
|
this.trigger(Events.MANIFEST_LOADING, { url: url });
|
421
432
|
}
|
422
433
|
|
434
|
+
/**
|
435
|
+
* Gets the currently loaded URL
|
436
|
+
*/
|
437
|
+
public get url(): string | null {
|
438
|
+
return this._url;
|
439
|
+
}
|
440
|
+
|
423
441
|
/**
|
424
442
|
* Start loading data from the stream source.
|
425
443
|
* Depending on default config, client starts loading automatically when a source is set.
|
@@ -428,8 +446,7 @@ export default class Hls implements HlsEventEmitter {
|
|
428
446
|
* Defaults to -1 (None: starts from earliest point)
|
429
447
|
*/
|
430
448
|
startLoad(startPosition: number = -1) {
|
431
|
-
logger.log(`startLoad(${startPosition})`);
|
432
|
-
this.started = true;
|
449
|
+
this.logger.log(`startLoad(${startPosition})`);
|
433
450
|
this.networkControllers.forEach((controller) => {
|
434
451
|
controller.startLoad(startPosition);
|
435
452
|
});
|
@@ -439,34 +456,31 @@ export default class Hls implements HlsEventEmitter {
|
|
439
456
|
* Stop loading of any stream data.
|
440
457
|
*/
|
441
458
|
stopLoad() {
|
442
|
-
logger.log('stopLoad');
|
443
|
-
this.started = false;
|
459
|
+
this.logger.log('stopLoad');
|
444
460
|
this.networkControllers.forEach((controller) => {
|
445
461
|
controller.stopLoad();
|
446
462
|
});
|
447
463
|
}
|
448
464
|
|
449
465
|
/**
|
450
|
-
* Resumes stream controller segment loading
|
466
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
451
467
|
*/
|
452
468
|
resumeBuffering() {
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
});
|
459
|
-
}
|
469
|
+
this.networkControllers.forEach((controller) => {
|
470
|
+
if (controller.resumeBuffering) {
|
471
|
+
controller.resumeBuffering();
|
472
|
+
}
|
473
|
+
});
|
460
474
|
}
|
461
475
|
|
462
476
|
/**
|
463
|
-
*
|
477
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
464
478
|
* This allows for media buffering to be paused without interupting playlist loading.
|
465
479
|
*/
|
466
480
|
pauseBuffering() {
|
467
481
|
this.networkControllers.forEach((controller) => {
|
468
|
-
if (
|
469
|
-
controller.
|
482
|
+
if (controller.pauseBuffering) {
|
483
|
+
controller.pauseBuffering();
|
470
484
|
}
|
471
485
|
});
|
472
486
|
}
|
@@ -475,7 +489,7 @@ export default class Hls implements HlsEventEmitter {
|
|
475
489
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
476
490
|
*/
|
477
491
|
swapAudioCodec() {
|
478
|
-
logger.log('swapAudioCodec');
|
492
|
+
this.logger.log('swapAudioCodec');
|
479
493
|
this.streamController.swapAudioCodec();
|
480
494
|
}
|
481
495
|
|
@@ -486,7 +500,7 @@ export default class Hls implements HlsEventEmitter {
|
|
486
500
|
* Automatic recovery of media-errors by this process is configurable.
|
487
501
|
*/
|
488
502
|
recoverMediaError() {
|
489
|
-
logger.log('recoverMediaError');
|
503
|
+
this.logger.log('recoverMediaError');
|
490
504
|
const media = this._media;
|
491
505
|
this.detachMedia();
|
492
506
|
if (media) {
|
@@ -517,7 +531,7 @@ export default class Hls implements HlsEventEmitter {
|
|
517
531
|
* Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
|
518
532
|
*/
|
519
533
|
set currentLevel(newLevel: number) {
|
520
|
-
logger.log(`set currentLevel:${newLevel}`);
|
534
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
521
535
|
this.levelController.manualLevel = newLevel;
|
522
536
|
this.streamController.immediateLevelSwitch();
|
523
537
|
}
|
@@ -536,7 +550,7 @@ export default class Hls implements HlsEventEmitter {
|
|
536
550
|
* @param newLevel - Pass -1 for automatic level selection
|
537
551
|
*/
|
538
552
|
set nextLevel(newLevel: number) {
|
539
|
-
logger.log(`set nextLevel:${newLevel}`);
|
553
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
540
554
|
this.levelController.manualLevel = newLevel;
|
541
555
|
this.streamController.nextLevelSwitch();
|
542
556
|
}
|
@@ -555,7 +569,7 @@ export default class Hls implements HlsEventEmitter {
|
|
555
569
|
* @param newLevel - Pass -1 for automatic level selection
|
556
570
|
*/
|
557
571
|
set loadLevel(newLevel: number) {
|
558
|
-
logger.log(`set loadLevel:${newLevel}`);
|
572
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
559
573
|
this.levelController.manualLevel = newLevel;
|
560
574
|
}
|
561
575
|
|
@@ -586,7 +600,7 @@ export default class Hls implements HlsEventEmitter {
|
|
586
600
|
* Sets "first-level", see getter.
|
587
601
|
*/
|
588
602
|
set firstLevel(newLevel: number) {
|
589
|
-
logger.log(`set firstLevel:${newLevel}`);
|
603
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
590
604
|
this.levelController.firstLevel = newLevel;
|
591
605
|
}
|
592
606
|
|
@@ -611,7 +625,7 @@ export default class Hls implements HlsEventEmitter {
|
|
611
625
|
* (determined from download of first segment)
|
612
626
|
*/
|
613
627
|
set startLevel(newLevel: number) {
|
614
|
-
logger.log(`set startLevel:${newLevel}`);
|
628
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
615
629
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
616
630
|
if (newLevel !== -1) {
|
617
631
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -686,7 +700,7 @@ export default class Hls implements HlsEventEmitter {
|
|
686
700
|
*/
|
687
701
|
set autoLevelCapping(newLevel: number) {
|
688
702
|
if (this._autoLevelCapping !== newLevel) {
|
689
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
703
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
690
704
|
this._autoLevelCapping = newLevel;
|
691
705
|
this.levelController.checkMaxAutoUpdated();
|
692
706
|
}
|
@@ -795,6 +809,10 @@ export default class Hls implements HlsEventEmitter {
|
|
795
809
|
return this.streamController.getMainFwdBufferInfo();
|
796
810
|
}
|
797
811
|
|
812
|
+
public get maxBufferLength(): number {
|
813
|
+
return this.streamController.maxBufferLength;
|
814
|
+
}
|
815
|
+
|
798
816
|
/**
|
799
817
|
* Find and select the best matching audio track, making a level switch when a Group change is necessary.
|
800
818
|
* Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found.
|
@@ -802,7 +820,7 @@ export default class Hls implements HlsEventEmitter {
|
|
802
820
|
public setAudioOption(
|
803
821
|
audioOption: MediaPlaylist | AudioSelectionOption | undefined,
|
804
822
|
): MediaPlaylist | null {
|
805
|
-
return this.audioTrackController?.setAudioOption(audioOption);
|
823
|
+
return this.audioTrackController?.setAudioOption(audioOption) || null;
|
806
824
|
}
|
807
825
|
/**
|
808
826
|
* Find and select the best matching subtitle track, making a level switch when a Group change is necessary.
|
@@ -811,8 +829,9 @@ export default class Hls implements HlsEventEmitter {
|
|
811
829
|
public setSubtitleOption(
|
812
830
|
subtitleOption: MediaPlaylist | SubtitleSelectionOption | undefined,
|
813
831
|
): MediaPlaylist | null {
|
814
|
-
|
815
|
-
|
832
|
+
return (
|
833
|
+
this.subtitleTrackController?.setSubtitleOption(subtitleOption) || null
|
834
|
+
);
|
816
835
|
}
|
817
836
|
|
818
837
|
/**
|
@@ -957,6 +976,10 @@ export default class Hls implements HlsEventEmitter {
|
|
957
976
|
return this.latencyController.targetLatency;
|
958
977
|
}
|
959
978
|
|
979
|
+
set targetLatency(latency: number) {
|
980
|
+
this.latencyController.targetLatency = latency;
|
981
|
+
}
|
982
|
+
|
960
983
|
/**
|
961
984
|
* the rate at which the edge of the current live playlist is advancing or 1 if there is none
|
962
985
|
*/
|
@@ -970,6 +993,17 @@ export default class Hls implements HlsEventEmitter {
|
|
970
993
|
get forceStartLoad(): boolean {
|
971
994
|
return this.streamController.forceStartLoad;
|
972
995
|
}
|
996
|
+
|
997
|
+
/**
|
998
|
+
* ContentSteering pathwayPriority getter/setter
|
999
|
+
*/
|
1000
|
+
get pathwayPriority(): string[] | null {
|
1001
|
+
return this.levelController.pathwayPriority;
|
1002
|
+
}
|
1003
|
+
|
1004
|
+
set pathwayPriority(pathwayPriority: string[]) {
|
1005
|
+
this.levelController.pathwayPriority = pathwayPriority;
|
1006
|
+
}
|
973
1007
|
}
|
974
1008
|
|
975
1009
|
export type {
|
@@ -1032,7 +1066,7 @@ export type {
|
|
1032
1066
|
TSDemuxerConfig,
|
1033
1067
|
} from './config';
|
1034
1068
|
export type { MediaKeySessionContext } from './controller/eme-controller';
|
1035
|
-
export type { ILogger } from './utils/logger';
|
1069
|
+
export type { ILogger, Logger } from './utils/logger';
|
1036
1070
|
export type {
|
1037
1071
|
PathwayClone,
|
1038
1072
|
SteeringManifest,
|
@@ -1046,7 +1080,7 @@ export type {
|
|
1046
1080
|
KeySystems,
|
1047
1081
|
KeySystemFormats,
|
1048
1082
|
} from './utils/mediakeys-helper';
|
1049
|
-
export type { DateRange } from './loader/date-range';
|
1083
|
+
export type { DateRange, DateRangeCue } from './loader/date-range';
|
1050
1084
|
export type { LoadStats } from './loader/load-stats';
|
1051
1085
|
export type { LevelKey } from './loader/level-key';
|
1052
1086
|
export type { LevelDetails } from './loader/level-details';
|
@@ -1096,6 +1130,7 @@ export type { ChunkMetadata } from './types/transmuxer';
|
|
1096
1130
|
export type {
|
1097
1131
|
BaseSegment,
|
1098
1132
|
Fragment,
|
1133
|
+
MediaFragment,
|
1099
1134
|
Part,
|
1100
1135
|
ElementaryStreams,
|
1101
1136
|
ElementaryStreamTypes,
|
@@ -1147,6 +1182,7 @@ export type {
|
|
1147
1182
|
ManifestParsedData,
|
1148
1183
|
MediaAttachedData,
|
1149
1184
|
MediaAttachingData,
|
1185
|
+
MediaEndedData,
|
1150
1186
|
NonNativeTextTrack,
|
1151
1187
|
NonNativeTextTracksData,
|
1152
1188
|
SteeringManifestLoadedData,
|
@@ -1161,3 +1197,4 @@ export type {
|
|
1161
1197
|
IErrorAction,
|
1162
1198
|
} from './controller/error-controller';
|
1163
1199
|
export type { AttrList } from './utils/attr-list';
|
1200
|
+
export type { ParsedMultivariantPlaylist } from './loader/m3u8-parser';
|
package/src/loader/date-range.ts
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
import { AttrList } from '../utils/attr-list';
|
2
2
|
import { logger } from '../utils/logger';
|
3
|
+
import type { Fragment } from './fragment';
|
3
4
|
|
4
5
|
// Avoid exporting const enum so that these values can be inlined
|
5
6
|
const enum DateRangeAttribute {
|
6
7
|
ID = 'ID',
|
7
8
|
CLASS = 'CLASS',
|
9
|
+
CUE = 'CUE',
|
8
10
|
START_DATE = 'START-DATE',
|
9
11
|
DURATION = 'DURATION',
|
10
12
|
END_DATE = 'END-DATE',
|
@@ -12,12 +14,22 @@ const enum DateRangeAttribute {
|
|
12
14
|
PLANNED_DURATION = 'PLANNED-DURATION',
|
13
15
|
SCTE35_OUT = 'SCTE35-OUT',
|
14
16
|
SCTE35_IN = 'SCTE35-IN',
|
17
|
+
SCTE35_CMD = 'SCTE35-CMD',
|
15
18
|
}
|
16
19
|
|
20
|
+
export type DateRangeCue = {
|
21
|
+
pre: boolean;
|
22
|
+
post: boolean;
|
23
|
+
once: boolean;
|
24
|
+
};
|
25
|
+
|
26
|
+
const CLASS_INTERSTITIAL = 'com.apple.hls.interstitial';
|
27
|
+
|
17
28
|
export function isDateRangeCueAttribute(attrName: string): boolean {
|
18
29
|
return (
|
19
30
|
attrName !== DateRangeAttribute.ID &&
|
20
31
|
attrName !== DateRangeAttribute.CLASS &&
|
32
|
+
attrName !== DateRangeAttribute.CUE &&
|
21
33
|
attrName !== DateRangeAttribute.START_DATE &&
|
22
34
|
attrName !== DateRangeAttribute.DURATION &&
|
23
35
|
attrName !== DateRangeAttribute.END_DATE &&
|
@@ -28,17 +40,27 @@ export function isDateRangeCueAttribute(attrName: string): boolean {
|
|
28
40
|
export function isSCTE35Attribute(attrName: string): boolean {
|
29
41
|
return (
|
30
42
|
attrName === DateRangeAttribute.SCTE35_OUT ||
|
31
|
-
attrName === DateRangeAttribute.SCTE35_IN
|
43
|
+
attrName === DateRangeAttribute.SCTE35_IN ||
|
44
|
+
attrName === DateRangeAttribute.SCTE35_CMD
|
32
45
|
);
|
33
46
|
}
|
34
47
|
|
35
48
|
export class DateRange {
|
36
49
|
public attr: AttrList;
|
50
|
+
public tagAnchor: Fragment | null;
|
51
|
+
public tagOrder: number;
|
37
52
|
private _startDate: Date;
|
38
53
|
private _endDate?: Date;
|
54
|
+
private _cue?: DateRangeCue;
|
39
55
|
private _badValueForSameId?: string;
|
40
56
|
|
41
|
-
constructor(
|
57
|
+
constructor(
|
58
|
+
dateRangeAttr: AttrList,
|
59
|
+
dateRangeWithSameId?: DateRange | undefined,
|
60
|
+
tagCount: number = 0,
|
61
|
+
) {
|
62
|
+
this.tagAnchor = dateRangeWithSameId?.tagAnchor || null;
|
63
|
+
this.tagOrder = dateRangeWithSameId?.tagOrder ?? tagCount;
|
42
64
|
if (dateRangeWithSameId) {
|
43
65
|
const previousAttr = dateRangeWithSameId.attr;
|
44
66
|
for (const key in previousAttr) {
|
@@ -61,9 +83,13 @@ export class DateRange {
|
|
61
83
|
);
|
62
84
|
}
|
63
85
|
this.attr = dateRangeAttr;
|
64
|
-
this._startDate =
|
86
|
+
this._startDate = dateRangeWithSameId
|
87
|
+
? dateRangeWithSameId.startDate
|
88
|
+
: new Date(dateRangeAttr[DateRangeAttribute.START_DATE]);
|
65
89
|
if (DateRangeAttribute.END_DATE in this.attr) {
|
66
|
-
const endDate =
|
90
|
+
const endDate =
|
91
|
+
dateRangeWithSameId?.endDate ||
|
92
|
+
new Date(this.attr[DateRangeAttribute.END_DATE]);
|
67
93
|
if (Number.isFinite(endDate.getTime())) {
|
68
94
|
this._endDate = endDate;
|
69
95
|
}
|
@@ -78,6 +104,36 @@ export class DateRange {
|
|
78
104
|
return this.attr.CLASS;
|
79
105
|
}
|
80
106
|
|
107
|
+
get cue(): DateRangeCue {
|
108
|
+
const _cue = this._cue;
|
109
|
+
if (_cue === undefined) {
|
110
|
+
return (this._cue = this.attr.enumeratedStringList(
|
111
|
+
this.attr.CUE ? 'CUE' : 'X-CUE',
|
112
|
+
{
|
113
|
+
pre: false,
|
114
|
+
post: false,
|
115
|
+
once: false,
|
116
|
+
},
|
117
|
+
));
|
118
|
+
}
|
119
|
+
return _cue;
|
120
|
+
}
|
121
|
+
|
122
|
+
get startTime(): number {
|
123
|
+
const { tagAnchor } = this;
|
124
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
125
|
+
if (tagAnchor === null || tagAnchor.programDateTime === null) {
|
126
|
+
logger.warn(
|
127
|
+
`Expected tagAnchor Fragment with PDT set for DateRange "${this.id}": ${tagAnchor}`,
|
128
|
+
);
|
129
|
+
return NaN;
|
130
|
+
}
|
131
|
+
return (
|
132
|
+
tagAnchor.start +
|
133
|
+
(this.startDate.getTime() - tagAnchor.programDateTime) / 1000
|
134
|
+
);
|
135
|
+
}
|
136
|
+
|
81
137
|
get startDate(): Date {
|
82
138
|
return this._startDate;
|
83
139
|
}
|
@@ -120,13 +176,23 @@ export class DateRange {
|
|
120
176
|
return this.attr.bool(DateRangeAttribute.END_ON_NEXT);
|
121
177
|
}
|
122
178
|
|
179
|
+
get isInterstitial(): boolean {
|
180
|
+
return this.class === CLASS_INTERSTITIAL;
|
181
|
+
}
|
182
|
+
|
123
183
|
get isValid(): boolean {
|
124
184
|
return (
|
125
185
|
!!this.id &&
|
126
186
|
!this._badValueForSameId &&
|
127
187
|
Number.isFinite(this.startDate.getTime()) &&
|
128
188
|
(this.duration === null || this.duration >= 0) &&
|
129
|
-
(!this.endOnNext || !!this.class)
|
189
|
+
(!this.endOnNext || !!this.class) &&
|
190
|
+
(!this.attr.CUE ||
|
191
|
+
(!this.cue.pre && !this.cue.post) ||
|
192
|
+
this.cue.pre !== this.cue.post) &&
|
193
|
+
(!this.isInterstitial ||
|
194
|
+
'X-ASSET-URI' in this.attr ||
|
195
|
+
'X-ASSET-LIST' in this.attr)
|
130
196
|
);
|
131
197
|
}
|
132
198
|
}
|
@@ -1,18 +1,17 @@
|
|
1
1
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
2
|
-
import { Fragment } from './fragment';
|
3
|
-
import {
|
4
|
-
Loader,
|
5
|
-
LoaderConfiguration,
|
6
|
-
FragmentLoaderContext,
|
7
|
-
} from '../types/loader';
|
8
2
|
import { getLoaderConfigWithoutReties } from '../utils/error-helper';
|
9
3
|
import type { HlsConfig } from '../config';
|
10
|
-
import type { BaseSegment, Part } from './fragment';
|
4
|
+
import type { BaseSegment, Fragment, Part } from './fragment';
|
11
5
|
import type {
|
12
6
|
ErrorData,
|
13
7
|
FragLoadedData,
|
14
8
|
PartsLoadedData,
|
15
9
|
} from '../types/events';
|
10
|
+
import type {
|
11
|
+
Loader,
|
12
|
+
LoaderConfiguration,
|
13
|
+
FragmentLoaderContext,
|
14
|
+
} from '../types/loader';
|
16
15
|
|
17
16
|
const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb
|
18
17
|
|
@@ -77,13 +76,11 @@ export default class FragmentLoader {
|
|
77
76
|
frag.gap = false;
|
78
77
|
}
|
79
78
|
}
|
80
|
-
const loader =
|
81
|
-
(
|
82
|
-
|
83
|
-
FragmentILoader
|
84
|
-
? new FragmentILoader(config)
|
85
|
-
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
79
|
+
const loader = (this.loader = FragmentILoader
|
80
|
+
? new FragmentILoader(config)
|
81
|
+
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
86
82
|
const loaderContext = createLoaderContext(frag);
|
83
|
+
frag.loader = loader;
|
87
84
|
const loadPolicy = getLoaderConfigWithoutReties(
|
88
85
|
config.fragLoadPolicy.default,
|
89
86
|
);
|
@@ -188,13 +185,11 @@ export default class FragmentLoader {
|
|
188
185
|
reject(createGapLoadError(frag, part));
|
189
186
|
return;
|
190
187
|
}
|
191
|
-
const loader =
|
192
|
-
(
|
193
|
-
|
194
|
-
FragmentILoader
|
195
|
-
? new FragmentILoader(config)
|
196
|
-
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
188
|
+
const loader = (this.loader = FragmentILoader
|
189
|
+
? new FragmentILoader(config)
|
190
|
+
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
197
191
|
const loaderContext = createLoaderContext(frag, part);
|
192
|
+
frag.loader = loader;
|
198
193
|
// Should we define another load policy for parts?
|
199
194
|
const loadPolicy = getLoaderConfigWithoutReties(
|
200
195
|
config.fragLoadPolicy.default,
|
@@ -336,8 +331,11 @@ function createLoaderContext(
|
|
336
331
|
if (Number.isFinite(start) && Number.isFinite(end)) {
|
337
332
|
let byteRangeStart = start;
|
338
333
|
let byteRangeEnd = end;
|
339
|
-
if (
|
340
|
-
|
334
|
+
if (
|
335
|
+
frag.sn === 'initSegment' &&
|
336
|
+
isMethodFullSegmentAesCbc(frag.decryptdata?.method)
|
337
|
+
) {
|
338
|
+
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
341
339
|
// has the unencrypted size specified in the range.
|
342
340
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
343
341
|
const fragmentLen = end - start;
|
@@ -372,6 +370,10 @@ function createGapLoadError(frag: Fragment, part?: Part): LoadError {
|
|
372
370
|
return new LoadError(errorData);
|
373
371
|
}
|
374
372
|
|
373
|
+
function isMethodFullSegmentAesCbc(method) {
|
374
|
+
return method === 'AES-128' || method === 'AES-256';
|
375
|
+
}
|
376
|
+
|
375
377
|
export class LoadError extends Error {
|
376
378
|
public readonly data: FragLoadFailResult;
|
377
379
|
constructor(data: FragLoadFailResult) {
|