hls.js 1.5.8-0.canary.10170 → 1.5.8
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 +3 -4
- package/dist/hls-demo.js +3 -12
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2366 -3626
- package/dist/hls.js.d.ts +84 -98
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1643 -2278
- 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 +1258 -1903
- 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 +1531 -2794
- 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 +30 -30
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +20 -24
- package/src/controller/audio-stream-controller.ts +74 -68
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +10 -27
- package/src/controller/base-stream-controller.ts +38 -160
- package/src/controller/buffer-controller.ts +92 -230
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -3
- package/src/controller/cmcd-controller.ts +9 -30
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +23 -10
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +7 -7
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +19 -13
- package/src/controller/stream-controller.ts +32 -37
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +31 -25
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +3 -4
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/audio/base-audio-demuxer.ts +14 -16
- package/src/demux/audio/mp3demuxer.ts +3 -4
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/id3.ts +411 -0
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +37 -71
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +2 -134
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +37 -49
- package/src/loader/fragment-loader.ts +3 -10
- package/src/loader/key-loader.ts +1 -3
- package/src/loader/level-key.ts +9 -10
- package/src/loader/playlist-loader.ts +5 -4
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +8 -24
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +0 -4
- package/src/types/remuxer.ts +1 -1
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/codecs.ts +5 -34
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/webvtt-parser.ts +1 -1
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -749
- package/src/utils/encryption-methods-util.ts +0 -21
- package/src/utils/utf8-utils.ts +0 -18
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import {
|
2
2
|
ManifestLoadedData,
|
3
3
|
ManifestParsedData,
|
4
4
|
LevelLoadedData,
|
@@ -27,6 +27,8 @@ import type Hls from '../hls';
|
|
27
27
|
import type { HlsUrlParameters, LevelParsed } from '../types/level';
|
28
28
|
import type { MediaPlaylist } from '../types/media-playlist';
|
29
29
|
|
30
|
+
let chromeOrFirefox: boolean;
|
31
|
+
|
30
32
|
export default class LevelController extends BasePlaylistController {
|
31
33
|
private _levels: Level[] = [];
|
32
34
|
private _firstLevel: number = -1;
|
@@ -43,7 +45,7 @@ export default class LevelController extends BasePlaylistController {
|
|
43
45
|
hls: Hls,
|
44
46
|
contentSteeringController: ContentSteeringController | null,
|
45
47
|
) {
|
46
|
-
super(hls, 'level-controller');
|
48
|
+
super(hls, '[level-controller]');
|
47
49
|
this.steering = contentSteeringController;
|
48
50
|
this._registerListeners();
|
49
51
|
}
|
@@ -117,12 +119,22 @@ export default class LevelController extends BasePlaylistController {
|
|
117
119
|
|
118
120
|
data.levels.forEach((levelParsed: LevelParsed) => {
|
119
121
|
const attributes = levelParsed.attrs;
|
122
|
+
|
123
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
124
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
120
125
|
let { audioCodec, videoCodec } = levelParsed;
|
126
|
+
if (audioCodec?.indexOf('mp4a.40.34') !== -1) {
|
127
|
+
chromeOrFirefox ||= /chrome|firefox/i.test(navigator.userAgent);
|
128
|
+
if (chromeOrFirefox) {
|
129
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
121
133
|
if (audioCodec) {
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
134
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(
|
135
|
+
audioCodec,
|
136
|
+
preferManagedMediaSource,
|
137
|
+
);
|
126
138
|
}
|
127
139
|
|
128
140
|
if (videoCodec?.indexOf('avc1') === 0) {
|
@@ -560,13 +572,7 @@ export default class LevelController extends BasePlaylistController {
|
|
560
572
|
if (curLevel.fragmentError === 0) {
|
561
573
|
curLevel.loadError = 0;
|
562
574
|
}
|
563
|
-
|
564
|
-
let previousDetails = curLevel.details;
|
565
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
566
|
-
previousDetails = undefined;
|
567
|
-
}
|
568
|
-
|
569
|
-
this.playlistLoaded(level, data, previousDetails);
|
575
|
+
this.playlistLoaded(level, data, curLevel.details);
|
570
576
|
} else if (data.deliveryDirectives?.skip) {
|
571
577
|
// received a delta playlist update that cannot be merged
|
572
578
|
details.deltaUpdateFailed = true;
|
@@ -49,6 +49,8 @@ export default class StreamController
|
|
49
49
|
private altAudio: boolean = false;
|
50
50
|
private audioOnly: boolean = false;
|
51
51
|
private fragPlaying: Fragment | null = null;
|
52
|
+
private onvplaying: EventListener | null = null;
|
53
|
+
private onvseeked: EventListener | null = null;
|
52
54
|
private fragLastKbps: number = 0;
|
53
55
|
private couldBacktrack: boolean = false;
|
54
56
|
private backtrackFragment: Fragment | null = null;
|
@@ -64,15 +66,17 @@ export default class StreamController
|
|
64
66
|
hls,
|
65
67
|
fragmentTracker,
|
66
68
|
keyLoader,
|
67
|
-
'stream-controller',
|
69
|
+
'[stream-controller]',
|
68
70
|
PlaylistLevelType.MAIN,
|
69
71
|
);
|
70
|
-
this.
|
72
|
+
this._registerListeners();
|
71
73
|
}
|
72
74
|
|
73
|
-
|
74
|
-
super.registerListeners();
|
75
|
+
private _registerListeners() {
|
75
76
|
const { hls } = this;
|
77
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
78
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
79
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
76
80
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
77
81
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
78
82
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
@@ -81,6 +85,7 @@ export default class StreamController
|
|
81
85
|
this.onFragLoadEmergencyAborted,
|
82
86
|
this,
|
83
87
|
);
|
88
|
+
hls.on(Events.ERROR, this.onError, this);
|
84
89
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
85
90
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
86
91
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -89,9 +94,11 @@ export default class StreamController
|
|
89
94
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
90
95
|
}
|
91
96
|
|
92
|
-
protected
|
93
|
-
super.unregisterListeners();
|
97
|
+
protected _unregisterListeners() {
|
94
98
|
const { hls } = this;
|
99
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
100
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
101
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
95
102
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
96
103
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
97
104
|
hls.off(
|
@@ -99,6 +106,7 @@ export default class StreamController
|
|
99
106
|
this.onFragLoadEmergencyAborted,
|
100
107
|
this,
|
101
108
|
);
|
109
|
+
hls.off(Events.ERROR, this.onError, this);
|
102
110
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
103
111
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
104
112
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -108,9 +116,7 @@ export default class StreamController
|
|
108
116
|
}
|
109
117
|
|
110
118
|
protected onHandlerDestroying() {
|
111
|
-
|
112
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
113
|
-
this.unregisterListeners();
|
119
|
+
this._unregisterListeners();
|
114
120
|
super.onHandlerDestroying();
|
115
121
|
}
|
116
122
|
|
@@ -214,9 +220,6 @@ export default class StreamController
|
|
214
220
|
}
|
215
221
|
|
216
222
|
private doTickIdle() {
|
217
|
-
if (!this.buffering) {
|
218
|
-
return;
|
219
|
-
}
|
220
223
|
const { hls, levelLastLoaded, levels, media } = this;
|
221
224
|
|
222
225
|
// if start level not parsed yet OR
|
@@ -513,8 +516,10 @@ export default class StreamController
|
|
513
516
|
) {
|
514
517
|
super.onMediaAttached(event, data);
|
515
518
|
const media = data.media;
|
516
|
-
|
517
|
-
|
519
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
520
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
521
|
+
media.addEventListener('playing', this.onvplaying as EventListener);
|
522
|
+
media.addEventListener('seeked', this.onvseeked as EventListener);
|
518
523
|
this.gapController = new GapController(
|
519
524
|
this.config,
|
520
525
|
media,
|
@@ -525,11 +530,12 @@ export default class StreamController
|
|
525
530
|
|
526
531
|
protected onMediaDetaching() {
|
527
532
|
const { media } = this;
|
528
|
-
if (media) {
|
529
|
-
media.removeEventListener('playing', this.
|
530
|
-
media.removeEventListener('seeked', this.
|
533
|
+
if (media && this.onvplaying && this.onvseeked) {
|
534
|
+
media.removeEventListener('playing', this.onvplaying);
|
535
|
+
media.removeEventListener('seeked', this.onvseeked);
|
536
|
+
this.onvplaying = this.onvseeked = null;
|
537
|
+
this.videoBuffer = null;
|
531
538
|
}
|
532
|
-
this.videoBuffer = null;
|
533
539
|
this.fragPlaying = null;
|
534
540
|
if (this.gapController) {
|
535
541
|
this.gapController.destroy();
|
@@ -538,12 +544,12 @@ export default class StreamController
|
|
538
544
|
super.onMediaDetaching();
|
539
545
|
}
|
540
546
|
|
541
|
-
private onMediaPlaying
|
547
|
+
private onMediaPlaying() {
|
542
548
|
// tick to speed up FRAG_CHANGED triggering
|
543
549
|
this.tick();
|
544
|
-
}
|
550
|
+
}
|
545
551
|
|
546
|
-
private onMediaSeeked
|
552
|
+
private onMediaSeeked() {
|
547
553
|
const media = this.media;
|
548
554
|
const currentTime = media ? media.currentTime : null;
|
549
555
|
if (Number.isFinite(currentTime)) {
|
@@ -563,9 +569,9 @@ export default class StreamController
|
|
563
569
|
|
564
570
|
// tick to speed up FRAG_CHANGED triggering
|
565
571
|
this.tick();
|
566
|
-
}
|
572
|
+
}
|
567
573
|
|
568
|
-
|
574
|
+
private onManifestLoading() {
|
569
575
|
// reset buffer on manifest loading
|
570
576
|
this.log('Trigger BUFFER_RESET');
|
571
577
|
this.hls.trigger(Events.BUFFER_RESET, undefined);
|
@@ -878,7 +884,7 @@ export default class StreamController
|
|
878
884
|
this.fragBufferedComplete(frag, part);
|
879
885
|
}
|
880
886
|
|
881
|
-
|
887
|
+
private onError(event: Events.ERROR, data: ErrorData) {
|
882
888
|
if (data.fatal) {
|
883
889
|
this.state = State.ERROR;
|
884
890
|
return;
|
@@ -936,10 +942,8 @@ export default class StreamController
|
|
936
942
|
|
937
943
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
938
944
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
939
|
-
const
|
940
|
-
|
941
|
-
const levelDetails = this.getLevelDetails();
|
942
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
945
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
946
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
943
947
|
}
|
944
948
|
|
945
949
|
this.lastCurrentTime = media.currentTime;
|
@@ -1341,15 +1345,6 @@ export default class StreamController
|
|
1341
1345
|
);
|
1342
1346
|
}
|
1343
1347
|
|
1344
|
-
public get maxBufferLength(): number {
|
1345
|
-
const { levels, level } = this;
|
1346
|
-
const levelInfo = levels?.[level];
|
1347
|
-
if (!levelInfo) {
|
1348
|
-
return this.config.maxBufferLength;
|
1349
|
-
}
|
1350
|
-
return this.getMaxBufferLength(levelInfo.maxBitrate);
|
1351
|
-
}
|
1352
|
-
|
1353
1348
|
private backtrack(frag: Fragment) {
|
1354
1349
|
this.couldBacktrack = true;
|
1355
1350
|
// Causes findFragments to backtrack through fragments to find the keyframe
|
@@ -9,10 +9,6 @@ import { PlaylistLevelType } from '../types/loader';
|
|
9
9
|
import { Level } from '../types/level';
|
10
10
|
import { subtitleOptionsIdentical } from '../utils/media-option-attributes';
|
11
11
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
12
|
-
import {
|
13
|
-
isFullSegmentEncryption,
|
14
|
-
getAesModeFromFullSegmentMethod,
|
15
|
-
} from '../utils/encryption-methods-util';
|
16
12
|
import type { NetworkComponentAPI } from '../types/component-api';
|
17
13
|
import type Hls from '../hls';
|
18
14
|
import type { FragmentTracker } from './fragment-tracker';
|
@@ -55,22 +51,25 @@ export class SubtitleStreamController
|
|
55
51
|
hls,
|
56
52
|
fragmentTracker,
|
57
53
|
keyLoader,
|
58
|
-
'subtitle-stream-controller',
|
54
|
+
'[subtitle-stream-controller]',
|
59
55
|
PlaylistLevelType.SUBTITLE,
|
60
56
|
);
|
61
|
-
this.
|
57
|
+
this._registerListeners();
|
62
58
|
}
|
63
59
|
|
64
60
|
protected onHandlerDestroying() {
|
65
|
-
this.
|
61
|
+
this._unregisterListeners();
|
66
62
|
super.onHandlerDestroying();
|
67
63
|
this.mainDetails = null;
|
68
64
|
}
|
69
65
|
|
70
|
-
|
71
|
-
super.registerListeners();
|
66
|
+
private _registerListeners() {
|
72
67
|
const { hls } = this;
|
68
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
69
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
70
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
73
71
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
72
|
+
hls.on(Events.ERROR, this.onError, this);
|
74
73
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
75
74
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
76
75
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -79,10 +78,13 @@ export class SubtitleStreamController
|
|
79
78
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
80
79
|
}
|
81
80
|
|
82
|
-
|
83
|
-
super.unregisterListeners();
|
81
|
+
private _unregisterListeners() {
|
84
82
|
const { hls } = this;
|
83
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
84
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
85
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
85
86
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
87
|
+
hls.off(Events.ERROR, this.onError, this);
|
86
88
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
87
89
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
88
90
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -105,21 +107,21 @@ export class SubtitleStreamController
|
|
105
107
|
this.tick();
|
106
108
|
}
|
107
109
|
|
108
|
-
|
110
|
+
onManifestLoading() {
|
109
111
|
this.mainDetails = null;
|
110
112
|
this.fragmentTracker.removeAllFragments();
|
111
113
|
}
|
112
114
|
|
113
|
-
|
115
|
+
onMediaDetaching(): void {
|
114
116
|
this.tracksBuffered = [];
|
115
117
|
super.onMediaDetaching();
|
116
118
|
}
|
117
119
|
|
118
|
-
|
120
|
+
onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
119
121
|
this.mainDetails = data.details;
|
120
122
|
}
|
121
123
|
|
122
|
-
|
124
|
+
onSubtitleFragProcessed(
|
123
125
|
event: Events.SUBTITLE_FRAG_PROCESSED,
|
124
126
|
data: SubtitleFragProcessed,
|
125
127
|
) {
|
@@ -160,10 +162,7 @@ export class SubtitleStreamController
|
|
160
162
|
this.fragBufferedComplete(frag, null);
|
161
163
|
}
|
162
164
|
|
163
|
-
|
164
|
-
event: Events.BUFFER_FLUSHING,
|
165
|
-
data: BufferFlushingData,
|
166
|
-
) {
|
165
|
+
onBufferFlushing(event: Events.BUFFER_FLUSHING, data: BufferFlushingData) {
|
167
166
|
const { startOffset, endOffset } = data;
|
168
167
|
if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
|
169
168
|
const endOffsetSubtitles = endOffset - 1;
|
@@ -192,7 +191,7 @@ export class SubtitleStreamController
|
|
192
191
|
}
|
193
192
|
}
|
194
193
|
|
195
|
-
|
194
|
+
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
196
195
|
if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) {
|
197
196
|
if (this.media?.buffered.length) {
|
198
197
|
this.loadedmetadata = true;
|
@@ -201,7 +200,7 @@ export class SubtitleStreamController
|
|
201
200
|
}
|
202
201
|
|
203
202
|
// If something goes wrong, proceed to next frag, if we were processing one.
|
204
|
-
|
203
|
+
onError(event: Events.ERROR, data: ErrorData) {
|
205
204
|
const frag = data.frag;
|
206
205
|
|
207
206
|
if (frag?.type === PlaylistLevelType.SUBTITLE) {
|
@@ -215,7 +214,7 @@ export class SubtitleStreamController
|
|
215
214
|
}
|
216
215
|
|
217
216
|
// Got all new subtitle levels.
|
218
|
-
|
217
|
+
onSubtitleTracksUpdated(
|
219
218
|
event: Events.SUBTITLE_TRACKS_UPDATED,
|
220
219
|
{ subtitleTracks }: SubtitleTracksUpdatedData,
|
221
220
|
) {
|
@@ -240,7 +239,7 @@ export class SubtitleStreamController
|
|
240
239
|
this.mediaBuffer = null;
|
241
240
|
}
|
242
241
|
|
243
|
-
|
242
|
+
onSubtitleTrackSwitch(
|
244
243
|
event: Events.SUBTITLE_TRACK_SWITCH,
|
245
244
|
data: TrackSwitchedData,
|
246
245
|
) {
|
@@ -258,13 +257,13 @@ export class SubtitleStreamController
|
|
258
257
|
} else {
|
259
258
|
this.mediaBuffer = null;
|
260
259
|
}
|
261
|
-
if (currentTrack
|
260
|
+
if (currentTrack) {
|
262
261
|
this.setInterval(TICK_INTERVAL);
|
263
262
|
}
|
264
263
|
}
|
265
264
|
|
266
265
|
// Got a new set of subtitle fragments.
|
267
|
-
|
266
|
+
onSubtitleTrackLoaded(
|
268
267
|
event: Events.SUBTITLE_TRACK_LOADED,
|
269
268
|
data: TrackLoadedData,
|
270
269
|
) {
|
@@ -361,7 +360,7 @@ export class SubtitleStreamController
|
|
361
360
|
payload.byteLength > 0 &&
|
362
361
|
decryptData?.key &&
|
363
362
|
decryptData.iv &&
|
364
|
-
|
363
|
+
decryptData.method === 'AES-128'
|
365
364
|
) {
|
366
365
|
const startTime = performance.now();
|
367
366
|
// decrypt the subtitles
|
@@ -370,7 +369,6 @@ export class SubtitleStreamController
|
|
370
369
|
new Uint8Array(payload),
|
371
370
|
decryptData.key.buffer,
|
372
371
|
decryptData.iv.buffer,
|
373
|
-
getAesModeFromFullSegmentMethod(decryptData.method),
|
374
372
|
)
|
375
373
|
.catch((err) => {
|
376
374
|
hls.trigger(Events.ERROR, {
|
@@ -421,9 +419,15 @@ export class SubtitleStreamController
|
|
421
419
|
config.maxBufferHole,
|
422
420
|
);
|
423
421
|
const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
|
422
|
+
|
423
|
+
const mainBufferInfo = this.getFwdBufferInfo(
|
424
|
+
this.media,
|
425
|
+
PlaylistLevelType.MAIN,
|
426
|
+
);
|
424
427
|
const trackDetails = track.details as LevelDetails;
|
425
428
|
const maxBufLen =
|
426
|
-
this.
|
429
|
+
this.getMaxBufferLength(mainBufferInfo?.len) +
|
430
|
+
trackDetails.levelTargetDuration;
|
427
431
|
|
428
432
|
if (bufferLen > maxBufLen) {
|
429
433
|
return;
|
@@ -479,6 +483,14 @@ export class SubtitleStreamController
|
|
479
483
|
}
|
480
484
|
}
|
481
485
|
|
486
|
+
protected getMaxBufferLength(mainBufferLength?: number): number {
|
487
|
+
const maxConfigBuffer = super.getMaxBufferLength();
|
488
|
+
if (!mainBufferLength) {
|
489
|
+
return maxConfigBuffer;
|
490
|
+
}
|
491
|
+
return Math.max(maxConfigBuffer, mainBufferLength);
|
492
|
+
}
|
493
|
+
|
482
494
|
protected loadFragment(
|
483
495
|
frag: Fragment,
|
484
496
|
level: Level,
|
@@ -35,14 +35,13 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
35
35
|
private currentTrack: MediaPlaylist | null = null;
|
36
36
|
private selectDefaultTrack: boolean = true;
|
37
37
|
private queuedDefaultTrack: number = -1;
|
38
|
+
private asyncPollTrackChange: () => void = () => this.pollTrackChange(0);
|
38
39
|
private useTextTrackPolling: boolean = false;
|
39
40
|
private subtitlePollingInterval: number = -1;
|
40
41
|
private _subtitleDisplay: boolean = true;
|
41
42
|
|
42
|
-
private asyncPollTrackChange = () => this.pollTrackChange(0);
|
43
|
-
|
44
43
|
constructor(hls: Hls) {
|
45
|
-
super(hls, 'subtitle-track-controller');
|
44
|
+
super(hls, '[subtitle-track-controller]');
|
46
45
|
this.registerListeners();
|
47
46
|
}
|
48
47
|
|
@@ -51,8 +50,7 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
51
50
|
this.tracks.length = 0;
|
52
51
|
this.tracksInGroup.length = 0;
|
53
52
|
this.currentTrack = null;
|
54
|
-
|
55
|
-
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
53
|
+
this.onTextTracksChanged = this.asyncPollTrackChange = null as any;
|
56
54
|
super.destroy();
|
57
55
|
}
|
58
56
|
|
@@ -28,6 +28,7 @@ import type {
|
|
28
28
|
BufferFlushingData,
|
29
29
|
FragLoadingData,
|
30
30
|
} from '../types/events';
|
31
|
+
import { logger } from '../utils/logger';
|
31
32
|
import type Hls from '../hls';
|
32
33
|
import type { ComponentAPI } from '../types/component-api';
|
33
34
|
import type { HlsConfig } from '../config';
|
@@ -130,17 +131,22 @@ export class TimelineController implements ComponentAPI {
|
|
130
131
|
hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
|
131
132
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
132
133
|
// @ts-ignore
|
133
|
-
this.hls = this.config =
|
134
|
+
this.hls = this.config = null;
|
134
135
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
135
136
|
}
|
136
137
|
|
137
138
|
private initCea608Parsers() {
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
139
|
+
if (
|
140
|
+
this.config.enableCEA708Captions &&
|
141
|
+
(!this.cea608Parser1 || !this.cea608Parser2)
|
142
|
+
) {
|
143
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
144
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
145
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
146
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
147
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
148
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
149
|
+
}
|
144
150
|
}
|
145
151
|
|
146
152
|
public addCues(
|
@@ -303,7 +309,6 @@ export class TimelineController implements ComponentAPI {
|
|
303
309
|
delete captionsTracks[trackName];
|
304
310
|
});
|
305
311
|
this.nonNativeCaptionsTracks = {};
|
306
|
-
this.media = null;
|
307
312
|
}
|
308
313
|
|
309
314
|
private onManifestLoading() {
|
@@ -405,7 +410,7 @@ export class TimelineController implements ComponentAPI {
|
|
405
410
|
.filter((t) => t !== null)
|
406
411
|
.map((t) => (t as TextTrack).label);
|
407
412
|
if (unusedTextTracks.length) {
|
408
|
-
|
413
|
+
logger.warn(
|
409
414
|
`Media element contains unused subtitle tracks: ${unusedTextTracks.join(
|
410
415
|
', ',
|
411
416
|
)}. Replace media element for each source to clear TextTracks and captions menu.`,
|
@@ -463,19 +468,21 @@ export class TimelineController implements ComponentAPI {
|
|
463
468
|
}
|
464
469
|
|
465
470
|
private onFragLoading(event: Events.FRAG_LOADING, data: FragLoadingData) {
|
471
|
+
this.initCea608Parsers();
|
472
|
+
const { cea608Parser1, cea608Parser2, lastCc, lastSn, lastPartIndex } =
|
473
|
+
this;
|
474
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
475
|
+
return;
|
476
|
+
}
|
466
477
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
467
|
-
if (
|
468
|
-
const { cea608Parser1, cea608Parser2, lastSn } = this;
|
469
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
470
|
-
return;
|
471
|
-
}
|
478
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
472
479
|
const { cc, sn } = data.frag;
|
473
|
-
const partIndex = data
|
480
|
+
const partIndex = data?.part?.index ?? -1;
|
474
481
|
if (
|
475
482
|
!(
|
476
483
|
sn === lastSn + 1 ||
|
477
|
-
(sn === lastSn && partIndex ===
|
478
|
-
cc ===
|
484
|
+
(sn === lastSn && partIndex === lastPartIndex + 1) ||
|
485
|
+
cc === lastCc
|
479
486
|
)
|
480
487
|
) {
|
481
488
|
cea608Parser1.reset();
|
@@ -543,7 +550,7 @@ export class TimelineController implements ComponentAPI {
|
|
543
550
|
});
|
544
551
|
},
|
545
552
|
(error) => {
|
546
|
-
|
553
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
547
554
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
548
555
|
success: false,
|
549
556
|
frag: frag,
|
@@ -590,7 +597,7 @@ export class TimelineController implements ComponentAPI {
|
|
590
597
|
this._fallbackToIMSC1(frag, payload);
|
591
598
|
}
|
592
599
|
// Something went wrong while parsing. Trigger event with success false.
|
593
|
-
|
600
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
594
601
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
595
602
|
return;
|
596
603
|
}
|
@@ -662,7 +669,9 @@ export class TimelineController implements ComponentAPI {
|
|
662
669
|
event: Events.FRAG_PARSING_USERDATA,
|
663
670
|
data: FragParsingUserdataData,
|
664
671
|
) {
|
665
|
-
|
672
|
+
this.initCea608Parsers();
|
673
|
+
const { cea608Parser1, cea608Parser2 } = this;
|
674
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
666
675
|
return;
|
667
676
|
}
|
668
677
|
const { frag, samples } = data;
|
@@ -677,12 +686,9 @@ export class TimelineController implements ComponentAPI {
|
|
677
686
|
for (let i = 0; i < samples.length; i++) {
|
678
687
|
const ccBytes = samples[i].bytes;
|
679
688
|
if (ccBytes) {
|
680
|
-
if (!this.cea608Parser1) {
|
681
|
-
this.initCea608Parsers();
|
682
|
-
}
|
683
689
|
const ccdatas = this.extractCea608Data(ccBytes);
|
684
|
-
|
685
|
-
|
690
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
691
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
686
692
|
}
|
687
693
|
}
|
688
694
|
}
|
package/src/crypt/aes-crypto.ts
CHANGED
@@ -1,32 +1,13 @@
|
|
1
|
-
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
-
|
3
1
|
export default class AESCrypto {
|
4
2
|
private subtle: SubtleCrypto;
|
5
3
|
private aesIV: Uint8Array;
|
6
|
-
private aesMode: DecrypterAesMode;
|
7
4
|
|
8
|
-
constructor(subtle: SubtleCrypto, iv: Uint8Array
|
5
|
+
constructor(subtle: SubtleCrypto, iv: Uint8Array) {
|
9
6
|
this.subtle = subtle;
|
10
7
|
this.aesIV = iv;
|
11
|
-
this.aesMode = aesMode;
|
12
8
|
}
|
13
9
|
|
14
10
|
decrypt(data: ArrayBuffer, key: CryptoKey) {
|
15
|
-
|
16
|
-
case DecrypterAesMode.cbc:
|
17
|
-
return this.subtle.decrypt(
|
18
|
-
{ name: 'AES-CBC', iv: this.aesIV },
|
19
|
-
key,
|
20
|
-
data,
|
21
|
-
);
|
22
|
-
case DecrypterAesMode.ctr:
|
23
|
-
return this.subtle.decrypt(
|
24
|
-
{ name: 'AES-CTR', counter: this.aesIV, length: 64 }, //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
25
|
-
key,
|
26
|
-
data,
|
27
|
-
);
|
28
|
-
default:
|
29
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
30
|
-
}
|
11
|
+
return this.subtle.decrypt({ name: 'AES-CBC', iv: this.aesIV }, key, data);
|
31
12
|
}
|
32
13
|
}
|