hls.js 1.5.14-0.canary.10517 → 1.5.14
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 +2903 -4542
- package/dist/hls.js.d.ts +112 -186
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2284 -3295
- 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 +1804 -2817
- 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 +4652 -6293
- 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 +2 -5
- package/src/controller/abr-controller.ts +25 -39
- package/src/controller/audio-stream-controller.ts +136 -156
- 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 +107 -263
- package/src/controller/buffer-controller.ts +98 -252
- 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 +22 -28
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-finders.ts +16 -44
- package/src/controller/fragment-tracker.ts +25 -58
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +35 -45
- package/src/controller/latency-controller.ts +13 -18
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +83 -100
- package/src/controller/subtitle-stream-controller.ts +47 -35
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +22 -20
- 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 +5 -5
- package/src/demux/audio/ac3-demuxer.ts +4 -5
- 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/inject-worker.ts +4 -38
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +83 -106
- package/src/demux/transmuxer-worker.ts +77 -111
- package/src/demux/transmuxer.ts +22 -46
- package/src/demux/tsdemuxer.ts +62 -122
- package/src/demux/video/avc-video-parser.ts +121 -210
- package/src/demux/video/base-video-parser.ts +2 -135
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/errors.ts +0 -2
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +48 -97
- package/src/loader/date-range.ts +5 -71
- package/src/loader/fragment-loader.ts +21 -23
- package/src/loader/fragment.ts +4 -8
- package/src/loader/key-loader.ts +1 -3
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +9 -10
- package/src/loader/m3u8-parser.ts +144 -138
- package/src/loader/playlist-loader.ts +7 -5
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +84 -55
- package/src/remux/passthrough-remuxer.ts +8 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +6 -19
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/general.ts +6 -0
- package/src/types/media-playlist.ts +1 -9
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +9 -96
- 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/discontinuities.ts +47 -21
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hdr.ts +7 -4
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/level-helper.ts +44 -71
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/rendition-helper.ts +74 -100
- package/src/utils/variable-substitution.ts +19 -0
- package/src/utils/webvtt-parser.ts +12 -2
- 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/hash.ts +0 -10
- package/src/utils/utf8-utils.ts +0 -18
- package/src/version.ts +0 -1
@@ -9,16 +9,12 @@ 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';
|
19
15
|
import type KeyLoader from '../loader/key-loader';
|
20
16
|
import type { LevelDetails } from '../loader/level-details';
|
21
|
-
import type { Fragment
|
17
|
+
import type { Fragment } from '../loader/fragment';
|
22
18
|
import type {
|
23
19
|
ErrorData,
|
24
20
|
FragLoadedData,
|
@@ -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,28 +107,26 @@ export class SubtitleStreamController
|
|
105
107
|
this.tick();
|
106
108
|
}
|
107
109
|
|
108
|
-
|
109
|
-
super.onManifestLoading();
|
110
|
+
onManifestLoading() {
|
110
111
|
this.mainDetails = null;
|
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
|
) {
|
126
128
|
const { frag, success } = data;
|
127
|
-
|
128
|
-
this.fragPrevious = frag as MediaFragment;
|
129
|
-
}
|
129
|
+
this.fragPrevious = frag;
|
130
130
|
this.state = State.IDLE;
|
131
131
|
if (!success) {
|
132
132
|
return;
|
@@ -158,14 +158,11 @@ export class SubtitleStreamController
|
|
158
158
|
};
|
159
159
|
buffered.push(timeRange);
|
160
160
|
}
|
161
|
-
this.fragmentTracker.fragBuffered(frag
|
161
|
+
this.fragmentTracker.fragBuffered(frag);
|
162
162
|
this.fragBufferedComplete(frag, null);
|
163
163
|
}
|
164
164
|
|
165
|
-
|
166
|
-
event: Events.BUFFER_FLUSHING,
|
167
|
-
data: BufferFlushingData,
|
168
|
-
) {
|
165
|
+
onBufferFlushing(event: Events.BUFFER_FLUSHING, data: BufferFlushingData) {
|
169
166
|
const { startOffset, endOffset } = data;
|
170
167
|
if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
|
171
168
|
const endOffsetSubtitles = endOffset - 1;
|
@@ -194,7 +191,7 @@ export class SubtitleStreamController
|
|
194
191
|
}
|
195
192
|
}
|
196
193
|
|
197
|
-
|
194
|
+
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
198
195
|
if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) {
|
199
196
|
if (this.media?.buffered.length) {
|
200
197
|
this.loadedmetadata = true;
|
@@ -203,12 +200,12 @@ export class SubtitleStreamController
|
|
203
200
|
}
|
204
201
|
|
205
202
|
// If something goes wrong, proceed to next frag, if we were processing one.
|
206
|
-
|
203
|
+
onError(event: Events.ERROR, data: ErrorData) {
|
207
204
|
const frag = data.frag;
|
208
205
|
|
209
206
|
if (frag?.type === PlaylistLevelType.SUBTITLE) {
|
210
207
|
if (data.details === ErrorDetails.FRAG_GAP) {
|
211
|
-
this.fragmentTracker.fragBuffered(frag
|
208
|
+
this.fragmentTracker.fragBuffered(frag, true);
|
212
209
|
}
|
213
210
|
if (this.fragCurrent) {
|
214
211
|
this.fragCurrent.abortRequests();
|
@@ -220,7 +217,7 @@ export class SubtitleStreamController
|
|
220
217
|
}
|
221
218
|
|
222
219
|
// Got all new subtitle levels.
|
223
|
-
|
220
|
+
onSubtitleTracksUpdated(
|
224
221
|
event: Events.SUBTITLE_TRACKS_UPDATED,
|
225
222
|
{ subtitleTracks }: SubtitleTracksUpdatedData,
|
226
223
|
) {
|
@@ -245,7 +242,7 @@ export class SubtitleStreamController
|
|
245
242
|
this.mediaBuffer = null;
|
246
243
|
}
|
247
244
|
|
248
|
-
|
245
|
+
onSubtitleTrackSwitch(
|
249
246
|
event: Events.SUBTITLE_TRACK_SWITCH,
|
250
247
|
data: TrackSwitchedData,
|
251
248
|
) {
|
@@ -263,13 +260,13 @@ export class SubtitleStreamController
|
|
263
260
|
} else {
|
264
261
|
this.mediaBuffer = null;
|
265
262
|
}
|
266
|
-
if (currentTrack
|
263
|
+
if (currentTrack) {
|
267
264
|
this.setInterval(TICK_INTERVAL);
|
268
265
|
}
|
269
266
|
}
|
270
267
|
|
271
268
|
// Got a new set of subtitle fragments.
|
272
|
-
|
269
|
+
onSubtitleTrackLoaded(
|
273
270
|
event: Events.SUBTITLE_TRACK_LOADED,
|
274
271
|
data: TrackLoadedData,
|
275
272
|
) {
|
@@ -370,7 +367,7 @@ export class SubtitleStreamController
|
|
370
367
|
payload.byteLength > 0 &&
|
371
368
|
decryptData?.key &&
|
372
369
|
decryptData.iv &&
|
373
|
-
|
370
|
+
decryptData.method === 'AES-128'
|
374
371
|
) {
|
375
372
|
const startTime = performance.now();
|
376
373
|
// decrypt the subtitles
|
@@ -379,7 +376,6 @@ export class SubtitleStreamController
|
|
379
376
|
new Uint8Array(payload),
|
380
377
|
decryptData.key.buffer,
|
381
378
|
decryptData.iv.buffer,
|
382
|
-
getAesModeFromFullSegmentMethod(decryptData.method),
|
383
379
|
)
|
384
380
|
.catch((err) => {
|
385
381
|
hls.trigger(Events.ERROR, {
|
@@ -430,9 +426,15 @@ export class SubtitleStreamController
|
|
430
426
|
config.maxBufferHole,
|
431
427
|
);
|
432
428
|
const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
|
429
|
+
|
430
|
+
const mainBufferInfo = this.getFwdBufferInfo(
|
431
|
+
this.media,
|
432
|
+
PlaylistLevelType.MAIN,
|
433
|
+
);
|
433
434
|
const trackDetails = track.details as LevelDetails;
|
434
435
|
const maxBufLen =
|
435
|
-
this.
|
436
|
+
this.getMaxBufferLength(mainBufferInfo?.len) +
|
437
|
+
trackDetails.levelTargetDuration;
|
436
438
|
|
437
439
|
if (bufferLen > maxBufLen) {
|
438
440
|
return;
|
@@ -488,14 +490,24 @@ export class SubtitleStreamController
|
|
488
490
|
}
|
489
491
|
}
|
490
492
|
|
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
|
+
|
491
501
|
protected loadFragment(
|
492
502
|
frag: Fragment,
|
493
503
|
level: Level,
|
494
504
|
targetBufferTime: number,
|
495
505
|
) {
|
506
|
+
this.fragCurrent = frag;
|
496
507
|
if (frag.sn === 'initSegment') {
|
497
508
|
this._loadInitSegment(frag, level);
|
498
509
|
} else {
|
510
|
+
this.startFragRequested = true;
|
499
511
|
super.loadFragment(frag, level, targetBufferTime);
|
500
512
|
}
|
501
513
|
}
|
@@ -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(
|
@@ -192,7 +198,7 @@ export class TimelineController implements ComponentAPI {
|
|
192
198
|
{ frag, id, initPTS, timescale }: InitPTSFoundData,
|
193
199
|
) {
|
194
200
|
const { unparsedVttFrags } = this;
|
195
|
-
if (id ===
|
201
|
+
if (id === 'main') {
|
196
202
|
this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
|
197
203
|
}
|
198
204
|
|
@@ -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
|
}
|
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,6 +36,7 @@ export default class Decrypter {
|
|
38
36
|
/* no-op */
|
39
37
|
}
|
40
38
|
}
|
39
|
+
|
41
40
|
this.useSoftware = !this.subtle;
|
42
41
|
}
|
43
42
|
|
@@ -82,11 +81,10 @@ export default class Decrypter {
|
|
82
81
|
data: Uint8Array | ArrayBuffer,
|
83
82
|
key: ArrayBuffer,
|
84
83
|
iv: ArrayBuffer,
|
85
|
-
aesMode: DecrypterAesMode,
|
86
84
|
): Promise<ArrayBuffer> {
|
87
85
|
if (this.useSoftware) {
|
88
86
|
return new Promise((resolve, reject) => {
|
89
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
87
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
90
88
|
const decryptResult = this.flush();
|
91
89
|
if (decryptResult) {
|
92
90
|
resolve(decryptResult.buffer);
|
@@ -95,7 +93,7 @@ export default class Decrypter {
|
|
95
93
|
}
|
96
94
|
});
|
97
95
|
}
|
98
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
96
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
99
97
|
}
|
100
98
|
|
101
99
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -104,13 +102,8 @@ export default class Decrypter {
|
|
104
102
|
data: Uint8Array,
|
105
103
|
key: ArrayBuffer,
|
106
104
|
iv: ArrayBuffer,
|
107
|
-
aesMode: DecrypterAesMode,
|
108
105
|
): ArrayBuffer | null {
|
109
106
|
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
107
|
this.logOnce('JS AES decrypt');
|
115
108
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
116
109
|
// 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,24 +146,23 @@ export default class Decrypter {
|
|
153
146
|
data: Uint8Array,
|
154
147
|
key: ArrayBuffer,
|
155
148
|
iv: ArrayBuffer,
|
156
|
-
aesMode: DecrypterAesMode,
|
157
149
|
): Promise<ArrayBuffer> {
|
158
150
|
if (this.key !== key || !this.fastAesKey) {
|
159
151
|
if (!this.subtle) {
|
160
|
-
return Promise.resolve(this.onWebCryptoError(data, key, iv
|
152
|
+
return Promise.resolve(this.onWebCryptoError(data, key, iv));
|
161
153
|
}
|
162
154
|
this.key = key;
|
163
|
-
this.fastAesKey = new FastAESKey(this.subtle, key
|
155
|
+
this.fastAesKey = new FastAESKey(this.subtle, key);
|
164
156
|
}
|
165
157
|
return this.fastAesKey
|
166
158
|
.expandKey()
|
167
|
-
.then((aesKey
|
159
|
+
.then((aesKey) => {
|
168
160
|
// decrypt using web crypto
|
169
161
|
if (!this.subtle) {
|
170
162
|
return Promise.reject(new Error('web crypto not initialized'));
|
171
163
|
}
|
172
164
|
this.logOnce('WebCrypto AES decrypt');
|
173
|
-
const crypto = new AESCrypto(this.subtle, new Uint8Array(iv)
|
165
|
+
const crypto = new AESCrypto(this.subtle, new Uint8Array(iv));
|
174
166
|
return crypto.decrypt(data.buffer, aesKey);
|
175
167
|
})
|
176
168
|
.catch((err) => {
|
@@ -178,7 +170,7 @@ export default class Decrypter {
|
|
178
170
|
`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
|
179
171
|
);
|
180
172
|
|
181
|
-
return this.onWebCryptoError(data, key, iv
|
173
|
+
return this.onWebCryptoError(data, key, iv);
|
182
174
|
});
|
183
175
|
}
|
184
176
|
|
@@ -186,23 +178,15 @@ export default class Decrypter {
|
|
186
178
|
data: Uint8Array,
|
187
179
|
key: ArrayBuffer,
|
188
180
|
iv: ArrayBuffer,
|
189
|
-
aesMode: DecrypterAesMode,
|
190
181
|
): ArrayBuffer | never {
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
if (decryptResult) {
|
198
|
-
return decryptResult.buffer;
|
199
|
-
}
|
182
|
+
this.useSoftware = true;
|
183
|
+
this.logEnabled = true;
|
184
|
+
this.softwareDecrypt(data, key, iv);
|
185
|
+
const decryptResult = this.flush();
|
186
|
+
if (decryptResult) {
|
187
|
+
return decryptResult.buffer;
|
200
188
|
}
|
201
|
-
throw new Error(
|
202
|
-
'WebCrypto' +
|
203
|
-
(enableSoftwareAES ? ' and softwareDecrypt' : '') +
|
204
|
-
': failed to decrypt data',
|
205
|
-
);
|
189
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
206
190
|
}
|
207
191
|
|
208
192
|
private getValidChunk(data: Uint8Array): Uint8Array {
|
@@ -1,39 +1,16 @@
|
|
1
|
-
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
-
|
3
1
|
export default class FastAESKey {
|
4
2
|
private subtle: SubtleCrypto;
|
5
3
|
private key: ArrayBuffer;
|
6
|
-
private aesMode: DecrypterAesMode;
|
7
4
|
|
8
|
-
constructor(
|
9
|
-
subtle: SubtleCrypto,
|
10
|
-
key: ArrayBuffer,
|
11
|
-
aesMode: DecrypterAesMode,
|
12
|
-
) {
|
5
|
+
constructor(subtle: SubtleCrypto, key: ArrayBuffer) {
|
13
6
|
this.subtle = subtle;
|
14
7
|
this.key = key;
|
15
|
-
this.aesMode = aesMode;
|
16
8
|
}
|
17
9
|
|
18
10
|
expandKey() {
|
19
|
-
|
20
|
-
|
21
|
-
'
|
22
|
-
|
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}`);
|
11
|
+
return this.subtle.importKey('raw', this.key, { name: 'AES-CBC' }, false, [
|
12
|
+
'encrypt',
|
13
|
+
'decrypt',
|
14
|
+
]);
|
38
15
|
}
|
39
16
|
}
|
@@ -4,16 +4,16 @@
|
|
4
4
|
import BaseAudioDemuxer from './base-audio-demuxer';
|
5
5
|
import * as ADTS from './adts';
|
6
6
|
import * as MpegAudio from './mpegaudio';
|
7
|
-
import {
|
7
|
+
import { logger } from '../../utils/logger';
|
8
|
+
import * as ID3 from '../id3';
|
8
9
|
import type { HlsEventEmitter } from '../../events';
|
9
10
|
import type { HlsConfig } from '../../config';
|
10
|
-
import type { ILogger } from '../../utils/logger';
|
11
11
|
|
12
12
|
class AACDemuxer extends BaseAudioDemuxer {
|
13
13
|
private readonly observer: HlsEventEmitter;
|
14
14
|
private readonly config: HlsConfig;
|
15
15
|
|
16
|
-
constructor(observer
|
16
|
+
constructor(observer, config) {
|
17
17
|
super();
|
18
18
|
this.observer = observer;
|
19
19
|
this.config = config;
|
@@ -42,7 +42,7 @@ class AACDemuxer extends BaseAudioDemuxer {
|
|
42
42
|
}
|
43
43
|
|
44
44
|
// Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS
|
45
|
-
static probe(data: Uint8Array | undefined
|
45
|
+
static probe(data: Uint8Array | undefined): boolean {
|
46
46
|
if (!data) {
|
47
47
|
return false;
|
48
48
|
}
|
@@ -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 = ID3.getID3Data(data, 0);
|
55
55
|
let offset = id3Data?.length || 0;
|
56
56
|
|
57
57
|
if (MpegAudio.probe(data, offset)) {
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import BaseAudioDemuxer from './base-audio-demuxer';
|
2
|
-
import {
|
3
|
-
import { getId3Timestamp } from '@svta/common-media-library/id3/getId3Timestamp';
|
2
|
+
import { getID3Data, getTimeStamp } from '../id3';
|
4
3
|
import { getAudioBSID } from './dolby';
|
5
4
|
import type { HlsEventEmitter } from '../../events';
|
6
5
|
import type { AudioFrame, DemuxedAudioTrack } from '../../types/demuxer';
|
@@ -8,7 +7,7 @@ import type { AudioFrame, DemuxedAudioTrack } from '../../types/demuxer';
|
|
8
7
|
export class AC3Demuxer extends BaseAudioDemuxer {
|
9
8
|
private readonly observer: HlsEventEmitter;
|
10
9
|
|
11
|
-
constructor(observer
|
10
|
+
constructor(observer) {
|
12
11
|
super();
|
13
12
|
this.observer = observer;
|
14
13
|
}
|
@@ -62,7 +61,7 @@ export class AC3Demuxer extends BaseAudioDemuxer {
|
|
62
61
|
return false;
|
63
62
|
}
|
64
63
|
|
65
|
-
const id3Data =
|
64
|
+
const id3Data = getID3Data(data, 0);
|
66
65
|
if (!id3Data) {
|
67
66
|
return false;
|
68
67
|
}
|
@@ -72,7 +71,7 @@ export class AC3Demuxer extends BaseAudioDemuxer {
|
|
72
71
|
if (
|
73
72
|
data[offset] === 0x0b &&
|
74
73
|
data[offset + 1] === 0x77 &&
|
75
|
-
|
74
|
+
getTimeStamp(id3Data) !== undefined &&
|
76
75
|
// check the bsid to confirm ac-3
|
77
76
|
getAudioBSID(data, offset) < 16
|
78
77
|
) {
|