hls.js 1.5.8-0.canary.10170 → 1.5.8

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 (87) hide show
  1. package/README.md +3 -4
  2. package/dist/hls-demo.js +3 -12
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2366 -3626
  5. package/dist/hls.js.d.ts +84 -98
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1643 -2278
  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 +1258 -1903
  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 +1531 -2794
  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 +30 -30
  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 +10 -27
  25. package/src/controller/base-stream-controller.ts +38 -160
  26. package/src/controller/buffer-controller.ts +92 -230
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -3
  29. package/src/controller/cmcd-controller.ts +9 -30
  30. package/src/controller/content-steering-controller.ts +6 -8
  31. package/src/controller/eme-controller.ts +23 -10
  32. package/src/controller/error-controller.ts +8 -6
  33. package/src/controller/fps-controller.ts +3 -8
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/id3-track-controller.ts +7 -7
  37. package/src/controller/latency-controller.ts +11 -9
  38. package/src/controller/level-controller.ts +19 -13
  39. package/src/controller/stream-controller.ts +32 -37
  40. package/src/controller/subtitle-stream-controller.ts +40 -28
  41. package/src/controller/subtitle-track-controller.ts +3 -5
  42. package/src/controller/timeline-controller.ts +31 -25
  43. package/src/crypt/aes-crypto.ts +2 -21
  44. package/src/crypt/decrypter.ts +18 -32
  45. package/src/crypt/fast-aes-key.ts +5 -24
  46. package/src/demux/audio/aacdemuxer.ts +2 -2
  47. package/src/demux/audio/ac3-demuxer.ts +3 -4
  48. package/src/demux/audio/adts.ts +4 -9
  49. package/src/demux/audio/base-audio-demuxer.ts +14 -16
  50. package/src/demux/audio/mp3demuxer.ts +3 -4
  51. package/src/demux/audio/mpegaudio.ts +1 -1
  52. package/src/demux/id3.ts +411 -0
  53. package/src/demux/mp4demuxer.ts +7 -7
  54. package/src/demux/sample-aes.ts +0 -2
  55. package/src/demux/transmuxer-interface.ts +12 -4
  56. package/src/demux/transmuxer-worker.ts +4 -4
  57. package/src/demux/transmuxer.ts +3 -16
  58. package/src/demux/tsdemuxer.ts +37 -71
  59. package/src/demux/video/avc-video-parser.ts +119 -208
  60. package/src/demux/video/base-video-parser.ts +2 -134
  61. package/src/demux/video/exp-golomb.ts +208 -0
  62. package/src/events.ts +1 -8
  63. package/src/exports-named.ts +1 -1
  64. package/src/hls.ts +37 -49
  65. package/src/loader/fragment-loader.ts +3 -10
  66. package/src/loader/key-loader.ts +1 -3
  67. package/src/loader/level-key.ts +9 -10
  68. package/src/loader/playlist-loader.ts +5 -4
  69. package/src/remux/mp4-generator.ts +1 -196
  70. package/src/remux/mp4-remuxer.ts +8 -24
  71. package/src/task-loop.ts +2 -5
  72. package/src/types/component-api.ts +1 -3
  73. package/src/types/demuxer.ts +0 -3
  74. package/src/types/events.ts +0 -4
  75. package/src/types/remuxer.ts +1 -1
  76. package/src/utils/buffer-helper.ts +31 -12
  77. package/src/utils/codecs.ts +5 -34
  78. package/src/utils/fetch-loader.ts +1 -1
  79. package/src/utils/imsc1-ttml-parser.ts +1 -1
  80. package/src/utils/keysystem-util.ts +6 -1
  81. package/src/utils/logger.ts +23 -58
  82. package/src/utils/mp4-tools.ts +3 -5
  83. package/src/utils/webvtt-parser.ts +1 -1
  84. package/src/crypt/decrypter-aes-mode.ts +0 -4
  85. package/src/demux/video/hevc-video-parser.ts +0 -749
  86. package/src/utils/encryption-methods-util.ts +0 -21
  87. package/src/utils/utf8-utils.ts +0 -18
