hls.js 1.5.13 → 1.5.14-0.canary.10415

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 (103) hide show
  1. package/README.md +4 -3
  2. package/dist/hls-demo.js +41 -38
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +4211 -2666
  5. package/dist/hls.js.d.ts +179 -110
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2841 -1921
  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 +2569 -1639
  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 +3572 -2017
  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 +38 -38
  20. package/src/config.ts +5 -2
  21. package/src/controller/abr-controller.ts +39 -25
  22. package/src/controller/audio-stream-controller.ts +156 -136
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +27 -10
  25. package/src/controller/base-stream-controller.ts +234 -89
  26. package/src/controller/buffer-controller.ts +250 -97
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +3 -2
  29. package/src/controller/cmcd-controller.ts +51 -14
  30. package/src/controller/content-steering-controller.ts +29 -15
  31. package/src/controller/eme-controller.ts +10 -23
  32. package/src/controller/error-controller.ts +28 -22
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-finders.ts +44 -16
  35. package/src/controller/fragment-tracker.ts +58 -25
  36. package/src/controller/gap-controller.ts +43 -16
  37. package/src/controller/id3-track-controller.ts +45 -35
  38. package/src/controller/latency-controller.ts +18 -13
  39. package/src/controller/level-controller.ts +37 -19
  40. package/src/controller/stream-controller.ts +100 -83
  41. package/src/controller/subtitle-stream-controller.ts +35 -47
  42. package/src/controller/subtitle-track-controller.ts +5 -3
  43. package/src/controller/timeline-controller.ts +20 -22
  44. package/src/crypt/aes-crypto.ts +21 -2
  45. package/src/crypt/decrypter-aes-mode.ts +4 -0
  46. package/src/crypt/decrypter.ts +32 -16
  47. package/src/crypt/fast-aes-key.ts +28 -5
  48. package/src/demux/audio/aacdemuxer.ts +2 -2
  49. package/src/demux/audio/ac3-demuxer.ts +4 -3
  50. package/src/demux/audio/adts.ts +9 -4
  51. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  52. package/src/demux/audio/mp3demuxer.ts +4 -3
  53. package/src/demux/audio/mpegaudio.ts +1 -1
  54. package/src/demux/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +2 -0
  56. package/src/demux/transmuxer-interface.ts +8 -16
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +16 -3
  59. package/src/demux/tsdemuxer.ts +75 -38
  60. package/src/demux/video/avc-video-parser.ts +210 -121
  61. package/src/demux/video/base-video-parser.ts +135 -2
  62. package/src/demux/video/exp-golomb.ts +0 -208
  63. package/src/demux/video/hevc-video-parser.ts +749 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +84 -47
  67. package/src/loader/date-range.ts +71 -5
  68. package/src/loader/fragment-loader.ts +23 -21
  69. package/src/loader/fragment.ts +8 -4
  70. package/src/loader/key-loader.ts +3 -1
  71. package/src/loader/level-details.ts +6 -6
  72. package/src/loader/level-key.ts +10 -9
  73. package/src/loader/m3u8-parser.ts +138 -144
  74. package/src/loader/playlist-loader.ts +5 -7
  75. package/src/remux/mp4-generator.ts +196 -1
  76. package/src/remux/mp4-remuxer.ts +32 -62
  77. package/src/remux/passthrough-remuxer.ts +1 -1
  78. package/src/task-loop.ts +5 -2
  79. package/src/types/component-api.ts +3 -1
  80. package/src/types/demuxer.ts +3 -0
  81. package/src/types/events.ts +19 -6
  82. package/src/types/fragment-tracker.ts +2 -2
  83. package/src/types/media-playlist.ts +9 -1
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +96 -9
  86. package/src/utils/buffer-helper.ts +12 -31
  87. package/src/utils/cea-608-parser.ts +1 -3
  88. package/src/utils/codecs.ts +34 -5
  89. package/src/utils/encryption-methods-util.ts +21 -0
  90. package/src/utils/fetch-loader.ts +1 -1
  91. package/src/utils/hash.ts +10 -0
  92. package/src/utils/hdr.ts +4 -7
  93. package/src/utils/imsc1-ttml-parser.ts +1 -1
  94. package/src/utils/keysystem-util.ts +1 -6
  95. package/src/utils/level-helper.ts +71 -44
  96. package/src/utils/logger.ts +58 -23
  97. package/src/utils/mp4-tools.ts +5 -3
  98. package/src/utils/rendition-helper.ts +100 -74
  99. package/src/utils/utf8-utils.ts +18 -0
  100. package/src/utils/variable-substitution.ts +0 -19
  101. package/src/utils/webvtt-parser.ts +2 -12
  102. package/src/demux/id3.ts +0 -411
  103. 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
- '[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,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
- this.fragPrevious = frag;
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(event: Events.BUFFER_FLUSHING, data: BufferFlushingData) {
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 === 'AES-128'
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.getMaxBufferLength(mainBufferInfo?.len) +
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, '[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';
@@ -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
- 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(
@@ -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 === 'main') {
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.initCea608Parsers();
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.addData(samples[i].pts, ccdatas[0]);
686
- 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]);
687
685
  }
688
686
  }
689
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,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
- 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;
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('WebCrypto and softwareDecrypt: failed to decrypt data');
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(subtle: SubtleCrypto, key: ArrayBuffer) {
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
- return this.subtle.importKey('raw', this.key, { name: 'AES-CBC' }, false, [
12
- 'encrypt',
13
- 'decrypt',
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 * as ID3 from '../id3';
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 = ID3.getID3Data(data, 0);
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 { getID3Data, getTimeStamp } from '../id3';
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 = getID3Data(data, 0);
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
- getTimeStamp(id3Data) !== undefined &&
75
+ getId3Timestamp(id3Data) !== undefined &&
75
76
  // check the bsid to confirm ac-3
76
77
  getAudioBSID(data, offset) < 16
77
78
  ) {