hls.js 1.5.7-0.canary.10042 → 1.5.7
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 +1 -2
- package/dist/hls-demo.js +0 -10
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1283 -2293
- package/dist/hls.js.d.ts +84 -97
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1030 -1435
- 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 +809 -1209
- 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 +1039 -2030
- 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 +22 -22
- 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 +8 -20
- package/src/controller/base-stream-controller.ts +36 -157
- package/src/controller/buffer-controller.ts +99 -226
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +6 -27
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +22 -9
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -2
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +18 -12
- package/src/controller/stream-controller.ts +31 -36
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +30 -23
- 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/adts.ts +4 -9
- 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 +0 -7
- package/src/hls.ts +37 -49
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- 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 +7 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +0 -2
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +0 -4
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/codecs.ts +5 -34
- package/src/utils/logger.ts +24 -54
- package/src/utils/mp4-tools.ts +2 -4
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -746
- package/src/utils/encryption-methods-util.ts +0 -21
@@ -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,9 +530,10 @@ 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;
|
531
537
|
this.videoBuffer = null;
|
532
538
|
}
|
533
539
|
this.fragPlaying = null;
|
@@ -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';
|
@@ -135,12 +136,17 @@ export class TimelineController implements ComponentAPI {
|
|
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(
|
@@ -404,7 +410,7 @@ export class TimelineController implements ComponentAPI {
|
|
404
410
|
.filter((t) => t !== null)
|
405
411
|
.map((t) => (t as TextTrack).label);
|
406
412
|
if (unusedTextTracks.length) {
|
407
|
-
|
413
|
+
logger.warn(
|
408
414
|
`Media element contains unused subtitle tracks: ${unusedTextTracks.join(
|
409
415
|
', ',
|
410
416
|
)}. Replace media element for each source to clear TextTracks and captions menu.`,
|
@@ -462,19 +468,21 @@ export class TimelineController implements ComponentAPI {
|
|
462
468
|
}
|
463
469
|
|
464
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
|
+
}
|
465
477
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
466
|
-
if (
|
467
|
-
const { cea608Parser1, cea608Parser2, lastSn } = this;
|
468
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
469
|
-
return;
|
470
|
-
}
|
478
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
471
479
|
const { cc, sn } = data.frag;
|
472
|
-
const partIndex = data
|
480
|
+
const partIndex = data?.part?.index ?? -1;
|
473
481
|
if (
|
474
482
|
!(
|
475
483
|
sn === lastSn + 1 ||
|
476
|
-
(sn === lastSn && partIndex ===
|
477
|
-
cc ===
|
484
|
+
(sn === lastSn && partIndex === lastPartIndex + 1) ||
|
485
|
+
cc === lastCc
|
478
486
|
)
|
479
487
|
) {
|
480
488
|
cea608Parser1.reset();
|
@@ -542,7 +550,7 @@ export class TimelineController implements ComponentAPI {
|
|
542
550
|
});
|
543
551
|
},
|
544
552
|
(error) => {
|
545
|
-
|
553
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
546
554
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
547
555
|
success: false,
|
548
556
|
frag: frag,
|
@@ -589,7 +597,7 @@ export class TimelineController implements ComponentAPI {
|
|
589
597
|
this._fallbackToIMSC1(frag, payload);
|
590
598
|
}
|
591
599
|
// Something went wrong while parsing. Trigger event with success false.
|
592
|
-
|
600
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
593
601
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
594
602
|
return;
|
595
603
|
}
|
@@ -661,7 +669,9 @@ export class TimelineController implements ComponentAPI {
|
|
661
669
|
event: Events.FRAG_PARSING_USERDATA,
|
662
670
|
data: FragParsingUserdataData,
|
663
671
|
) {
|
664
|
-
|
672
|
+
this.initCea608Parsers();
|
673
|
+
const { cea608Parser1, cea608Parser2 } = this;
|
674
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
665
675
|
return;
|
666
676
|
}
|
667
677
|
const { frag, samples } = data;
|
@@ -676,12 +686,9 @@ export class TimelineController implements ComponentAPI {
|
|
676
686
|
for (let i = 0; i < samples.length; i++) {
|
677
687
|
const ccBytes = samples[i].bytes;
|
678
688
|
if (ccBytes) {
|
679
|
-
if (!this.cea608Parser1) {
|
680
|
-
this.initCea608Parsers();
|
681
|
-
}
|
682
689
|
const ccdatas = this.extractCea608Data(ccBytes);
|
683
|
-
|
684
|
-
|
690
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
691
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
685
692
|
}
|
686
693
|
}
|
687
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
|
}
|
package/src/crypt/decrypter.ts
CHANGED
@@ -4,7 +4,6 @@ import AESDecryptor, { removePadding } from './aes-decryptor';
|
|
4
4
|
import { logger } from '../utils/logger';
|
5
5
|
import { appendUint8Array } from '../utils/mp4-tools';
|
6
6
|
import { sliceUint8 } from '../utils/typed-array';
|
7
|
-
import { DecrypterAesMode } from './decrypter-aes-mode';
|
8
7
|
import type { HlsConfig } from '../config';
|
9
8
|
|
10
9
|
const CHUNK_SIZE = 16; // 16 bytes, 128 bits
|
@@ -20,10 +19,9 @@ export default class Decrypter {
|
|
20
19
|
private currentIV: ArrayBuffer | null = null;
|
21
20
|
private currentResult: ArrayBuffer | null = null;
|
22
21
|
private useSoftware: boolean;
|
23
|
-
private enableSoftwareAES: boolean;
|
24
22
|
|
25
23
|
constructor(config: HlsConfig, { removePKCS7Padding = true } = {}) {
|
26
|
-
this.
|
24
|
+
this.useSoftware = config.enableSoftwareAES;
|
27
25
|
this.removePKCS7Padding = removePKCS7Padding;
|
28
26
|
// built in decryptor expects PKCS7 padding
|
29
27
|
if (removePKCS7Padding) {
|
@@ -38,7 +36,9 @@ export default class Decrypter {
|
|
38
36
|
/* no-op */
|
39
37
|
}
|
40
38
|
}
|
41
|
-
|
39
|
+
if (this.subtle === null) {
|
40
|
+
this.useSoftware = true;
|
41
|
+
}
|
42
42
|
}
|
43
43
|
|
44
44
|
destroy() {
|
@@ -82,11 +82,10 @@ export default class Decrypter {
|
|
82
82
|
data: Uint8Array | ArrayBuffer,
|
83
83
|
key: ArrayBuffer,
|
84
84
|
iv: ArrayBuffer,
|
85
|
-
aesMode: DecrypterAesMode,
|
86
85
|
): Promise<ArrayBuffer> {
|
87
86
|
if (this.useSoftware) {
|
88
87
|
return new Promise((resolve, reject) => {
|
89
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
88
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
90
89
|
const decryptResult = this.flush();
|
91
90
|
if (decryptResult) {
|
92
91
|
resolve(decryptResult.buffer);
|
@@ -95,7 +94,7 @@ export default class Decrypter {
|
|
95
94
|
}
|
96
95
|
});
|
97
96
|
}
|
98
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
97
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
99
98
|
}
|
100
99
|
|
101
100
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -104,13 +103,8 @@ export default class Decrypter {
|
|
104
103
|
data: Uint8Array,
|
105
104
|
key: ArrayBuffer,
|
106
105
|
iv: ArrayBuffer,
|
107
|
-
aesMode: DecrypterAesMode,
|
108
106
|
): ArrayBuffer | null {
|
109
107
|
const { currentIV, currentResult, remainderData } = this;
|
110
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
111
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
112
|
-
return null;
|
113
|
-
}
|
114
108
|
this.logOnce('JS AES decrypt');
|
115
109
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
116
110
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -153,22 +147,21 @@ export default class Decrypter {
|
|
153
147
|
data: Uint8Array,
|
154
148
|
key: ArrayBuffer,
|
155
149
|
iv: ArrayBuffer,
|
156
|
-
aesMode: DecrypterAesMode,
|
157
150
|
): Promise<ArrayBuffer> {
|
158
151
|
const subtle = this.subtle;
|
159
152
|
if (this.key !== key || !this.fastAesKey) {
|
160
153
|
this.key = key;
|
161
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
154
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
162
155
|
}
|
163
156
|
return this.fastAesKey
|
164
157
|
.expandKey()
|
165
|
-
.then((aesKey
|
158
|
+
.then((aesKey) => {
|
166
159
|
// decrypt using web crypto
|
167
160
|
if (!subtle) {
|
168
161
|
return Promise.reject(new Error('web crypto not initialized'));
|
169
162
|
}
|
170
163
|
this.logOnce('WebCrypto AES decrypt');
|
171
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
164
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
172
165
|
return crypto.decrypt(data.buffer, aesKey);
|
173
166
|
})
|
174
167
|
.catch((err) => {
|
@@ -176,26 +169,19 @@ export default class Decrypter {
|
|
176
169
|
`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
|
177
170
|
);
|
178
171
|
|
179
|
-
return this.onWebCryptoError(data, key, iv
|
172
|
+
return this.onWebCryptoError(data, key, iv);
|
180
173
|
});
|
181
174
|
}
|
182
175
|
|
183
|
-
private onWebCryptoError(data, key, iv
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
if (decryptResult) {
|
191
|
-
return decryptResult.buffer;
|
192
|
-
}
|
176
|
+
private onWebCryptoError(data, key, iv): ArrayBuffer | never {
|
177
|
+
this.useSoftware = true;
|
178
|
+
this.logEnabled = true;
|
179
|
+
this.softwareDecrypt(data, key, iv);
|
180
|
+
const decryptResult = this.flush();
|
181
|
+
if (decryptResult) {
|
182
|
+
return decryptResult.buffer;
|
193
183
|
}
|
194
|
-
throw new Error(
|
195
|
-
'WebCrypto' +
|
196
|
-
(enableSoftwareAES ? ' and softwareDecrypt' : '') +
|
197
|
-
': failed to decrypt data',
|
198
|
-
);
|
184
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
199
185
|
}
|
200
186
|
|
201
187
|
private getValidChunk(data: Uint8Array): Uint8Array {
|