hls.js 1.5.7-0.canary.10042 → 1.5.7

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 +1 -2
  2. package/dist/hls-demo.js +0 -10
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1283 -2293
  5. package/dist/hls.js.d.ts +84 -97
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1030 -1435
  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 +809 -1209
  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 +1039 -2030
  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 +2 -3
  21. package/src/controller/abr-controller.ts +20 -24
  22. package/src/controller/audio-stream-controller.ts +74 -68
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +8 -20
  25. package/src/controller/base-stream-controller.ts +36 -157
  26. package/src/controller/buffer-controller.ts +99 -226
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -2
  29. package/src/controller/cmcd-controller.ts +6 -27
  30. package/src/controller/content-steering-controller.ts +6 -8
  31. package/src/controller/eme-controller.ts +22 -9
  32. package/src/controller/error-controller.ts +8 -6
  33. package/src/controller/fps-controller.ts +3 -2
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/latency-controller.ts +11 -9
  37. package/src/controller/level-controller.ts +18 -12
  38. package/src/controller/stream-controller.ts +31 -36
  39. package/src/controller/subtitle-stream-controller.ts +40 -28
  40. package/src/controller/subtitle-track-controller.ts +3 -5
  41. package/src/controller/timeline-controller.ts +30 -23
  42. package/src/crypt/aes-crypto.ts +2 -21
  43. package/src/crypt/decrypter.ts +18 -32
  44. package/src/crypt/fast-aes-key.ts +5 -24
  45. package/src/demux/audio/adts.ts +4 -9
  46. package/src/demux/sample-aes.ts +0 -2
  47. package/src/demux/transmuxer-interface.ts +12 -4
  48. package/src/demux/transmuxer-worker.ts +4 -4
  49. package/src/demux/transmuxer.ts +3 -16
  50. package/src/demux/tsdemuxer.ts +37 -71
  51. package/src/demux/video/avc-video-parser.ts +119 -208
  52. package/src/demux/video/base-video-parser.ts +2 -134
  53. package/src/demux/video/exp-golomb.ts +208 -0
  54. package/src/events.ts +0 -7
  55. package/src/hls.ts +37 -49
  56. package/src/loader/fragment-loader.ts +2 -9
  57. package/src/loader/key-loader.ts +0 -2
  58. package/src/loader/level-key.ts +9 -10
  59. package/src/loader/playlist-loader.ts +5 -4
  60. package/src/remux/mp4-generator.ts +1 -196
  61. package/src/remux/mp4-remuxer.ts +7 -23
  62. package/src/task-loop.ts +2 -5
  63. package/src/types/component-api.ts +0 -2
  64. package/src/types/demuxer.ts +0 -3
  65. package/src/types/events.ts +0 -4
  66. package/src/utils/buffer-helper.ts +31 -12
  67. package/src/utils/codecs.ts +5 -34
  68. package/src/utils/logger.ts +24 -54
  69. package/src/utils/mp4-tools.ts +2 -4
  70. package/src/crypt/decrypter-aes-mode.ts +0 -4
  71. package/src/demux/video/hevc-video-parser.ts +0 -746
  72. package/src/utils/encryption-methods-util.ts +0 -21
@@ -49,6 +49,8 @@ export default class StreamController
49
49
  private altAudio: boolean = false;
50
50
  private audioOnly: boolean = false;
51
51
  private fragPlaying: Fragment | null = null;
52
+ private onvplaying: EventListener | null = null;
53
+ private onvseeked: EventListener | null = null;
52
54
  private fragLastKbps: number = 0;
53
55
  private couldBacktrack: boolean = false;
54
56
  private backtrackFragment: Fragment | null = null;
@@ -64,15 +66,17 @@ export default class StreamController
64
66
  hls,
65
67
  fragmentTracker,
66
68
  keyLoader,
67
- 'stream-controller',
69
+ '[stream-controller]',
68
70
  PlaylistLevelType.MAIN,
69
71
  );
70
- this.registerListeners();
72
+ this._registerListeners();
71
73
  }
72
74
 
73
- protected registerListeners() {
74
- super.registerListeners();
75
+ private _registerListeners() {
75
76
  const { hls } = this;
77
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
78
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
79
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
76
80
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
77
81
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
78
82
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
@@ -81,6 +85,7 @@ export default class StreamController
81
85
  this.onFragLoadEmergencyAborted,
82
86
  this,
83
87
  );
88
+ hls.on(Events.ERROR, this.onError, this);
84
89
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
85
90
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
86
91
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -89,9 +94,11 @@ export default class StreamController
89
94
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
90
95
  }
