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.
Files changed (72) hide show
  1. package/README.md +2 -1
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2314 -1298
  5. package/dist/hls.js.d.ts +97 -84
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1486 -1075
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +1195 -789
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +1979 -982
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +22 -22
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +24 -20
  22. package/src/controller/audio-stream-controller.ts +68 -74
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +20 -8
  25. package/src/controller/base-stream-controller.ts +157 -36
  26. package/src/controller/buffer-controller.ts +203 -67
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +2 -2
  29. package/src/controller/cmcd-controller.ts +27 -6
  30. package/src/controller/content-steering-controller.ts +8 -6
  31. package/src/controller/eme-controller.ts +9 -22
  32. package/src/controller/error-controller.ts +6 -8
  33. package/src/controller/fps-controller.ts +2 -3
  34. package/src/controller/fragment-tracker.ts +15 -11
  35. package/src/controller/gap-controller.ts +43 -16
  36. package/src/controller/latency-controller.ts +9 -11
  37. package/src/controller/level-controller.ts +12 -18
  38. package/src/controller/stream-controller.ts +36 -31
  39. package/src/controller/subtitle-stream-controller.ts +28 -40
  40. package/src/controller/subtitle-track-controller.ts +5 -3
  41. package/src/controller/timeline-controller.ts +23 -30
  42. package/src/crypt/aes-crypto.ts +21 -2
  43. package/src/crypt/decrypter-aes-mode.ts +4 -0
  44. package/src/crypt/decrypter.ts +32 -18
  45. package/src/crypt/fast-aes-key.ts +24 -5
  46. package/src/demux/audio/adts.ts +9 -4
  47. package/src/demux/sample-aes.ts +2 -0
  48. package/src/demux/transmuxer-interface.ts +4 -12
  49. package/src/demux/transmuxer-worker.ts +4 -4
  50. package/src/demux/transmuxer.ts +16 -3
  51. package/src/demux/tsdemuxer.ts +71 -37
  52. package/src/demux/video/avc-video-parser.ts +208 -119
  53. package/src/demux/video/base-video-parser.ts +134 -2
  54. package/src/demux/video/exp-golomb.ts +0 -208
  55. package/src/demux/video/hevc-video-parser.ts +746 -0
  56. package/src/events.ts +7 -0
  57. package/src/hls.ts +49 -37
  58. package/src/loader/fragment-loader.ts +9 -2
  59. package/src/loader/key-loader.ts +2 -0
  60. package/src/loader/level-key.ts +10 -9
  61. package/src/loader/playlist-loader.ts +4 -5
  62. package/src/remux/mp4-generator.ts +196 -1
  63. package/src/remux/mp4-remuxer.ts +23 -7
  64. package/src/task-loop.ts +5 -2
  65. package/src/types/component-api.ts +2 -0
  66. package/src/types/demuxer.ts +3 -0
  67. package/src/types/events.ts +4 -0
  68. package/src/utils/buffer-helper.ts +12 -31
  69. package/src/utils/codecs.ts +34 -5
  70. package/src/utils/encryption-methods-util.ts +21 -0
  71. package/src/utils/logger.ts +54 -24
  72. 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
- '[subtitle-stream-controller]',
58
+ 'subtitle-stream-controller',
55
59
  PlaylistLevelType.SUBTITLE,
56
60
  );
57
- this._registerListeners();
61
+ this.registerListeners();
58
62
  }
59
63
 
60
64
  protected onHandlerDestroying() {
61
- this._unregisterListeners();
65
+ this.unregisterListeners();
62
66
  super.onHandlerDestroying();
63
67
  this.mainDetails = null;
64
68
  }
65
69
 
66
- private _registerListeners() {
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
- private _unregisterListeners() {
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(event: Events.BUFFER_FLUSHING, data: BufferFlushingData) {
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 === 'AES-128'
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.getMaxBufferLength(mainBufferInfo?.len) +
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, '[subtitle-track-controller]');
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
- this.onTextTracksChanged = this.asyncPollTrackChange = null as any;
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
- 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
- }
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?.part?.index ?? -1;
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.initCea608Parsers();
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.addData(samples[i].pts, ccdatas[0]);
691
- cea608Parser2.addData(samples[i].pts, ccdatas[1]);
683
+ this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
684
+ this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
692
685
  }
693
686
  }
694
687
  }
@@ -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
- return this.subtle.decrypt({ name: 'AES-CBC', iv: this.aesIV }, key, data);
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
  }
@@ -0,0 +1,4 @@
1
+ export const enum DecrypterAesMode {
2
+ cbc = 0,
3
+ ctr = 1,
4
+ }
@@ -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.useSoftware = config.enableSoftwareAES;
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
- if (this.subtle === null) {
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
- 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;
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('WebCrypto and softwareDecrypt: failed to decrypt data');
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
- return this.subtle.importKey('raw', this.key, { name: 'AES-CBC' }, false, [
12
- 'encrypt',
13
- 'decrypt',
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
  }
@@ -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 = ((data[offset + 2] & 0xc0) >>> 6) + 1;
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
- // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
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
  }
@@ -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 MediaSource = getMediaSource(config.preferManagedMediaSource) || {
68
- isTypeSupported: () => false,
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 { ILogFunction, enableLogs, logger } from '../utils/logger';
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
  }