@@ -1,4 +1,4 @@
1
- import type {
1
+ import {
2
2
  ManifestLoadedData,
3
3
  ManifestParsedData,
4
4
  LevelLoadedData,
@@ -27,6 +27,8 @@ import type Hls from '../hls';
27
27
  import type { HlsUrlParameters, LevelParsed } from '../types/level';
28
28
  import type { MediaPlaylist } from '../types/media-playlist';
29
29
 
30
+ let chromeOrFirefox: boolean;
31
+
30
32
  export default class LevelController extends BasePlaylistController {
31
33
  private _levels: Level[] = [];
32
34
  private _firstLevel: number = -1;
@@ -43,7 +45,7 @@ export default class LevelController extends BasePlaylistController {
43
45
  hls: Hls,
44
46
  contentSteeringController: ContentSteeringController | null,
45
47
  ) {
46
- super(hls, 'level-controller');
48
+ super(hls, '[level-controller]');
47
49
  this.steering = contentSteeringController;
48
50
  this._registerListeners();
49
51
  }
@@ -117,12 +119,22 @@ export default class LevelController extends BasePlaylistController {
117
119
 
118
120
  data.levels.forEach((levelParsed: LevelParsed) => {
119
121
  const attributes = levelParsed.attrs;
122
+
123
+ // erase audio codec info if browser does not support mp4a.40.34.
124
+ // demuxer will autodetect codec and fallback to mpeg/audio
120
125
  let { audioCodec, videoCodec } = levelParsed;
126
+ if (audioCodec?.indexOf('mp4a.40.34') !== -1) {
127
+ chromeOrFirefox ||= /chrome|firefox/i.test(navigator.userAgent);
128
+ if (chromeOrFirefox) {
129
+ levelParsed.audioCodec = audioCodec = undefined;
130
+ }
131
+ }
132
+
121
133
  if (audioCodec) {
122
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
123
- levelParsed.audioCodec = audioCodec =
124
- getCodecCompatibleName(audioCodec, preferManagedMediaSource) ||
125
- undefined;
134
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(
135
+ audioCodec,
136
+ preferManagedMediaSource,
137
+ );
126
138
  }
127
139
 
128
140
  if (videoCodec?.indexOf('avc1') === 0) {
@@ -560,13 +572,7 @@ export default class LevelController extends BasePlaylistController {
560
572
  if (curLevel.fragmentError === 0) {
561
573
  curLevel.loadError = 0;
562
574
  }
563
- // Ignore matching details populated by loading a Media Playlist directly
564
- let previousDetails = curLevel.details;
565
- if (previousDetails === data.details && previousDetails.advanced) {
566
- previousDetails = undefined;
567
- }
568
-
569
- this.playlistLoaded(level, data, previousDetails);
575
+ this.playlistLoaded(level, data, curLevel.details);
570
576
  } else if (data.deliveryDirectives?.skip) {
571
577
  // received a delta playlist update that cannot be merged
572
578
  details.deltaUpdateFailed = true;
@@ -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,11 +530,12 @@ 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;
537
+ this.videoBuffer = null;
531
538
  }
532
- this.videoBuffer = null;
533
539
  this.fragPlaying = null;
534
540
  if (this.gapController) {
535
541
  this.gapController.destroy();
@@ -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';
@@ -130,17 +131,22 @@ export class TimelineController implements ComponentAPI {
130
131
  hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
131
132
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
132
133
  // @ts-ignore
133
- this.hls = this.config = this.media = null;
134
+ this.hls = this.config = null;
134
135
  this.cea608Parser1 = this.cea608Parser2 = undefined;
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(
@@ -303,7 +309,6 @@ export class TimelineController implements ComponentAPI {
303
309
  delete captionsTracks[trackName];
304
310
  });
305
311
  this.nonNativeCaptionsTracks = {};
306
- this.media = null;
307
312
  }
308
313
 
309
314
  private onManifestLoading() {
@@ -405,7 +410,7 @@ export class TimelineController implements ComponentAPI {
405
410
  .filter((t) => t !== null)
406
411
  .map((t) => (t as TextTrack).label);
407
412
  if (unusedTextTracks.length) {
408
- this.hls.logger.warn(
413
+ logger.warn(
409
414
  `Media element contains unused subtitle tracks: ${unusedTextTracks.join(
410
415
  ', ',
411
416
  )}. Replace media element for each source to clear TextTracks and captions menu.`,
@@ -463,19 +468,21 @@ export class TimelineController implements ComponentAPI {
463
468
  }
464
469
 
465
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
+ }
466
477
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
467
- if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
468
- const { cea608Parser1, cea608Parser2, lastSn } = this;
469
- if (!cea608Parser1 || !cea608Parser2) {
470
- return;
471
- }
478
+ if (data.frag.type === PlaylistLevelType.MAIN) {
472
479
  const { cc, sn } = data.frag;
473
- const partIndex = data.part?.index ?? -1;
480
+ const partIndex = data?.part?.index ?? -1;
474
481
  if (
475
482
  !(
476
483
  sn === lastSn + 1 ||
477
- (sn === lastSn && partIndex === this.lastPartIndex + 1) ||
478
- cc === this.lastCc
484
+ (sn === lastSn && partIndex === lastPartIndex + 1) ||
485
+ cc === lastCc
479
486
  )
480
487
  ) {
481
488
  cea608Parser1.reset();
@@ -543,7 +550,7 @@ export class TimelineController implements ComponentAPI {
543
550
  });
544
551
  },
545
552
  (error) => {
546
- hls.logger.log(`Failed to parse IMSC1: ${error}`);
553
+ logger.log(`Failed to parse IMSC1: ${error}`);
547
554
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
548
555
  success: false,
549
556
  frag: frag,
@@ -590,7 +597,7 @@ export class TimelineController implements ComponentAPI {
590
597
  this._fallbackToIMSC1(frag, payload);
591
598
  }
592
599
  // Something went wrong while parsing. Trigger event with success false.
593
- hls.logger.log(`Failed to parse VTT cue: ${error}`);
600
+ logger.log(`Failed to parse VTT cue: ${error}`);
594
601
  if (missingInitPTS && maxAvCC > frag.cc) {
595
602
  return;
596
603
  }
@@ -662,7 +669,9 @@ export class TimelineController implements ComponentAPI {
662
669
  event: Events.FRAG_PARSING_USERDATA,
663
670
  data: FragParsingUserdataData,
664
671
  ) {
665
- if (!this.enabled || !this.config.enableCEA708Captions) {
672
+ this.initCea608Parsers();
673
+ const { cea608Parser1, cea608Parser2 } = this;
674
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
666
675
  return;
667
676
  }
668
677
  const { frag, samples } = data;
@@ -677,12 +686,9 @@ export class TimelineController implements ComponentAPI {
677
686
  for (let i = 0; i < samples.length; i++) {
678
687
  const ccBytes = samples[i].bytes;
679
688
  if (ccBytes) {
680
- if (!this.cea608Parser1) {
681
- this.initCea608Parsers();
682
- }
683
689
  const ccdatas = this.extractCea608Data(ccBytes);
684
- this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
685
- this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
690
+ cea608Parser1.addData(samples[i].pts, ccdatas[0]);
691
+ cea608Parser2.addData(samples[i].pts, ccdatas[1]);
686
692
  }
687
693
  }
688
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
  }