91
96
 
92
- protected unregisterListeners() {
93
- super.unregisterListeners();
97
+ protected _unregisterListeners() {
94
98
  const { hls } = this;
99
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
100
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
101
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
95
102
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
96
103
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
97
104
  hls.off(
@@ -99,6 +106,7 @@ export default class StreamController
99
106
  this.onFragLoadEmergencyAborted,
100
107
  this,
101
108
  );
109
+ hls.off(Events.ERROR, this.onError, this);
102
110
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
103
111
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
104
112
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -108,9 +116,7 @@ export default class StreamController
108
116
  }
109
117
 
110
118
  protected onHandlerDestroying() {
111
- // @ts-ignore
112
- this.onMediaPlaying = this.onMediaSeeked = null;
113
- this.unregisterListeners();
119
+ this._unregisterListeners();
114
120
  super.onHandlerDestroying();
115
121
  }
116
122
 
@@ -214,9 +220,6 @@ export default class StreamController
214
220
  }
215
221
 
216
222
  private doTickIdle() {
217
- if (!this.buffering) {
218
- return;
219
- }
220
223
  const { hls, levelLastLoaded, levels, media } = this;
221
224
 
222
225
  // if start level not parsed yet OR
@@ -513,8 +516,10 @@ export default class StreamController
513
516
  ) {
514
517
  super.onMediaAttached(event, data);
515
518
  const media = data.media;
516
- media.addEventListener('playing', this.onMediaPlaying);
517
- media.addEventListener('seeked', this.onMediaSeeked);
519
+ this.onvplaying = this.onMediaPlaying.bind(this);
520
+ this.onvseeked = this.onMediaSeeked.bind(this);
521
+ media.addEventListener('playing', this.onvplaying as EventListener);
522
+ media.addEventListener('seeked', this.onvseeked as EventListener);
518
523
  this.gapController = new GapController(
519
524
  this.config,
520
525
  media,
@@ -525,9 +530,10 @@ export default class StreamController
525
530
 
526
531
  protected onMediaDetaching() {
527
532
  const { media } = this;
528
- if (media) {
529
- media.removeEventListener('playing', this.onMediaPlaying);
530
- media.removeEventListener('seeked', this.onMediaSeeked);
533
+ if (media && this.onvplaying && this.onvseeked) {
534
+ media.removeEventListener('playing', this.onvplaying);
535
+ media.removeEventListener('seeked', this.onvseeked);
536
+ this.onvplaying = this.onvseeked = null;
531
537
  this.videoBuffer = null;
532
538
  }
533
539
  this.fragPlaying = null;
@@ -538,12 +544,12 @@ export default class StreamController
538
544
  super.onMediaDetaching();
539
545
  }
540
546
 
541
- private onMediaPlaying = () => {
547
+ private onMediaPlaying() {
542
548
  // tick to speed up FRAG_CHANGED triggering
543
549
  this.tick();
544
- };
550
+ }
545
551
 
546
- private onMediaSeeked = () => {
552
+ private onMediaSeeked() {
547
553
  const media = this.media;
548
554
  const currentTime = media ? media.currentTime : null;
549
555
  if (Number.isFinite(currentTime)) {
@@ -563,9 +569,9 @@ export default class StreamController
563
569
 
564
570
  // tick to speed up FRAG_CHANGED triggering
565
571
  this.tick();
566
- };
572
+ }
567
573
 
568
- protected onManifestLoading() {
574
+ private onManifestLoading() {
569
575
  // reset buffer on manifest loading
570
576
  this.log('Trigger BUFFER_RESET');
571
577
  this.hls.trigger(Events.BUFFER_RESET, undefined);
@@ -878,7 +884,7 @@ export default class StreamController
878
884
  this.fragBufferedComplete(frag, part);
879
885
  }
880
886
 
881
- protected onError(event: Events.ERROR, data: ErrorData) {
887
+ private onError(event: Events.ERROR, data: ErrorData) {
882
888
  if (data.fatal) {
883
889
  this.state = State.ERROR;
884
890
  return;
@@ -936,10 +942,8 @@ export default class StreamController
936
942
 
937
943
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
938
944
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
939
- const state = this.state;
940
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
941
- const levelDetails = this.getLevelDetails();
942
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
945
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
946
+ gapController.poll(this.lastCurrentTime, activeFrag);
943
947
  }
944
948
 
945
949
  this.lastCurrentTime = media.currentTime;
@@ -1341,15 +1345,6 @@ export default class StreamController
1341
1345
  );
1342
1346
  }
1343
1347
 
1344
- public get maxBufferLength(): number {
1345
- const { levels, level } = this;
1346
- const levelInfo = levels?.[level];
1347
- if (!levelInfo) {
1348
- return this.config.maxBufferLength;
1349
- }
1350
- return this.getMaxBufferLength(levelInfo.maxBitrate);
1351
- }
1352
-
1353
1348
  private backtrack(frag: Fragment) {
1354
1349
  this.couldBacktrack = true;
1355
1350
  // Causes findFragments to backtrack through fragments to find the keyframe
@@ -9,10 +9,6 @@ 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';
@@ -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.registerListeners();
57
+ this._registerListeners();
62
58
  }
63
59
 
64
60
  protected onHandlerDestroying() {
65
- this.unregisterListeners();
61
+ this._unregisterListeners();
66
62
  super.onHandlerDestroying();
67
63
  this.mainDetails = null;
68
64
  }
69
65
 
70
- protected registerListeners() {
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
- protected unregisterListeners() {
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,21 +107,21 @@ export class SubtitleStreamController
105
107
  this.tick();
106
108
  }
107
109
 
108
- protected onManifestLoading() {
110
+ onManifestLoading() {
109
111
  this.mainDetails = null;
110
112
  this.fragmentTracker.removeAllFragments();
111
113
  }
112
114
 
113
- protected onMediaDetaching(): void {
115
+ onMediaDetaching(): void {
114
116
  this.tracksBuffered = [];
115
117
  super.onMediaDetaching();
116
118
  }
117
119
 
118
- private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
120
+ onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
119
121
  this.mainDetails = data.details;
120
122
  }
121
123
 
122
- private onSubtitleFragProcessed(
124
+ onSubtitleFragProcessed(
123
125
  event: Events.SUBTITLE_FRAG_PROCESSED,
124
126
  data: SubtitleFragProcessed,
125
127
  ) {
@@ -160,10 +162,7 @@ export class SubtitleStreamController
160
162
  this.fragBufferedComplete(frag, null);
161
163
  }
162
164
 
163
- private onBufferFlushing(
164
- event: Events.BUFFER_FLUSHING,
165
- data: BufferFlushingData,
166
- ) {
165
+ onBufferFlushing(event: Events.BUFFER_FLUSHING, data: BufferFlushingData) {
167
166
  const { startOffset, endOffset } = data;
168
167
  if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
169
168
  const endOffsetSubtitles = endOffset - 1;
@@ -192,7 +191,7 @@ export class SubtitleStreamController
192
191
  }
193
192
  }
194
193
 
195
- private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
194
+ onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
196
195
  if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) {
197
196
  if (this.media?.buffered.length) {
198
197
  this.loadedmetadata = true;
@@ -201,7 +200,7 @@ export class SubtitleStreamController
201
200
  }
202
201
 
203
202
  // If something goes wrong, proceed to next frag, if we were processing one.
204
- protected onError(event: Events.ERROR, data: ErrorData) {
203
+ onError(event: Events.ERROR, data: ErrorData) {
205
204
  const frag = data.frag;
206
205
 
207
206
  if (frag?.type === PlaylistLevelType.SUBTITLE) {
@@ -215,7 +214,7 @@ export class SubtitleStreamController
215
214
  }
216
215
 
217
216
  // Got all new subtitle levels.
218
- private onSubtitleTracksUpdated(
217
+ onSubtitleTracksUpdated(
219
218
  event: Events.SUBTITLE_TRACKS_UPDATED,
220
219
  { subtitleTracks }: SubtitleTracksUpdatedData,
221
220
  ) {
@@ -240,7 +239,7 @@ export class SubtitleStreamController
240
239
  this.mediaBuffer = null;
241
240
  }
242
241
 
243
- private onSubtitleTrackSwitch(
242
+ onSubtitleTrackSwitch(
244
243
  event: Events.SUBTITLE_TRACK_SWITCH,
245
244
  data: TrackSwitchedData,
246
245
  ) {
@@ -258,13 +257,13 @@ export class SubtitleStreamController
258
257
  } else {
259
258
  this.mediaBuffer = null;
260
259
  }
261
- if (currentTrack && this.state !== State.STOPPED) {
260
+ if (currentTrack) {
262
261
  this.setInterval(TICK_INTERVAL);
263
262
  }
264
263
  }
265
264
 
266
265
  // Got a new set of subtitle fragments.
267
- private onSubtitleTrackLoaded(
266
+ onSubtitleTrackLoaded(
268
267
  event: Events.SUBTITLE_TRACK_LOADED,
269
268
  data: TrackLoadedData,
270
269
  ) {
@@ -361,7 +360,7 @@ export class SubtitleStreamController
361
360
  payload.byteLength > 0 &&
362
361
  decryptData?.key &&
363
362
  decryptData.iv &&
364
- isFullSegmentEncryption(decryptData.method)
363
+ decryptData.method === 'AES-128'
365
364
  ) {
366
365
  const startTime = performance.now();
367
366
  // decrypt the subtitles
@@ -370,7 +369,6 @@ export class SubtitleStreamController
370
369
  new Uint8Array(payload),
371
370
  decryptData.key.buffer,
372
371
  decryptData.iv.buffer,
373
- getAesModeFromFullSegmentMethod(decryptData.method),
374
372
  )
375
373
  .catch((err) => {
376
374
  hls.trigger(Events.ERROR, {
@@ -421,9 +419,15 @@ export class SubtitleStreamController
421
419
  config.maxBufferHole,
422
420
  );
423
421
  const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
422
+
423
+ const mainBufferInfo = this.getFwdBufferInfo(
424
+ this.media,
425
+ PlaylistLevelType.MAIN,
426
+ );
424
427
  const trackDetails = track.details as LevelDetails;
425
428
  const maxBufLen =
426
- this.hls.maxBufferLength + trackDetails.levelTargetDuration;
429
+ this.getMaxBufferLength(mainBufferInfo?.len) +
430
+ trackDetails.levelTargetDuration;
427
431
 
428
432
  if (bufferLen > maxBufLen) {
429
433
  return;
@@ -479,6 +483,14 @@ export class SubtitleStreamController
479
483
  }
480
484
  }
481
485
 
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
+
482
494
  protected loadFragment(
483
495
  frag: Fragment,
484
496
  level: Level,
@@ -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
- // @ts-ignore
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';
@@ -135,12 +136,17 @@ export class TimelineController implements ComponentAPI {
135
136
  }
136
137
 
137
138
  private initCea608Parsers() {
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);
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(
@@ -404,7 +410,7 @@ export class TimelineController implements ComponentAPI {
404
410
  .filter((t) => t !== null)
405
411
  .map((t) => (t as TextTrack).label);
406
412
  if (unusedTextTracks.length) {
407
- this.hls.logger.warn(
413
+ logger.warn(
408
414
  `Media element contains unused subtitle tracks: ${unusedTextTracks.join(
409
415
  ', ',
410
416
  )}. Replace media element for each source to clear TextTracks and captions menu.`,
@@ -462,19 +468,21 @@ export class TimelineController implements ComponentAPI {
462
468
  }
463
469
 
464
470
  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
+ }
465
477
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
466
- if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
467
- const { cea608Parser1, cea608Parser2, lastSn } = this;
468
- if (!cea608Parser1 || !cea608Parser2) {
469
- return;
470
- }
478
+ if (data.frag.type === PlaylistLevelType.MAIN) {
471
479
  const { cc, sn } = data.frag;
472
- const partIndex = data.part?.index ?? -1;
480
+ const partIndex = data?.part?.index ?? -1;
473
481
  if (
474
482
  !(
475
483
  sn === lastSn + 1 ||
476
- (sn === lastSn && partIndex === this.lastPartIndex + 1) ||
477
- cc === this.lastCc
484
+ (sn === lastSn && partIndex === lastPartIndex + 1) ||
485
+ cc === lastCc
478
486
  )
479
487
  ) {
480
488
  cea608Parser1.reset();
@@ -542,7 +550,7 @@ export class TimelineController implements ComponentAPI {
542
550
  });
543
551
  },
544
552
  (error) => {
545
- hls.logger.log(`Failed to parse IMSC1: ${error}`);
553
+ logger.log(`Failed to parse IMSC1: ${error}`);
546
554
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
547
555
  success: false,
548
556
  frag: frag,
@@ -589,7 +597,7 @@ export class TimelineController implements ComponentAPI {
589
597
  this._fallbackToIMSC1(frag, payload);
590
598
  }
591
599
  // Something went wrong while parsing. Trigger event with success false.
592
- hls.logger.log(`Failed to parse VTT cue: ${error}`);
600
+ logger.log(`Failed to parse VTT cue: ${error}`);
593
601
  if (missingInitPTS && maxAvCC > frag.cc) {
594
602
  return;
595
603
  }
@@ -661,7 +669,9 @@ export class TimelineController implements ComponentAPI {
661
669
  event: Events.FRAG_PARSING_USERDATA,
662
670
  data: FragParsingUserdataData,
663
671
  ) {
664
- if (!this.enabled || !this.config.enableCEA708Captions) {
672
+ this.initCea608Parsers();
673
+ const { cea608Parser1, cea608Parser2 } = this;
674
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
665
675
  return;
666
676
  }
667
677
  const { frag, samples } = data;
@@ -676,12 +686,9 @@ export class TimelineController implements ComponentAPI {
676
686
  for (let i = 0; i < samples.length; i++) {
677
687
  const ccBytes = samples[i].bytes;
678
688
  if (ccBytes) {
679
- if (!this.cea608Parser1) {
680
- this.initCea608Parsers();
681
- }
682
689
  const ccdatas = this.extractCea608Data(ccBytes);
683
- this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
684
- this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
690
+ cea608Parser1.addData(samples[i].pts, ccdatas[0]);
691
+ cea608Parser2.addData(samples[i].pts, ccdatas[1]);
685
692
  }
686
693
  }
687
694
  }
@@ -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, aesMode: DecrypterAesMode) {
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
- 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
- }
11
+ return this.subtle.decrypt({ name: 'AES-CBC', iv: this.aesIV }, key, data);
31
12
  }
32
13
  }
@@ -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.enableSoftwareAES = config.enableSoftwareAES;
24
+ this.useSoftware = config.enableSoftwareAES;
27
25
  this.removePKCS7Padding = removePKCS7Padding;
28
26
  // built in decryptor expects PKCS7 padding
29
27
  if (removePKCS7Padding) {
@@ -38,7 +36,9 @@ export default class Decrypter {
38
36
  /* no-op */
39
37
  }
40
38
  }
41
- this.useSoftware = this.subtle === null;
39
+ if (this.subtle === null) {
40
+ this.useSoftware = true;
41
+ }
42
42
  }
43
43
 
44
44
  destroy() {
@@ -82,11 +82,10 @@ export default class Decrypter {
82
82
  data: Uint8Array | ArrayBuffer,
83
83
  key: ArrayBuffer,
84
84
  iv: ArrayBuffer,
85
- aesMode: DecrypterAesMode,
86
85
  ): Promise<ArrayBuffer> {
87
86
  if (this.useSoftware) {
88
87
  return new Promise((resolve, reject) => {
89
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
88
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
90
89
  const decryptResult = this.flush();
91
90
  if (decryptResult) {
92
91
  resolve(decryptResult.buffer);
@@ -95,7 +94,7 @@ export default class Decrypter {
95
94
  }
96
95
  });
97
96
  }
98
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
97
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
99
98
  }
100
99
 
101
100
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
@@ -104,13 +103,8 @@ export default class Decrypter {
104
103
  data: Uint8Array,
105
104
  key: ArrayBuffer,
106
105
  iv: ArrayBuffer,
107
- aesMode: DecrypterAesMode,
108
106
  ): ArrayBuffer | null {
109
107
  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
108
  this.logOnce('JS AES decrypt');
115
109
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
116
110
  // 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,22 +147,21 @@ export default class Decrypter {
153
147
  data: Uint8Array,
154
148
  key: ArrayBuffer,
155
149
  iv: ArrayBuffer,
156
- aesMode: DecrypterAesMode,
157
150
  ): Promise<ArrayBuffer> {
158
151
  const subtle = this.subtle;
159
152
  if (this.key !== key || !this.fastAesKey) {
160
153
  this.key = key;
161
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
154
+ this.fastAesKey = new FastAESKey(subtle, key);
162
155
  }
163
156
  return this.fastAesKey
164
157
  .expandKey()
165
- .then((aesKey: CryptoKey) => {
158
+ .then((aesKey) => {
166
159
  // decrypt using web crypto
167
160
  if (!subtle) {
168
161
  return Promise.reject(new Error('web crypto not initialized'));
169
162
  }
170
163
  this.logOnce('WebCrypto AES decrypt');
171
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
164
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
172
165
  return crypto.decrypt(data.buffer, aesKey);
173
166
  })
174
167
  .catch((err) => {
@@ -176,26 +169,19 @@ export default class Decrypter {
176
169
  `[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
177
170
  );
178
171
 
179
- return this.onWebCryptoError(data, key, iv, aesMode);
172
+ return this.onWebCryptoError(data, key, iv);
180
173
  });
181
174
  }
182
175
 
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
- }
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;
193
183
  }
194
- throw new Error(
195
- 'WebCrypto' +
196
- (enableSoftwareAES ? ' and softwareDecrypt' : '') +
197
- ': failed to decrypt data',
198
- );
184
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
199
185
  }
200
186
 
201
187
  private getValidChunk(data: Uint8Array): Uint8Array {