hls.js 1.5.10-0.canary.10328 → 1.5.10
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 +38 -41
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2194 -3476
- package/dist/hls.js.d.ts +85 -108
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +3137 -3784
- 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 +1256 -1928
- 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 +4182 -5487
- 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 +36 -36
- 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 +14 -51
- package/src/controller/content-steering-controller.ts +15 -29
- 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 -37
- 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 +21 -19
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +16 -32
- package/src/crypt/fast-aes-key.ts +5 -28
- 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 +38 -75
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +18 -147
- 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 +38 -61
- 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 -4
- 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/cea-608-parser.ts +3 -1
- 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
@@ -6,6 +6,7 @@ import type {
|
|
6
6
|
LevelUpdatedData,
|
7
7
|
MediaAttachingData,
|
8
8
|
} from '../types/events';
|
9
|
+
import { logger } from '../utils/logger';
|
9
10
|
import type { ComponentAPI } from '../types/component-api';
|
10
11
|
import type Hls from '../hls';
|
11
12
|
import type { HlsConfig } from '../config';
|
@@ -18,6 +19,7 @@ export default class LatencyController implements ComponentAPI {
|
|
18
19
|
private currentTime: number = 0;
|
19
20
|
private stallCount: number = 0;
|
20
21
|
private _latency: number | null = null;
|
22
|
+
private timeupdateHandler = () => this.timeupdate();
|
21
23
|
|
22
24
|
constructor(hls: Hls) {
|
23
25
|
this.hls = hls;
|
@@ -124,7 +126,7 @@ export default class LatencyController implements ComponentAPI {
|
|
124
126
|
this.onMediaDetaching();
|
125
127
|
this.levelDetails = null;
|
126
128
|
// @ts-ignore
|
127
|
-
this.hls = null;
|
129
|
+
this.hls = this.timeupdateHandler = null;
|
128
130
|
}
|
129
131
|
|
130
132
|
private registerListeners() {
|
@@ -148,12 +150,12 @@ export default class LatencyController implements ComponentAPI {
|
|
148
150
|
data: MediaAttachingData,
|
149
151
|
) {
|
150
152
|
this.media = data.media;
|
151
|
-
this.media.addEventListener('timeupdate', this.
|
153
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
152
154
|
}
|
153
155
|
|
154
156
|
private onMediaDetaching() {
|
155
157
|
if (this.media) {
|
156
|
-
this.media.removeEventListener('timeupdate', this.
|
158
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
157
159
|
this.media = null;
|
158
160
|
}
|
159
161
|
}
|
@@ -170,10 +172,10 @@ export default class LatencyController implements ComponentAPI {
|
|
170
172
|
) {
|
171
173
|
this.levelDetails = details;
|
172
174
|
if (details.advanced) {
|
173
|
-
this.
|
175
|
+
this.timeupdate();
|
174
176
|
}
|
175
177
|
if (!details.live && this.media) {
|
176
|
-
this.media.removeEventListener('timeupdate', this.
|
178
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
177
179
|
}
|
178
180
|
}
|
179
181
|
|
@@ -183,13 +185,13 @@ export default class LatencyController implements ComponentAPI {
|
|
183
185
|
}
|
184
186
|
this.stallCount++;
|
185
187
|
if (this.levelDetails?.live) {
|
186
|
-
|
187
|
-
'[
|
188
|
+
logger.warn(
|
189
|
+
'[playback-rate-controller]: Stall detected, adjusting target latency',
|
188
190
|
);
|
189
191
|
}
|
190
192
|
}
|
191
193
|
|
192
|
-
private
|
194
|
+
private timeupdate() {
|
193
195
|
const { media, levelDetails } = this;
|
194
196
|
if (!media || !levelDetails) {
|
195
197
|
return;
|
@@ -240,7 +242,7 @@ export default class LatencyController implements ComponentAPI {
|
|
240
242
|
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
241
243
|
media.playbackRate = 1;
|
242
244
|
}
|
243
|
-
}
|
245
|
+
}
|
244
246
|
|
245
247
|
private estimateLiveEdge(): number | null {
|
246
248
|
const { levelDetails } = this;
|
@@ -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) {
|
@@ -509,30 +521,6 @@ export default class LevelController extends BasePlaylistController {
|
|
509
521
|
this._startLevel = newLevel;
|
510
522
|
}
|
511
523
|
|
512
|
-
get pathwayPriority(): string[] | null {
|
513
|
-
if (this.steering) {
|
514
|
-
return this.steering.pathwayPriority;
|
515
|
-
}
|
516
|
-
|
517
|
-
return null;
|
518
|
-
}
|
519
|
-
|
520
|
-
set pathwayPriority(pathwayPriority: string[]) {
|
521
|
-
if (this.steering) {
|
522
|
-
const pathwaysList = this.steering.pathways();
|
523
|
-
const filteredPathwayPriority = pathwayPriority.filter((pathwayId) => {
|
524
|
-
return pathwaysList.indexOf(pathwayId) !== -1;
|
525
|
-
});
|
526
|
-
if (pathwayPriority.length < 1) {
|
527
|
-
this.warn(
|
528
|
-
`pathwayPriority ${pathwayPriority} should contain at least one pathway from list: ${pathwaysList}`,
|
529
|
-
);
|
530
|
-
return;
|
531
|
-
}
|
532
|
-
this.steering.pathwayPriority = filteredPathwayPriority;
|
533
|
-
}
|
534
|
-
}
|
535
|
-
|
536
524
|
protected onError(event: Events.ERROR, data: ErrorData) {
|
537
525
|
if (data.fatal || !data.context) {
|
538
526
|
return;
|
@@ -584,13 +572,7 @@ export default class LevelController extends BasePlaylistController {
|
|
584
572
|
if (curLevel.fragmentError === 0) {
|
585
573
|
curLevel.loadError = 0;
|
586
574
|
}
|
587
|
-
|
588
|
-
let previousDetails = curLevel.details;
|
589
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
590
|
-
previousDetails = undefined;
|
591
|
-
}
|
592
|
-
|
593
|
-
this.playlistLoaded(level, data, previousDetails);
|
575
|
+
this.playlistLoaded(level, data, curLevel.details);
|
594
576
|
} else if (data.deliveryDirectives?.skip) {
|
595
577
|
// received a delta playlist update that cannot be merged
|
596
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;
|
@@ -1347,15 +1351,6 @@ export default class StreamController
|
|
1347
1351
|
);
|
1348
1352
|
}
|
1349
1353
|
|
1350
|
-
public get maxBufferLength(): number {
|
1351
|
-
const { levels, level } = this;
|
1352
|
-
const levelInfo = levels?.[level];
|
1353
|
-
if (!levelInfo) {
|
1354
|
-
return this.config.maxBufferLength;
|
1355
|
-
}
|
1356
|
-
return this.getMaxBufferLength(levelInfo.maxBitrate);
|
1357
|
-
}
|
1358
|
-
|
1359
1354
|
private backtrack(frag: Fragment) {
|
1360
1355
|
this.couldBacktrack = true;
|
1361
1356
|
// 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
|
) {
|
@@ -365,7 +364,7 @@ export class SubtitleStreamController
|
|
365
364
|
payload.byteLength > 0 &&
|
366
365
|
decryptData?.key &&
|
367
366
|
decryptData.iv &&
|
368
|
-
|
367
|
+
decryptData.method === 'AES-128'
|
369
368
|
) {
|
370
369
|
const startTime = performance.now();
|
371
370
|
// decrypt the subtitles
|
@@ -374,7 +373,6 @@ export class SubtitleStreamController
|
|
374
373
|
new Uint8Array(payload),
|
375
374
|
decryptData.key.buffer,
|
376
375
|
decryptData.iv.buffer,
|
377
|
-
getAesModeFromFullSegmentMethod(decryptData.method),
|
378
376
|
)
|
379
377
|
.catch((err) => {
|
380
378
|
hls.trigger(Events.ERROR, {
|
@@ -425,9 +423,15 @@ export class SubtitleStreamController
|
|
425
423
|
config.maxBufferHole,
|
426
424
|
);
|
427
425
|
const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
|
426
|
+
|
427
|
+
const mainBufferInfo = this.getFwdBufferInfo(
|
428
|
+
this.media,
|
429
|
+
PlaylistLevelType.MAIN,
|
430
|
+
);
|
428
431
|
const trackDetails = track.details as LevelDetails;
|
429
432
|
const maxBufLen =
|
430
|
-
this.
|
433
|
+
this.getMaxBufferLength(mainBufferInfo?.len) +
|
434
|
+
trackDetails.levelTargetDuration;
|
431
435
|
|
432
436
|
if (bufferLen > maxBufLen) {
|
433
437
|
return;
|
@@ -483,6 +487,14 @@ export class SubtitleStreamController
|
|
483
487
|
}
|
484
488
|
}
|
485
489
|
|
490
|
+
protected getMaxBufferLength(mainBufferLength?: number): number {
|
491
|
+
const maxConfigBuffer = super.getMaxBufferLength();
|
492
|
+
if (!mainBufferLength) {
|
493
|
+
return maxConfigBuffer;
|
494
|
+
}
|
495
|
+
return Math.max(maxConfigBuffer, mainBufferLength);
|
496
|
+
}
|
497
|
+
|
486
498
|
protected loadFragment(
|
487
499
|
frag: Fragment,
|
488
500
|
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(
|
@@ -215,8 +221,6 @@ export class TimelineController implements ComponentAPI {
|
|
215
221
|
canReuseVttTextTrack(textTrack, {
|
216
222
|
name: label,
|
217
223
|
lang: language,
|
218
|
-
characteristics:
|
219
|
-
'transcribes-spoken-dialog,describes-music-and-sound',
|
220
224
|
attrs: {} as any,
|
221
225
|
})
|
222
226
|
) {
|
@@ -305,7 +309,6 @@ export class TimelineController implements ComponentAPI {
|
|
305
309
|
delete captionsTracks[trackName];
|
306
310
|
});
|
307
311
|
this.nonNativeCaptionsTracks = {};
|
308
|
-
this.media = null;
|
309
312
|
}
|
310
313
|
|
311
314
|
private onManifestLoading() {
|
@@ -407,7 +410,7 @@ export class TimelineController implements ComponentAPI {
|
|
407
410
|
.filter((t) => t !== null)
|
408
411
|
.map((t) => (t as TextTrack).label);
|
409
412
|
if (unusedTextTracks.length) {
|
410
|
-
|
413
|
+
logger.warn(
|
411
414
|
`Media element contains unused subtitle tracks: ${unusedTextTracks.join(
|
412
415
|
', ',
|
413
416
|
)}. Replace media element for each source to clear TextTracks and captions menu.`,
|
@@ -542,7 +545,7 @@ export class TimelineController implements ComponentAPI {
|
|
542
545
|
});
|
543
546
|
},
|
544
547
|
(error) => {
|
545
|
-
|
548
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
546
549
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
547
550
|
success: false,
|
548
551
|
frag: frag,
|
@@ -589,7 +592,7 @@ export class TimelineController implements ComponentAPI {
|
|
589
592
|
this._fallbackToIMSC1(frag, payload);
|
590
593
|
}
|
591
594
|
// Something went wrong while parsing. Trigger event with success false.
|
592
|
-
|
595
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
593
596
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
594
597
|
return;
|
595
598
|
}
|
@@ -661,7 +664,9 @@ export class TimelineController implements ComponentAPI {
|
|
661
664
|
event: Events.FRAG_PARSING_USERDATA,
|
662
665
|
data: FragParsingUserdataData,
|
663
666
|
) {
|
664
|
-
|
667
|
+
this.initCea608Parsers();
|
668
|
+
const { cea608Parser1, cea608Parser2 } = this;
|
669
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
665
670
|
return;
|
666
671
|
}
|
667
672
|
const { frag, samples } = data;
|
@@ -676,12 +681,9 @@ export class TimelineController implements ComponentAPI {
|
|
676
681
|
for (let i = 0; i < samples.length; i++) {
|
677
682
|
const ccBytes = samples[i].bytes;
|
678
683
|
if (ccBytes) {
|
679
|
-
if (!this.cea608Parser1) {
|
680
|
-
this.initCea608Parsers();
|
681
|
-
}
|
682
684
|
const ccdatas = this.extractCea608Data(ccBytes);
|
683
|
-
|
684
|
-
|
685
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
686
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
685
687
|
}
|
686
688
|
}
|
687
689
|
}
|