hls.js 1.5.10 → 1.5.11-0.canary.10330
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 +3477 -2195
- package/dist/hls.js.d.ts +108 -85
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2401 -1754
- 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 +2148 -1476
- 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 +2863 -1558
- 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 +3 -2
- package/src/controller/abr-controller.ts +24 -20
- package/src/controller/audio-stream-controller.ts +68 -74
- 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 +160 -38
- package/src/controller/buffer-controller.ts +230 -92
- 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 +6 -8
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-tracker.ts +15 -11
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +7 -7
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +37 -32
- package/src/controller/subtitle-stream-controller.ts +28 -40
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +19 -21
- 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 +4 -12
- 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 +208 -119
- package/src/demux/video/base-video-parser.ts +147 -18
- 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 +61 -38
- package/src/loader/fragment-loader.ts +10 -3
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +24 -8
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +4 -0
- package/src/types/events.ts +4 -0
- package/src/types/remuxer.ts +1 -1
- 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/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/webvtt-parser.ts +1 -1
- package/src/demux/id3.ts +0 -411
@@ -6,7 +6,6 @@ import type {
|
|
6
6
|
LevelUpdatedData,
|
7
7
|
MediaAttachingData,
|
8
8
|
} from '../types/events';
|
9
|
-
import { logger } from '../utils/logger';
|
10
9
|
import type { ComponentAPI } from '../types/component-api';
|
11
10
|
import type Hls from '../hls';
|
12
11
|
import type { HlsConfig } from '../config';
|
@@ -19,7 +18,6 @@ export default class LatencyController implements ComponentAPI {
|
|
19
18
|
private currentTime: number = 0;
|
20
19
|
private stallCount: number = 0;
|
21
20
|
private _latency: number | null = null;
|
22
|
-
private timeupdateHandler = () => this.timeupdate();
|
23
21
|
|
24
22
|
constructor(hls: Hls) {
|
25
23
|
this.hls = hls;
|
@@ -126,7 +124,7 @@ export default class LatencyController implements ComponentAPI {
|
|
126
124
|
this.onMediaDetaching();
|
127
125
|
this.levelDetails = null;
|
128
126
|
// @ts-ignore
|
129
|
-
this.hls =
|
127
|
+
this.hls = null;
|
130
128
|
}
|
131
129
|
|
132
130
|
private registerListeners() {
|
@@ -150,12 +148,12 @@ export default class LatencyController implements ComponentAPI {
|
|
150
148
|
data: MediaAttachingData,
|
151
149
|
) {
|
152
150
|
this.media = data.media;
|
153
|
-
this.media.addEventListener('timeupdate', this.
|
151
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
154
152
|
}
|
155
153
|
|
156
154
|
private onMediaDetaching() {
|
157
155
|
if (this.media) {
|
158
|
-
this.media.removeEventListener('timeupdate', this.
|
156
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
159
157
|
this.media = null;
|
160
158
|
}
|
161
159
|
}
|
@@ -172,10 +170,10 @@ export default class LatencyController implements ComponentAPI {
|
|
172
170
|
) {
|
173
171
|
this.levelDetails = details;
|
174
172
|
if (details.advanced) {
|
175
|
-
this.
|
173
|
+
this.onTimeupdate();
|
176
174
|
}
|
177
175
|
if (!details.live && this.media) {
|
178
|
-
this.media.removeEventListener('timeupdate', this.
|
176
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
179
177
|
}
|
180
178
|
}
|
181
179
|
|
@@ -185,13 +183,13 @@ export default class LatencyController implements ComponentAPI {
|
|
185
183
|
}
|
186
184
|
this.stallCount++;
|
187
185
|
if (this.levelDetails?.live) {
|
188
|
-
logger.warn(
|
189
|
-
'[
|
186
|
+
this.hls.logger.warn(
|
187
|
+
'[latency-controller]: Stall detected, adjusting target latency',
|
190
188
|
);
|
191
189
|
}
|
192
190
|
}
|
193
191
|
|
194
|
-
private
|
192
|
+
private onTimeupdate = () => {
|
195
193
|
const { media, levelDetails } = this;
|
196
194
|
if (!media || !levelDetails) {
|
197
195
|
return;
|
@@ -242,7 +240,7 @@ export default class LatencyController implements ComponentAPI {
|
|
242
240
|
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
243
241
|
media.playbackRate = 1;
|
244
242
|
}
|
245
|
-
}
|
243
|
+
};
|
246
244
|
|
247
245
|
private estimateLiveEdge(): number | null {
|
248
246
|
const { levelDetails } = this;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import type {
|
2
2
|
ManifestLoadedData,
|
3
3
|
ManifestParsedData,
|
4
4
|
LevelLoadedData,
|
@@ -27,8 +27,6 @@ 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
|
-
|
32
30
|
export default class LevelController extends BasePlaylistController {
|
33
31
|
private _levels: Level[] = [];
|
34
32
|
private _firstLevel: number = -1;
|
@@ -45,7 +43,7 @@ export default class LevelController extends BasePlaylistController {
|
|
45
43
|
hls: Hls,
|
46
44
|
contentSteeringController: ContentSteeringController | null,
|
47
45
|
) {
|
48
|
-
super(hls, '
|
46
|
+
super(hls, 'level-controller');
|
49
47
|
this.steering = contentSteeringController;
|
50
48
|
this._registerListeners();
|
51
49
|
}
|
@@ -119,22 +117,12 @@ export default class LevelController extends BasePlaylistController {
|
|
119
117
|
|
120
118
|
data.levels.forEach((levelParsed: LevelParsed) => {
|
121
119
|
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
|
125
120
|
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
|
-
|
133
121
|
if (audioCodec) {
|
134
|
-
|
135
|
-
|
136
|
-
preferManagedMediaSource
|
137
|
-
|
122
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
123
|
+
levelParsed.audioCodec = audioCodec =
|
124
|
+
getCodecCompatibleName(audioCodec, preferManagedMediaSource) ||
|
125
|
+
undefined;
|
138
126
|
}
|
139
127
|
|
140
128
|
if (videoCodec?.indexOf('avc1') === 0) {
|
@@ -521,6 +509,30 @@ export default class LevelController extends BasePlaylistController {
|
|
521
509
|
this._startLevel = newLevel;
|
522
510
|
}
|
523
511
|
|
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
|
+
|
524
536
|
protected onError(event: Events.ERROR, data: ErrorData) {
|
525
537
|
if (data.fatal || !data.context) {
|
526
538
|
return;
|
@@ -572,7 +584,13 @@ export default class LevelController extends BasePlaylistController {
|
|
572
584
|
if (curLevel.fragmentError === 0) {
|
573
585
|
curLevel.loadError = 0;
|
574
586
|
}
|
575
|
-
|
587
|
+
// Ignore matching details populated by loading a Media Playlist directly
|
588
|
+
let previousDetails = curLevel.details;
|
589
|
+
if (previousDetails === data.details && previousDetails.advanced) {
|
590
|
+
previousDetails = undefined;
|
591
|
+
}
|
592
|
+
|
593
|
+
this.playlistLoaded(level, data, previousDetails);
|
576
594
|
} else if (data.deliveryDirectives?.skip) {
|
577
595
|
// received a delta playlist update that cannot be merged
|
578
596
|
details.deltaUpdateFailed = true;
|
@@ -49,8 +49,6 @@ 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;
|
54
52
|
private fragLastKbps: number = 0;
|
55
53
|
private couldBacktrack: boolean = false;
|
56
54
|
private backtrackFragment: Fragment | null = null;
|
@@ -66,17 +64,15 @@ export default class StreamController
|
|
66
64
|
hls,
|
67
65
|
fragmentTracker,
|
68
66
|
keyLoader,
|
69
|
-
'
|
67
|
+
'stream-controller',
|
70
68
|
PlaylistLevelType.MAIN,
|
71
69
|
);
|
72
|
-
this.
|
70
|
+
this.registerListeners();
|
73
71
|
}
|
74
72
|
|
75
|
-
|
73
|
+
protected registerListeners() {
|
74
|
+
super.registerListeners();
|
76
75
|
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);
|
80
76
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
81
77
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
82
78
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
@@ -85,7 +81,6 @@ export default class StreamController
|
|
85
81
|
this.onFragLoadEmergencyAborted,
|
86
82
|
this,
|
87
83
|
);
|
88
|
-
hls.on(Events.ERROR, this.onError, this);
|
89
84
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
90
85
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
91
86
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -94,11 +89,9 @@ export default class StreamController
|
|
94
89
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
95
90
|
}
|
96
91
|
|
97
|
-
protected
|
92
|
+
protected unregisterListeners() {
|
93
|
+
super.unregisterListeners();
|
98
94
|
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);
|
102
95
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
103
96
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
104
97
|
hls.off(
|
@@ -106,7 +99,6 @@ export default class StreamController
|
|
106
99
|
this.onFragLoadEmergencyAborted,
|
107
100
|
this,
|
108
101
|
);
|
109
|
-
hls.off(Events.ERROR, this.onError, this);
|
110
102
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
111
103
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
112
104
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -116,7 +108,9 @@ export default class StreamController
|
|
116
108
|
}
|
117
109
|
|
118
110
|
protected onHandlerDestroying() {
|
119
|
-
|
111
|
+
// @ts-ignore
|
112
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
113
|
+
this.unregisterListeners();
|
120
114
|
super.onHandlerDestroying();
|
121
115
|
}
|
122
116
|
|
@@ -220,6 +214,9 @@ export default class StreamController
|
|
220
214
|
}
|
221
215
|
|
222
216
|
private doTickIdle() {
|
217
|
+
if (!this.buffering) {
|
218
|
+
return;
|
219
|
+
}
|
223
220
|
const { hls, levelLastLoaded, levels, media } = this;
|
224
221
|
|
225
222
|
// if start level not parsed yet OR
|
@@ -516,10 +513,8 @@ export default class StreamController
|
|
516
513
|
) {
|
517
514
|
super.onMediaAttached(event, data);
|
518
515
|
const media = data.media;
|
519
|
-
|
520
|
-
|
521
|
-
media.addEventListener('playing', this.onvplaying as EventListener);
|
522
|
-
media.addEventListener('seeked', this.onvseeked as EventListener);
|
516
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
517
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
523
518
|
this.gapController = new GapController(
|
524
519
|
this.config,
|
525
520
|
media,
|
@@ -530,12 +525,11 @@ export default class StreamController
|
|
530
525
|
|
531
526
|
protected onMediaDetaching() {
|
532
527
|
const { media } = this;
|
533
|
-
if (media
|
534
|
-
media.removeEventListener('playing', this.
|
535
|
-
media.removeEventListener('seeked', this.
|
536
|
-
this.onvplaying = this.onvseeked = null;
|
537
|
-
this.videoBuffer = null;
|
528
|
+
if (media) {
|
529
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
530
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
538
531
|
}
|
532
|
+
this.videoBuffer = null;
|
539
533
|
this.fragPlaying = null;
|
540
534
|
if (this.gapController) {
|
541
535
|
this.gapController.destroy();
|
@@ -544,12 +538,12 @@ export default class StreamController
|
|
544
538
|
super.onMediaDetaching();
|
545
539
|
}
|
546
540
|
|
547
|
-
private onMediaPlaying() {
|
541
|
+
private onMediaPlaying = () => {
|
548
542
|
// tick to speed up FRAG_CHANGED triggering
|
549
543
|
this.tick();
|
550
|
-
}
|
544
|
+
};
|
551
545
|
|
552
|
-
private onMediaSeeked() {
|
546
|
+
private onMediaSeeked = () => {
|
553
547
|
const media = this.media;
|
554
548
|
const currentTime = media ? media.currentTime : null;
|
555
549
|
if (Number.isFinite(currentTime)) {
|
@@ -569,9 +563,9 @@ export default class StreamController
|
|
569
563
|
|
570
564
|
// tick to speed up FRAG_CHANGED triggering
|
571
565
|
this.tick();
|
572
|
-
}
|
566
|
+
};
|
573
567
|
|
574
|
-
|
568
|
+
protected onManifestLoading() {
|
575
569
|
// reset buffer on manifest loading
|
576
570
|
this.log('Trigger BUFFER_RESET');
|
577
571
|
this.hls.trigger(Events.BUFFER_RESET, undefined);
|
@@ -884,7 +878,7 @@ export default class StreamController
|
|
884
878
|
this.fragBufferedComplete(frag, part);
|
885
879
|
}
|
886
880
|
|
887
|
-
|
881
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
888
882
|
if (data.fatal) {
|
889
883
|
this.state = State.ERROR;
|
890
884
|
return;
|
@@ -942,8 +936,10 @@ export default class StreamController
|
|
942
936
|
|
943
937
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
944
938
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
945
|
-
const
|
946
|
-
|
939
|
+
const state = this.state;
|
940
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
941
|
+
const levelDetails = this.getLevelDetails();
|
942
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
947
943
|
}
|
948
944
|
|
949
945
|
this.lastCurrentTime = media.currentTime;
|
@@ -1351,6 +1347,15 @@ export default class StreamController
|
|
1351
1347
|
);
|
1352
1348
|
}
|
1353
1349
|
|
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
|
+
|
1354
1359
|
private backtrack(frag: Fragment) {
|
1355
1360
|
this.couldBacktrack = true;
|
1356
1361
|
// Causes findFragments to backtrack through fragments to find the keyframe
|
@@ -9,6 +9,10 @@ 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';
|
12
16
|
import type { NetworkComponentAPI } from '../types/component-api';
|
13
17
|
import type Hls from '../hls';
|
14
18
|
import type { FragmentTracker } from './fragment-tracker';
|
@@ -51,25 +55,22 @@ export class SubtitleStreamController
|
|
51
55
|
hls,
|
52
56
|
fragmentTracker,
|
53
57
|
keyLoader,
|
54
|
-
'
|
58
|
+
'subtitle-stream-controller',
|
55
59
|
PlaylistLevelType.SUBTITLE,
|
56
60
|
);
|
57
|
-
this.
|
61
|
+
this.registerListeners();
|
58
62
|
}
|
59
63
|
|
60
64
|
protected onHandlerDestroying() {
|
61
|
-
this.
|
65
|
+
this.unregisterListeners();
|
62
66
|
super.onHandlerDestroying();
|
63
67
|
this.mainDetails = null;
|
64
68
|
}
|
65
69
|
|
66
|
-
|
70
|
+
protected registerListeners() {
|
71
|
+
super.registerListeners();
|
67
72
|
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);
|
71
73
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
72
|
-
hls.on(Events.ERROR, this.onError, this);
|
73
74
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
74
75
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
75
76
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -78,13 +79,10 @@ export class SubtitleStreamController
|
|
78
79
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
79
80
|
}
|
80
81
|
|
81
|
-
|
82
|
+
protected unregisterListeners() {
|
83
|
+
super.unregisterListeners();
|
82
84
|
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);
|
86
85
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
87
|
-
hls.off(Events.ERROR, this.onError, this);
|
88
86
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
89
87
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
90
88
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -107,21 +105,21 @@ export class SubtitleStreamController
|
|
107
105
|
this.tick();
|
108
106
|
}
|
109
107
|
|
110
|
-
onManifestLoading() {
|
108
|
+
protected onManifestLoading() {
|
111
109
|
this.mainDetails = null;
|
112
110
|
this.fragmentTracker.removeAllFragments();
|
113
111
|
}
|
114
112
|
|
115
|
-
onMediaDetaching(): void {
|
113
|
+
protected onMediaDetaching(): void {
|
116
114
|
this.tracksBuffered = [];
|
117
115
|
super.onMediaDetaching();
|
118
116
|
}
|
119
117
|
|
120
|
-
onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
118
|
+
private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
121
119
|
this.mainDetails = data.details;
|
122
120
|
}
|
123
121
|
|
124
|
-
onSubtitleFragProcessed(
|
122
|
+
private onSubtitleFragProcessed(
|
125
123
|
event: Events.SUBTITLE_FRAG_PROCESSED,
|
126
124
|
data: SubtitleFragProcessed,
|
127
125
|
) {
|
@@ -162,7 +160,10 @@ export class SubtitleStreamController
|
|
162
160
|
this.fragBufferedComplete(frag, null);
|
163
161
|
}
|
164
162
|
|
165
|
-
onBufferFlushing(
|
163
|
+
private onBufferFlushing(
|
164
|
+
event: Events.BUFFER_FLUSHING,
|
165
|
+
data: BufferFlushingData,
|
166
|
+
) {
|
166
167
|
const { startOffset, endOffset } = data;
|
167
168
|
if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
|
168
169
|
const endOffsetSubtitles = endOffset - 1;
|
@@ -191,7 +192,7 @@ export class SubtitleStreamController
|
|
191
192
|
}
|
192
193
|
}
|
193
194
|
|
194
|
-
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
195
|
+
private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
195
196
|
if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) {
|
196
197
|
if (this.media?.buffered.length) {
|
197
198
|
this.loadedmetadata = true;
|
@@ -200,7 +201,7 @@ export class SubtitleStreamController
|
|
200
201
|
}
|
201
202
|
|
202
203
|
// If something goes wrong, proceed to next frag, if we were processing one.
|
203
|
-
onError(event: Events.ERROR, data: ErrorData) {
|
204
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
204
205
|
const frag = data.frag;
|
205
206
|
|
206
207
|
if (frag?.type === PlaylistLevelType.SUBTITLE) {
|
@@ -214,7 +215,7 @@ export class SubtitleStreamController
|
|
214
215
|
}
|
215
216
|
|
216
217
|
// Got all new subtitle levels.
|
217
|
-
onSubtitleTracksUpdated(
|
218
|
+
private onSubtitleTracksUpdated(
|
218
219
|
event: Events.SUBTITLE_TRACKS_UPDATED,
|
219
220
|
{ subtitleTracks }: SubtitleTracksUpdatedData,
|
220
221
|
) {
|
@@ -239,7 +240,7 @@ export class SubtitleStreamController
|
|
239
240
|
this.mediaBuffer = null;
|
240
241
|
}
|
241
242
|
|
242
|
-
onSubtitleTrackSwitch(
|
243
|
+
private onSubtitleTrackSwitch(
|
243
244
|
event: Events.SUBTITLE_TRACK_SWITCH,
|
244
245
|
data: TrackSwitchedData,
|
245
246
|
) {
|
@@ -257,13 +258,13 @@ export class SubtitleStreamController
|
|
257
258
|
} else {
|
258
259
|
this.mediaBuffer = null;
|
259
260
|
}
|
260
|
-
if (currentTrack) {
|
261
|
+
if (currentTrack && this.state !== State.STOPPED) {
|
261
262
|
this.setInterval(TICK_INTERVAL);
|
262
263
|
}
|
263
264
|
}
|
264
265
|
|
265
266
|
// Got a new set of subtitle fragments.
|
266
|
-
onSubtitleTrackLoaded(
|
267
|
+
private onSubtitleTrackLoaded(
|
267
268
|
event: Events.SUBTITLE_TRACK_LOADED,
|
268
269
|
data: TrackLoadedData,
|
269
270
|
) {
|
@@ -364,7 +365,7 @@ export class SubtitleStreamController
|
|
364
365
|
payload.byteLength > 0 &&
|
365
366
|
decryptData?.key &&
|
366
367
|
decryptData.iv &&
|
367
|
-
decryptData.method
|
368
|
+
isFullSegmentEncryption(decryptData.method)
|
368
369
|
) {
|
369
370
|
const startTime = performance.now();
|
370
371
|
// decrypt the subtitles
|
@@ -373,6 +374,7 @@ export class SubtitleStreamController
|
|
373
374
|
new Uint8Array(payload),
|
374
375
|
decryptData.key.buffer,
|
375
376
|
decryptData.iv.buffer,
|
377
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
376
378
|
)
|
377
379
|
.catch((err) => {
|
378
380
|
hls.trigger(Events.ERROR, {
|
@@ -423,15 +425,9 @@ export class SubtitleStreamController
|
|
423
425
|
config.maxBufferHole,
|
424
426
|
);
|
425
427
|
const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
|
426
|
-
|
427
|
-
const mainBufferInfo = this.getFwdBufferInfo(
|
428
|
-
this.media,
|
429
|
-
PlaylistLevelType.MAIN,
|
430
|
-
);
|
431
428
|
const trackDetails = track.details as LevelDetails;
|
432
429
|
const maxBufLen =
|
433
|
-
this.
|
434
|
-
trackDetails.levelTargetDuration;
|
430
|
+
this.hls.maxBufferLength + trackDetails.levelTargetDuration;
|
435
431
|
|
436
432
|
if (bufferLen > maxBufLen) {
|
437
433
|
return;
|
@@ -487,14 +483,6 @@ export class SubtitleStreamController
|
|
487
483
|
}
|
488
484
|
}
|
489
485
|
|
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
|
-
|
498
486
|
protected loadFragment(
|
499
487
|
frag: Fragment,
|
500
488
|
level: Level,
|
@@ -35,13 +35,14 @@ 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);
|
39
38
|
private useTextTrackPolling: boolean = false;
|
40
39
|
private subtitlePollingInterval: number = -1;
|
41
40
|
private _subtitleDisplay: boolean = true;
|
42
41
|
|
42
|
+
private asyncPollTrackChange = () => this.pollTrackChange(0);
|
43
|
+
|
43
44
|
constructor(hls: Hls) {
|
44
|
-
super(hls, '
|
45
|
+
super(hls, 'subtitle-track-controller');
|
45
46
|
this.registerListeners();
|
46
47
|
}
|
47
48
|
|
@@ -50,7 +51,8 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
50
51
|
this.tracks.length = 0;
|
51
52
|
this.tracksInGroup.length = 0;
|
52
53
|
this.currentTrack = null;
|
53
|
-
|
54
|
+
// @ts-ignore
|
55
|
+
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
54
56
|
super.destroy();
|
55
57
|
}
|
56
58
|
|
@@ -28,7 +28,6 @@ import type {
|
|
28
28
|
BufferFlushingData,
|
29
29
|
FragLoadingData,
|
30
30
|
} from '../types/events';
|
31
|
-
import { logger } from '../utils/logger';
|
32
31
|
import type Hls from '../hls';
|
33
32
|
import type { ComponentAPI } from '../types/component-api';
|
34
33
|
import type { HlsConfig } from '../config';
|
@@ -131,22 +130,17 @@ export class TimelineController implements ComponentAPI {
|
|
131
130
|
hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
|
132
131
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
133
132
|
// @ts-ignore
|
134
|
-
this.hls = this.config = null;
|
133
|
+
this.hls = this.config = this.media = null;
|
135
134
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
136
135
|
}
|
137
136
|
|
138
137
|
private initCea608Parsers() {
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
)
|
143
|
-
|
144
|
-
|
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
|
-
}
|
138
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
139
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
140
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
141
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
142
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
143
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
150
144
|
}
|
151
145
|
|
152
146
|
public addCues(
|
@@ -221,6 +215,8 @@ export class TimelineController implements ComponentAPI {
|
|
221
215
|
canReuseVttTextTrack(textTrack, {
|
222
216
|
name: label,
|
223
217
|
lang: language,
|
218
|
+
characteristics:
|
219
|
+
'transcribes-spoken-dialog,describes-music-and-sound',
|
224
220
|
attrs: {} as any,
|
225
221
|
})
|
226
222
|
) {
|
@@ -309,6 +305,7 @@ export class TimelineController implements ComponentAPI {
|
|
309
305
|
delete captionsTracks[trackName];
|
310
306
|
});
|
311
307
|
this.nonNativeCaptionsTracks = {};
|
308
|
+
this.media = null;
|
312
309
|
}
|
313
310
|
|
314
311
|
private onManifestLoading() {
|
@@ -410,7 +407,7 @@ export class TimelineController implements ComponentAPI {
|
|
410
407
|
.filter((t) => t !== null)
|
411
408
|
.map((t) => (t as TextTrack).label);
|
412
409
|
if (unusedTextTracks.length) {
|
413
|
-
logger.warn(
|
410
|
+
this.hls.logger.warn(
|
414
411
|
`Media element contains unused subtitle tracks: ${unusedTextTracks.join(
|
415
412
|
', ',
|
416
413
|
)}. Replace media element for each source to clear TextTracks and captions menu.`,
|
@@ -545,7 +542,7 @@ export class TimelineController implements ComponentAPI {
|
|
545
542
|
});
|
546
543
|
},
|
547
544
|
(error) => {
|
548
|
-
logger.log(`Failed to parse IMSC1: ${error}`);
|
545
|
+
hls.logger.log(`Failed to parse IMSC1: ${error}`);
|
549
546
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
550
547
|
success: false,
|
551
548
|
frag: frag,
|
@@ -592,7 +589,7 @@ export class TimelineController implements ComponentAPI {
|
|
592
589
|
this._fallbackToIMSC1(frag, payload);
|
593
590
|
}
|
594
591
|
// Something went wrong while parsing. Trigger event with success false.
|
595
|
-
logger.log(`Failed to parse VTT cue: ${error}`);
|
592
|
+
hls.logger.log(`Failed to parse VTT cue: ${error}`);
|
596
593
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
597
594
|
return;
|
598
595
|
}
|
@@ -664,9 +661,7 @@ export class TimelineController implements ComponentAPI {
|
|
664
661
|
event: Events.FRAG_PARSING_USERDATA,
|
665
662
|
data: FragParsingUserdataData,
|
666
663
|
) {
|
667
|
-
this.
|
668
|
-
const { cea608Parser1, cea608Parser2 } = this;
|
669
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
664
|
+
if (!this.enabled || !this.config.enableCEA708Captions) {
|
670
665
|
return;
|
671
666
|
}
|
672
667
|
const { frag, samples } = data;
|
@@ -681,9 +676,12 @@ export class TimelineController implements ComponentAPI {
|
|
681
676
|
for (let i = 0; i < samples.length; i++) {
|
682
677
|
const ccBytes = samples[i].bytes;
|
683
678
|
if (ccBytes) {
|
679
|
+
if (!this.cea608Parser1) {
|
680
|
+
this.initCea608Parsers();
|
681
|
+
}
|
684
682
|
const ccdatas = this.extractCea608Data(ccBytes);
|
685
|
-
cea608Parser1
|
686
|
-
cea608Parser2
|
683
|
+
this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
|
684
|
+
this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
|
687
685
|
}
|
688
686
|
}
|
689
687
|
}
|