hls.js 1.5.13 → 1.5.14-0.canary.10417
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +2569 -1639
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +3572 -2017
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +38 -38
- package/src/config.ts +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +8 -16
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
@@ -9,12 +9,16 @@ 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';
|
15
19
|
import type KeyLoader from '../loader/key-loader';
|
16
20
|
import type { LevelDetails } from '../loader/level-details';
|
17
|
-
import type { Fragment } from '../loader/fragment';
|
21
|
+
import type { Fragment, MediaFragment } from '../loader/fragment';
|
18
22
|
import type {
|
19
23
|
ErrorData,
|
20
24
|
FragLoadedData,
|
@@ -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,26 +105,28 @@ export class SubtitleStreamController
|
|
107
105
|
this.tick();
|
108
106
|
}
|
109
107
|
|
110
|
-
onManifestLoading() {
|
108
|
+
protected onManifestLoading() {
|
109
|
+
super.onManifestLoading();
|
111
110
|
this.mainDetails = null;
|
112
|
-
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
|
) {
|
128
126
|
const { frag, success } = data;
|
129
|
-
|
127
|
+
if (frag.sn !== 'initSegment') {
|
128
|
+
this.fragPrevious = frag as MediaFragment;
|
129
|
+
}
|
130
130
|
this.state = State.IDLE;
|
131
131
|
if (!success) {
|
132
132
|
return;
|
@@ -158,11 +158,14 @@ export class SubtitleStreamController
|
|
158
158
|
};
|
159
159
|
buffered.push(timeRange);
|
160
160
|
}
|
161
|
-
this.fragmentTracker.fragBuffered(frag);
|
161
|
+
this.fragmentTracker.fragBuffered(frag as MediaFragment);
|
162
162
|
this.fragBufferedComplete(frag, null);
|
163
163
|
}
|
164
164
|
|
165
|
-
onBufferFlushing(
|
165
|
+
private onBufferFlushing(
|
166
|
+
event: Events.BUFFER_FLUSHING,
|
167
|
+
data: BufferFlushingData,
|
168
|
+
) {
|
166
169
|
const { startOffset, endOffset } = data;
|
167
170
|
if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
|
168
171
|
const endOffsetSubtitles = endOffset - 1;
|
@@ -191,7 +194,7 @@ export class SubtitleStreamController
|
|
191
194
|
}
|
192
195
|
}
|
193
196
|
|
194
|
-
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
197
|
+
private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
195
198
|
if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) {
|
196
199
|
if (this.media?.buffered.length) {
|
197
200
|
this.loadedmetadata = true;
|
@@ -200,12 +203,12 @@ export class SubtitleStreamController
|
|
200
203
|
}
|
201
204
|
|
202
205
|
// If something goes wrong, proceed to next frag, if we were processing one.
|
203
|
-
onError(event: Events.ERROR, data: ErrorData) {
|
206
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
204
207
|
const frag = data.frag;
|
205
208
|
|
206
209
|
if (frag?.type === PlaylistLevelType.SUBTITLE) {
|
207
210
|
if (data.details === ErrorDetails.FRAG_GAP) {
|
208
|
-
this.fragmentTracker.fragBuffered(frag, true);
|
211
|
+
this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
|
209
212
|
}
|
210
213
|
if (this.fragCurrent) {
|
211
214
|
this.fragCurrent.abortRequests();
|
@@ -217,7 +220,7 @@ export class SubtitleStreamController
|
|
217
220
|
}
|
218
221
|
|
219
222
|
// Got all new subtitle levels.
|
220
|
-
onSubtitleTracksUpdated(
|
223
|
+
private onSubtitleTracksUpdated(
|
221
224
|
event: Events.SUBTITLE_TRACKS_UPDATED,
|
222
225
|
{ subtitleTracks }: SubtitleTracksUpdatedData,
|
223
226
|
) {
|
@@ -242,7 +245,7 @@ export class SubtitleStreamController
|
|
242
245
|
this.mediaBuffer = null;
|
243
246
|
}
|
244
247
|
|
245
|
-
onSubtitleTrackSwitch(
|
248
|
+
private onSubtitleTrackSwitch(
|
246
249
|
event: Events.SUBTITLE_TRACK_SWITCH,
|
247
250
|
data: TrackSwitchedData,
|
248
251
|
) {
|
@@ -260,13 +263,13 @@ export class SubtitleStreamController
|
|
260
263
|
} else {
|
261
264
|
this.mediaBuffer = null;
|
262
265
|
}
|
263
|
-
if (currentTrack) {
|
266
|
+
if (currentTrack && this.state !== State.STOPPED) {
|
264
267
|
this.setInterval(TICK_INTERVAL);
|
265
268
|
}
|
266
269
|
}
|
267
270
|
|
268
271
|
// Got a new set of subtitle fragments.
|
269
|
-
onSubtitleTrackLoaded(
|
272
|
+
private onSubtitleTrackLoaded(
|
270
273
|
event: Events.SUBTITLE_TRACK_LOADED,
|
271
274
|
data: TrackLoadedData,
|
272
275
|
) {
|
@@ -367,7 +370,7 @@ export class SubtitleStreamController
|
|
367
370
|
payload.byteLength > 0 &&
|
368
371
|
decryptData?.key &&
|
369
372
|
decryptData.iv &&
|
370
|
-
decryptData.method
|
373
|
+
isFullSegmentEncryption(decryptData.method)
|
371
374
|
) {
|
372
375
|
const startTime = performance.now();
|
373
376
|
// decrypt the subtitles
|
@@ -376,6 +379,7 @@ export class SubtitleStreamController
|
|
376
379
|
new Uint8Array(payload),
|
377
380
|
decryptData.key.buffer,
|
378
381
|
decryptData.iv.buffer,
|
382
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
379
383
|
)
|
380
384
|
.catch((err) => {
|
381
385
|
hls.trigger(Events.ERROR, {
|
@@ -426,15 +430,9 @@ export class SubtitleStreamController
|
|
426
430
|
config.maxBufferHole,
|
427
431
|
);
|
428
432
|
const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
|
429
|
-
|
430
|
-
const mainBufferInfo = this.getFwdBufferInfo(
|
431
|
-
this.media,
|
432
|
-
PlaylistLevelType.MAIN,
|
433
|
-
);
|
434
433
|
const trackDetails = track.details as LevelDetails;
|
435
434
|
const maxBufLen =
|
436
|
-
this.
|
437
|
-
trackDetails.levelTargetDuration;
|
435
|
+
this.hls.maxBufferLength + trackDetails.levelTargetDuration;
|
438
436
|
|
439
437
|
if (bufferLen > maxBufLen) {
|
440
438
|
return;
|
@@ -490,24 +488,14 @@ export class SubtitleStreamController
|
|
490
488
|
}
|
491
489
|
}
|
492
490
|
|
493
|
-
protected getMaxBufferLength(mainBufferLength?: number): number {
|
494
|
-
const maxConfigBuffer = super.getMaxBufferLength();
|
495
|
-
if (!mainBufferLength) {
|
496
|
-
return maxConfigBuffer;
|
497
|
-
}
|
498
|
-
return Math.max(maxConfigBuffer, mainBufferLength);
|
499
|
-
}
|
500
|
-
|
501
491
|
protected loadFragment(
|
502
492
|
frag: Fragment,
|
503
493
|
level: Level,
|
504
494
|
targetBufferTime: number,
|
505
495
|
) {
|
506
|
-
this.fragCurrent = frag;
|
507
496
|
if (frag.sn === 'initSegment') {
|
508
497
|
this._loadInitSegment(frag, level);
|
509
498
|
} else {
|
510
|
-
this.startFragRequested = true;
|
511
499
|
super.loadFragment(frag, level, targetBufferTime);
|
512
500
|
}
|
513
501
|
}
|
@@ -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(
|
@@ -198,7 +192,7 @@ export class TimelineController implements ComponentAPI {
|
|
198
192
|
{ frag, id, initPTS, timescale }: InitPTSFoundData,
|
199
193
|
) {
|
200
194
|
const { unparsedVttFrags } = this;
|
201
|
-
if (id ===
|
195
|
+
if (id === PlaylistLevelType.MAIN) {
|
202
196
|
this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
|
203
197
|
}
|
204
198
|
|
@@ -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
|
}
|
package/src/crypt/aes-crypto.ts
CHANGED
@@ -1,13 +1,32 @@
|
|
1
|
+
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
+
|
1
3
|
export default class AESCrypto {
|
2
4
|
private subtle: SubtleCrypto;
|
3
5
|
private aesIV: Uint8Array;
|
6
|
+
private aesMode: DecrypterAesMode;
|
4
7
|
|
5
|
-
constructor(subtle: SubtleCrypto, iv: Uint8Array) {
|
8
|
+
constructor(subtle: SubtleCrypto, iv: Uint8Array, aesMode: DecrypterAesMode) {
|
6
9
|
this.subtle = subtle;
|
7
10
|
this.aesIV = iv;
|
11
|
+
this.aesMode = aesMode;
|
8
12
|
}
|
9
13
|
|
10
14
|
decrypt(data: ArrayBuffer, key: CryptoKey) {
|
11
|
-
|
15
|
+
switch (this.aesMode) {
|
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
|
+
}
|
12
31
|
}
|
13
32
|
}
|
package/src/crypt/decrypter.ts
CHANGED
@@ -4,6 +4,7 @@ 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';
|
7
8
|
import type { HlsConfig } from '../config';
|
8
9
|
|
9
10
|
const CHUNK_SIZE = 16; // 16 bytes, 128 bits
|
@@ -19,9 +20,10 @@ export default class Decrypter {
|
|
19
20
|
private currentIV: ArrayBuffer | null = null;
|
20
21
|
private currentResult: ArrayBuffer | null = null;
|
21
22
|
private useSoftware: boolean;
|
23
|
+
private enableSoftwareAES: boolean;
|
22
24
|
|
23
25
|
constructor(config: HlsConfig, { removePKCS7Padding = true } = {}) {
|
24
|
-
this.
|
26
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
25
27
|
this.removePKCS7Padding = removePKCS7Padding;
|
26
28
|
// built in decryptor expects PKCS7 padding
|
27
29
|
if (removePKCS7Padding) {
|
@@ -36,7 +38,6 @@ export default class Decrypter {
|
|
36
38
|
/* no-op */
|
37
39
|
}
|
38
40
|
}
|
39
|
-
|
40
41
|
this.useSoftware = !this.subtle;
|
41
42
|
}
|
42
43
|
|
@@ -81,10 +82,11 @@ export default class Decrypter {
|
|
81
82
|
data: Uint8Array | ArrayBuffer,
|
82
83
|
key: ArrayBuffer,
|
83
84
|
iv: ArrayBuffer,
|
85
|
+
aesMode: DecrypterAesMode,
|
84
86
|
): Promise<ArrayBuffer> {
|
85
87
|
if (this.useSoftware) {
|
86
88
|
return new Promise((resolve, reject) => {
|
87
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
89
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
88
90
|
const decryptResult = this.flush();
|
89
91
|
if (decryptResult) {
|
90
92
|
resolve(decryptResult.buffer);
|
@@ -93,7 +95,7 @@ export default class Decrypter {
|
|
93
95
|
}
|
94
96
|
});
|
95
97
|
}
|
96
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
98
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
97
99
|
}
|
98
100
|
|
99
101
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -102,8 +104,13 @@ export default class Decrypter {
|
|
102
104
|
data: Uint8Array,
|
103
105
|
key: ArrayBuffer,
|
104
106
|
iv: ArrayBuffer,
|
107
|
+
aesMode: DecrypterAesMode,
|
105
108
|
): ArrayBuffer | null {
|
106
109
|
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
|
+
}
|
107
114
|
this.logOnce('JS AES decrypt');
|
108
115
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
109
116
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -146,23 +153,24 @@ export default class Decrypter {
|
|
146
153
|
data: Uint8Array,
|
147
154
|
key: ArrayBuffer,
|
148
155
|
iv: ArrayBuffer,
|
156
|
+
aesMode: DecrypterAesMode,
|
149
157
|
): Promise<ArrayBuffer> {
|
150
158
|
if (this.key !== key || !this.fastAesKey) {
|
151
159
|
if (!this.subtle) {
|
152
|
-
return Promise.resolve(this.onWebCryptoError(data, key, iv));
|
160
|
+
return Promise.resolve(this.onWebCryptoError(data, key, iv, aesMode));
|
153
161
|
}
|
154
162
|
this.key = key;
|
155
|
-
this.fastAesKey = new FastAESKey(this.subtle, key);
|
163
|
+
this.fastAesKey = new FastAESKey(this.subtle, key, aesMode);
|
156
164
|
}
|
157
165
|
return this.fastAesKey
|
158
166
|
.expandKey()
|
159
|
-
.then((aesKey) => {
|
167
|
+
.then((aesKey: CryptoKey) => {
|
160
168
|
// decrypt using web crypto
|
161
169
|
if (!this.subtle) {
|
162
170
|
return Promise.reject(new Error('web crypto not initialized'));
|
163
171
|
}
|
164
172
|
this.logOnce('WebCrypto AES decrypt');
|
165
|
-
const crypto = new AESCrypto(this.subtle, new Uint8Array(iv));
|
173
|
+
const crypto = new AESCrypto(this.subtle, new Uint8Array(iv), aesMode);
|
166
174
|
return crypto.decrypt(data.buffer, aesKey);
|
167
175
|
})
|
168
176
|
.catch((err) => {
|
@@ -170,7 +178,7 @@ export default class Decrypter {
|
|
170
178
|
`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
|
171
179
|
);
|
172
180
|
|
173
|
-
return this.onWebCryptoError(data, key, iv);
|
181
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
174
182
|
});
|
175
183
|
}
|
176
184
|
|
@@ -178,15 +186,23 @@ export default class Decrypter {
|
|
178
186
|
data: Uint8Array,
|
179
187
|
key: ArrayBuffer,
|
180
188
|
iv: ArrayBuffer,
|
189
|
+
aesMode: DecrypterAesMode,
|
181
190
|
): ArrayBuffer | never {
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
191
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
192
|
+
if (enableSoftwareAES) {
|
193
|
+
this.useSoftware = true;
|
194
|
+
this.logEnabled = true;
|
195
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
196
|
+
const decryptResult = this.flush();
|
197
|
+
if (decryptResult) {
|
198
|
+
return decryptResult.buffer;
|
199
|
+
}
|
188
200
|
}
|
189
|
-
throw new Error(
|
201
|
+
throw new Error(
|
202
|
+
'WebCrypto' +
|
203
|
+
(enableSoftwareAES ? ' and softwareDecrypt' : '') +
|
204
|
+
': failed to decrypt data',
|
205
|
+
);
|
190
206
|
}
|
191
207
|
|
192
208
|
private getValidChunk(data: Uint8Array): Uint8Array {
|
@@ -1,16 +1,39 @@
|
|
1
|
+
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
+
|
1
3
|
export default class FastAESKey {
|
2
4
|
private subtle: SubtleCrypto;
|
3
5
|
private key: ArrayBuffer;
|
6
|
+
private aesMode: DecrypterAesMode;
|
4
7
|
|
5
|
-
constructor(
|
8
|
+
constructor(
|
9
|
+
subtle: SubtleCrypto,
|
10
|
+
key: ArrayBuffer,
|
11
|
+
aesMode: DecrypterAesMode,
|
12
|
+
) {
|
6
13
|
this.subtle = subtle;
|
7
14
|
this.key = key;
|
15
|
+
this.aesMode = aesMode;
|
8
16
|
}
|
9
17
|
|
10
18
|
expandKey() {
|
11
|
-
|
12
|
-
|
13
|
-
'
|
14
|
-
|
19
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
20
|
+
return this.subtle.importKey(
|
21
|
+
'raw',
|
22
|
+
this.key,
|
23
|
+
{ name: subtleAlgoName },
|
24
|
+
false,
|
25
|
+
['encrypt', 'decrypt'],
|
26
|
+
);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
function getSubtleAlgoName(aesMode: DecrypterAesMode) {
|
31
|
+
switch (aesMode) {
|
32
|
+
case DecrypterAesMode.cbc:
|
33
|
+
return 'AES-CBC';
|
34
|
+
case DecrypterAesMode.ctr:
|
35
|
+
return 'AES-CTR';
|
36
|
+
default:
|
37
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
15
38
|
}
|
16
39
|
}
|
@@ -5,7 +5,7 @@ import BaseAudioDemuxer from './base-audio-demuxer';
|
|
5
5
|
import * as ADTS from './adts';
|
6
6
|
import * as MpegAudio from './mpegaudio';
|
7
7
|
import { logger } from '../../utils/logger';
|
8
|
-
import
|
8
|
+
import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
|
9
9
|
import type { HlsEventEmitter } from '../../events';
|
10
10
|
import type { HlsConfig } from '../../config';
|
11
11
|
|
@@ -51,7 +51,7 @@ class AACDemuxer extends BaseAudioDemuxer {
|
|
51
51
|
// Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
|
52
52
|
// Layer bits (position 14 and 15) in header should be always 0 for ADTS
|
53
53
|
// More info https://wiki.multimedia.cx/index.php?title=ADTS
|
54
|
-
const id3Data =
|
54
|
+
const id3Data = getId3Data(data, 0);
|
55
55
|
let offset = id3Data?.length || 0;
|
56
56
|
|
57
57
|
if (MpegAudio.probe(data, offset)) {
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import BaseAudioDemuxer from './base-audio-demuxer';
|
2
|
-
import {
|
2
|
+
import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
|
3
|
+
import { getId3Timestamp } from '@svta/common-media-library/id3/getId3Timestamp';
|
3
4
|
import { getAudioBSID } from './dolby';
|
4
5
|
import type { HlsEventEmitter } from '../../events';
|
5
6
|
import type { AudioFrame, DemuxedAudioTrack } from '../../types/demuxer';
|
@@ -61,7 +62,7 @@ export class AC3Demuxer extends BaseAudioDemuxer {
|
|
61
62
|
return false;
|
62
63
|
}
|
63
64
|
|
64
|
-
const id3Data =
|
65
|
+
const id3Data = getId3Data(data, 0);
|
65
66
|
if (!id3Data) {
|
66
67
|
return false;
|
67
68
|
}
|
@@ -71,7 +72,7 @@ export class AC3Demuxer extends BaseAudioDemuxer {
|
|
71
72
|
if (
|
72
73
|
data[offset] === 0x0b &&
|
73
74
|
data[offset + 1] === 0x77 &&
|
74
|
-
|
75
|
+
getId3Timestamp(id3Data) !== undefined &&
|
75
76
|
// check the bsid to confirm ac-3
|
76
77
|
getAudioBSID(data, offset) < 16
|
77
78
|
) {
|