hls.js 1.5.7 → 1.5.8-0.canary.10044
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 +2 -1
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2314 -1298
- package/dist/hls.js.d.ts +97 -84
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1486 -1075
- 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 +1195 -789
- 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 +1979 -982
- 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 +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 +20 -8
- package/src/controller/base-stream-controller.ts +157 -36
- package/src/controller/buffer-controller.ts +203 -67
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +27 -6
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/fragment-tracker.ts +15 -11
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +36 -31
- package/src/controller/subtitle-stream-controller.ts +28 -40
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- 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 +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +49 -37
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- 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 +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
- package/src/utils/mp4-tools.ts +4 -2
@@ -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
|
) {
|
@@ -360,7 +361,7 @@ export class SubtitleStreamController
|
|
360
361
|
payload.byteLength > 0 &&
|
361
362
|
decryptData?.key &&
|
362
363
|
decryptData.iv &&
|
363
|
-
decryptData.method
|
364
|
+
isFullSegmentEncryption(decryptData.method)
|
364
365
|
) {
|
365
366
|
const startTime = performance.now();
|
366
367
|
// decrypt the subtitles
|
@@ -369,6 +370,7 @@ export class SubtitleStreamController
|
|
369
370
|
new Uint8Array(payload),
|
370
371
|
decryptData.key.buffer,
|
371
372
|
decryptData.iv.buffer,
|
373
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
372
374
|
)
|
373
375
|
.catch((err) => {
|
374
376
|
hls.trigger(Events.ERROR, {
|
@@ -419,15 +421,9 @@ export class SubtitleStreamController
|
|
419
421
|
config.maxBufferHole,
|
420
422
|
);
|
421
423
|
const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
|
422
|
-
|
423
|
-
const mainBufferInfo = this.getFwdBufferInfo(
|
424
|
-
this.media,
|
425
|
-
PlaylistLevelType.MAIN,
|
426
|
-
);
|
427
424
|
const trackDetails = track.details as LevelDetails;
|
428
425
|
const maxBufLen =
|
429
|
-
this.
|
430
|
-
trackDetails.levelTargetDuration;
|
426
|
+
this.hls.maxBufferLength + trackDetails.levelTargetDuration;
|
431
427
|
|
432
428
|
if (bufferLen > maxBufLen) {
|
433
429
|
return;
|
@@ -483,14 +479,6 @@ export class SubtitleStreamController
|
|
483
479
|
}
|
484
480
|
}
|
485
481
|
|
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
|
-
|
494
482
|
protected loadFragment(
|
495
483
|
frag: Fragment,
|
496
484
|
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';
|
@@ -136,17 +135,12 @@ export class TimelineController implements ComponentAPI {
|
|
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(
|
@@ -410,7 +404,7 @@ export class TimelineController implements ComponentAPI {
|
|
410
404
|
.filter((t) => t !== null)
|
411
405
|
.map((t) => (t as TextTrack).label);
|
412
406
|
if (unusedTextTracks.length) {
|
413
|
-
logger.warn(
|
407
|
+
this.hls.logger.warn(
|
414
408
|
`Media element contains unused subtitle tracks: ${unusedTextTracks.join(
|
415
409
|
', ',
|
416
410
|
)}. Replace media element for each source to clear TextTracks and captions menu.`,
|
@@ -468,21 +462,19 @@ export class TimelineController implements ComponentAPI {
|
|
468
462
|
}
|
469
463
|
|
470
464
|
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
|
-
}
|
477
465
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
478
|
-
if (data.frag.type === PlaylistLevelType.MAIN) {
|
466
|
+
if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
|
467
|
+
const { cea608Parser1, cea608Parser2, lastSn } = this;
|
468
|
+
if (!cea608Parser1 || !cea608Parser2) {
|
469
|
+
return;
|
470
|
+
}
|
479
471
|
const { cc, sn } = data.frag;
|
480
|
-
const partIndex = data
|
472
|
+
const partIndex = data.part?.index ?? -1;
|
481
473
|
if (
|
482
474
|
!(
|
483
475
|
sn === lastSn + 1 ||
|
484
|
-
(sn === lastSn && partIndex === lastPartIndex + 1) ||
|
485
|
-
cc === lastCc
|
476
|
+
(sn === lastSn && partIndex === this.lastPartIndex + 1) ||
|
477
|
+
cc === this.lastCc
|
486
478
|
)
|
487
479
|
) {
|
488
480
|
cea608Parser1.reset();
|
@@ -550,7 +542,7 @@ export class TimelineController implements ComponentAPI {
|
|
550
542
|
});
|
551
543
|
},
|
552
544
|
(error) => {
|
553
|
-
logger.log(`Failed to parse IMSC1: ${error}`);
|
545
|
+
hls.logger.log(`Failed to parse IMSC1: ${error}`);
|
554
546
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
555
547
|
success: false,
|
556
548
|
frag: frag,
|
@@ -597,7 +589,7 @@ export class TimelineController implements ComponentAPI {
|
|
597
589
|
this._fallbackToIMSC1(frag, payload);
|
598
590
|
}
|
599
591
|
// Something went wrong while parsing. Trigger event with success false.
|
600
|
-
logger.log(`Failed to parse VTT cue: ${error}`);
|
592
|
+
hls.logger.log(`Failed to parse VTT cue: ${error}`);
|
601
593
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
602
594
|
return;
|
603
595
|
}
|
@@ -669,9 +661,7 @@ export class TimelineController implements ComponentAPI {
|
|
669
661
|
event: Events.FRAG_PARSING_USERDATA,
|
670
662
|
data: FragParsingUserdataData,
|
671
663
|
) {
|
672
|
-
this.
|
673
|
-
const { cea608Parser1, cea608Parser2 } = this;
|
674
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
664
|
+
if (!this.enabled || !this.config.enableCEA708Captions) {
|
675
665
|
return;
|
676
666
|
}
|
677
667
|
const { frag, samples } = data;
|
@@ -686,9 +676,12 @@ export class TimelineController implements ComponentAPI {
|
|
686
676
|
for (let i = 0; i < samples.length; i++) {
|
687
677
|
const ccBytes = samples[i].bytes;
|
688
678
|
if (ccBytes) {
|
679
|
+
if (!this.cea608Parser1) {
|
680
|
+
this.initCea608Parsers();
|
681
|
+
}
|
689
682
|
const ccdatas = this.extractCea608Data(ccBytes);
|
690
|
-
cea608Parser1
|
691
|
-
cea608Parser2
|
683
|
+
this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
|
684
|
+
this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
|
692
685
|
}
|
693
686
|
}
|
694
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,9 +38,7 @@ export default class Decrypter {
|
|
36
38
|
/* no-op */
|
37
39
|
}
|
38
40
|
}
|
39
|
-
|
40
|
-
this.useSoftware = true;
|
41
|
-
}
|
41
|
+
this.useSoftware = this.subtle === null;
|
42
42
|
}
|
43
43
|
|
44
44
|
destroy() {
|
@@ -82,10 +82,11 @@ export default class Decrypter {
|
|
82
82
|
data: Uint8Array | ArrayBuffer,
|
83
83
|
key: ArrayBuffer,
|
84
84
|
iv: ArrayBuffer,
|
85
|
+
aesMode: DecrypterAesMode,
|
85
86
|
): Promise<ArrayBuffer> {
|
86
87
|
if (this.useSoftware) {
|
87
88
|
return new Promise((resolve, reject) => {
|
88
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
89
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
89
90
|
const decryptResult = this.flush();
|
90
91
|
if (decryptResult) {
|
91
92
|
resolve(decryptResult.buffer);
|
@@ -94,7 +95,7 @@ export default class Decrypter {
|
|
94
95
|
}
|
95
96
|
});
|
96
97
|
}
|
97
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
98
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
98
99
|
}
|
99
100
|
|
100
101
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -103,8 +104,13 @@ export default class Decrypter {
|
|
103
104
|
data: Uint8Array,
|
104
105
|
key: ArrayBuffer,
|
105
106
|
iv: ArrayBuffer,
|
107
|
+
aesMode: DecrypterAesMode,
|
106
108
|
): ArrayBuffer | null {
|
107
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
|
+
}
|
108
114
|
this.logOnce('JS AES decrypt');
|
109
115
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
110
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
|
@@ -147,21 +153,22 @@ export default class Decrypter {
|
|
147
153
|
data: Uint8Array,
|
148
154
|
key: ArrayBuffer,
|
149
155
|
iv: ArrayBuffer,
|
156
|
+
aesMode: DecrypterAesMode,
|
150
157
|
): Promise<ArrayBuffer> {
|
151
158
|
const subtle = this.subtle;
|
152
159
|
if (this.key !== key || !this.fastAesKey) {
|
153
160
|
this.key = key;
|
154
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
161
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
155
162
|
}
|
156
163
|
return this.fastAesKey
|
157
164
|
.expandKey()
|
158
|
-
.then((aesKey) => {
|
165
|
+
.then((aesKey: CryptoKey) => {
|
159
166
|
// decrypt using web crypto
|
160
167
|
if (!subtle) {
|
161
168
|
return Promise.reject(new Error('web crypto not initialized'));
|
162
169
|
}
|
163
170
|
this.logOnce('WebCrypto AES decrypt');
|
164
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
171
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
165
172
|
return crypto.decrypt(data.buffer, aesKey);
|
166
173
|
})
|
167
174
|
.catch((err) => {
|
@@ -169,19 +176,26 @@ export default class Decrypter {
|
|
169
176
|
`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
|
170
177
|
);
|
171
178
|
|
172
|
-
return this.onWebCryptoError(data, key, iv);
|
179
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
173
180
|
});
|
174
181
|
}
|
175
182
|
|
176
|
-
private onWebCryptoError(data, key, iv): ArrayBuffer | never {
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
+
private onWebCryptoError(data, key, iv, aesMode): ArrayBuffer | never {
|
184
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
185
|
+
if (enableSoftwareAES) {
|
186
|
+
this.useSoftware = true;
|
187
|
+
this.logEnabled = true;
|
188
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
189
|
+
const decryptResult = this.flush();
|
190
|
+
if (decryptResult) {
|
191
|
+
return decryptResult.buffer;
|
192
|
+
}
|
183
193
|
}
|
184
|
-
throw new Error(
|
194
|
+
throw new Error(
|
195
|
+
'WebCrypto' +
|
196
|
+
(enableSoftwareAES ? ' and softwareDecrypt' : '') +
|
197
|
+
': failed to decrypt data',
|
198
|
+
);
|
185
199
|
}
|
186
200
|
|
187
201
|
private getValidChunk(data: Uint8Array): Uint8Array {
|
@@ -1,16 +1,35 @@
|
|
1
|
+
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
+
|
1
3
|
export default class FastAESKey {
|
2
4
|
private subtle: any;
|
3
5
|
private key: ArrayBuffer;
|
6
|
+
private aesMode: DecrypterAesMode;
|
4
7
|
|
5
|
-
constructor(subtle, key) {
|
8
|
+
constructor(subtle, key, aesMode: DecrypterAesMode) {
|
6
9
|
this.subtle = subtle;
|
7
10
|
this.key = key;
|
11
|
+
this.aesMode = aesMode;
|
8
12
|
}
|
9
13
|
|
10
14
|
expandKey() {
|
11
|
-
|
12
|
-
|
13
|
-
'
|
14
|
-
|
15
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
16
|
+
return this.subtle.importKey(
|
17
|
+
'raw',
|
18
|
+
this.key,
|
19
|
+
{ name: subtleAlgoName },
|
20
|
+
false,
|
21
|
+
['encrypt', 'decrypt'],
|
22
|
+
);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
function getSubtleAlgoName(aesMode: DecrypterAesMode) {
|
27
|
+
switch (aesMode) {
|
28
|
+
case DecrypterAesMode.cbc:
|
29
|
+
return 'AES-CBC';
|
30
|
+
case DecrypterAesMode.ctr:
|
31
|
+
return 'AES-CTR';
|
32
|
+
default:
|
33
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
15
34
|
}
|
16
35
|
}
|
package/src/demux/audio/adts.ts
CHANGED
@@ -17,6 +17,7 @@ type AudioConfig = {
|
|
17
17
|
samplerate: number;
|
18
18
|
channelCount: number;
|
19
19
|
codec: string;
|
20
|
+
parsedCodec: string;
|
20
21
|
manifestCodec: string;
|
21
22
|
};
|
22
23
|
|
@@ -32,6 +33,7 @@ export function getAudioConfig(
|
|
32
33
|
audioCodec: string,
|
33
34
|
): AudioConfig | void {
|
34
35
|
let adtsObjectType: number;
|
36
|
+
let originalAdtsObjectType: number;
|
35
37
|
let adtsExtensionSamplingIndex: number;
|
36
38
|
let adtsChannelConfig: number;
|
37
39
|
let config: number[];
|
@@ -42,7 +44,8 @@ export function getAudioConfig(
|
|
42
44
|
8000, 7350,
|
43
45
|
];
|
44
46
|
// byte 2
|
45
|
-
adtsObjectType =
|
47
|
+
adtsObjectType = originalAdtsObjectType =
|
48
|
+
((data[offset + 2] & 0xc0) >>> 6) + 1;
|
46
49
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
47
50
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
48
51
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -61,8 +64,8 @@ export function getAudioConfig(
|
|
61
64
|
logger.log(
|
62
65
|
`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`,
|
63
66
|
);
|
64
|
-
//
|
65
|
-
if (/firefox/i.test(userAgent)) {
|
67
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
68
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
66
69
|
if (adtsSamplingIndex >= 6) {
|
67
70
|
adtsObjectType = 5;
|
68
71
|
config = new Array(4);
|
@@ -167,6 +170,7 @@ export function getAudioConfig(
|
|
167
170
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
168
171
|
channelCount: adtsChannelConfig,
|
169
172
|
codec: 'mp4a.40.' + adtsObjectType,
|
173
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
170
174
|
manifestCodec,
|
171
175
|
};
|
172
176
|
}
|
@@ -244,8 +248,9 @@ export function initTrackConfig(
|
|
244
248
|
track.channelCount = config.channelCount;
|
245
249
|
track.codec = config.codec;
|
246
250
|
track.manifestCodec = config.manifestCodec;
|
251
|
+
track.parsedCodec = config.parsedCodec;
|
247
252
|
logger.log(
|
248
|
-
`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
|
253
|
+
`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
|
249
254
|
);
|
250
255
|
}
|
251
256
|
}
|
package/src/demux/sample-aes.ts
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
import { HlsConfig } from '../config';
|
6
6
|
import Decrypter from '../crypt/decrypter';
|
7
|
+
import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
|
7
8
|
import { HlsEventEmitter } from '../events';
|
8
9
|
import type {
|
9
10
|
AudioSample,
|
@@ -30,6 +31,7 @@ class SampleAesDecrypter {
|
|
30
31
|
encryptedData,
|
31
32
|
this.keyData.key.buffer,
|
32
33
|
this.keyData.iv.buffer,
|
34
|
+
DecrypterAesMode.cbc,
|
33
35
|
);
|
34
36
|
}
|
35
37
|
|
@@ -12,14 +12,13 @@ import Transmuxer, {
|
|
12
12
|
} from '../demux/transmuxer';
|
13
13
|
import { logger } from '../utils/logger';
|
14
14
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
15
|
-
import { getMediaSource } from '../utils/mediasource-helper';
|
16
15
|
import { EventEmitter } from 'eventemitter3';
|
17
16
|
import { Fragment, Part } from '../loader/fragment';
|
17
|
+
import { getM2TSSupportedAudioTypes } from '../utils/codecs';
|
18
18
|
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
19
19
|
import type Hls from '../hls';
|
20
20
|
import type { HlsEventEmitter } from '../events';
|
21
21
|
import type { PlaylistLevelType } from '../types/loader';
|
22
|
-
import type { TypeSupported } from './tsdemuxer';
|
23
22
|
import type { RationalTimestamp } from '../utils/timescale-conversion';
|
24
23
|
|
25
24
|
export default class TransmuxerInterface {
|
@@ -64,16 +63,9 @@ export default class TransmuxerInterface {
|
|
64
63
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
65
64
|
this.observer.on(Events.ERROR, forwardMessage);
|
66
65
|
|
67
|
-
const
|
68
|
-
|
69
|
-
|
70
|
-
const m2tsTypeSupported: TypeSupported = {
|
71
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
72
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
73
|
-
ac3: __USE_M2TS_ADVANCED_CODECS__
|
74
|
-
? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
75
|
-
: false,
|
76
|
-
};
|
66
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(
|
67
|
+
config.preferManagedMediaSource,
|
68
|
+
);
|
77
69
|
|
78
70
|
// navigator.vendor is not always available in Web Worker
|
79
71
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Transmuxer, { isPromise } from '../demux/transmuxer';
|
2
2
|
import { Events } from '../events';
|
3
|
-
import {
|
3
|
+
import { enableLogs, type ILogFunction, type ILogger } from '../utils/logger';
|
4
4
|
import { EventEmitter } from 'eventemitter3';
|
5
5
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
6
6
|
import type { RemuxedTrack, RemuxerResult } from '../types/remuxer';
|
@@ -21,7 +21,7 @@ function startWorker(self) {
|
|
21
21
|
observer.on(Events.ERROR, forwardMessage);
|
22
22
|
|
23
23
|
// forward logger events to main thread
|
24
|
-
const forwardWorkerLogs = () => {
|
24
|
+
const forwardWorkerLogs = (logger: ILogger) => {
|
25
25
|
for (const logFn in logger) {
|
26
26
|
const func: ILogFunction = (message?) => {
|
27
27
|
forwardMessage('workerLog', {
|
@@ -46,8 +46,8 @@ function startWorker(self) {
|
|
46
46
|
data.vendor,
|
47
47
|
data.id,
|
48
48
|
);
|
49
|
-
enableLogs(config.debug, data.id);
|
50
|
-
forwardWorkerLogs();
|
49
|
+
const logger = enableLogs(config.debug, data.id);
|
50
|
+
forwardWorkerLogs(logger);
|
51
51
|
forwardMessage('init', null);
|
52
52
|
break;
|
53
53
|
}
|