hls.js 1.5.9-0.canary.10310 → 1.5.9

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 (89) hide show
  1. package/README.md +3 -4
  2. package/dist/hls-demo.js +38 -41
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2191 -3474
  5. package/dist/hls.js.d.ts +85 -108
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +3136 -3783
  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 +1260 -1934
  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 +4182 -5488
  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 +35 -35
  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 +14 -51
  30. package/src/controller/content-steering-controller.ts +15 -29
  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 -37
  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 +21 -19
  43. package/src/crypt/aes-crypto.ts +2 -21
  44. package/src/crypt/decrypter.ts +16 -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 +18 -147
  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 +38 -61
  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 -4
  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/cea-608-parser.ts +3 -1
  78. package/src/utils/codecs.ts +5 -34
  79. package/src/utils/fetch-loader.ts +1 -1
  80. package/src/utils/imsc1-ttml-parser.ts +1 -1
  81. package/src/utils/keysystem-util.ts +6 -1
  82. package/src/utils/logger.ts +23 -58
  83. package/src/utils/mp4-tools.ts +3 -5
  84. package/src/utils/webvtt-parser.ts +1 -1
  85. package/src/crypt/decrypter-aes-mode.ts +0 -4
  86. package/src/demux/video/hevc-video-parser.ts +0 -749
  87. package/src/empty-es.js +0 -5
  88. package/src/utils/encryption-methods-util.ts +0 -21
  89. package/src/utils/utf8-utils.ts +0 -18
@@ -6,6 +6,7 @@ import type {
6
6
  LevelUpdatedData,
7
7
  MediaAttachingData,
8
8
  } from '../types/events';
9
+ import { logger } from '../utils/logger';
9
10
  import type { ComponentAPI } from '../types/component-api';
10
11
  import type Hls from '../hls';
11
12
  import type { HlsConfig } from '../config';
@@ -18,6 +19,7 @@ export default class LatencyController implements ComponentAPI {
18
19
  private currentTime: number = 0;
19
20
  private stallCount: number = 0;
20
21
  private _latency: number | null = null;
22
+ private timeupdateHandler = () => this.timeupdate();
21
23
 
22
24
  constructor(hls: Hls) {
23
25
  this.hls = hls;
@@ -124,7 +126,7 @@ export default class LatencyController implements ComponentAPI {
124
126
  this.onMediaDetaching();
125
127
  this.levelDetails = null;
126
128
  // @ts-ignore
127
- this.hls = null;
129
+ this.hls = this.timeupdateHandler = null;
128
130
  }
129
131
 
130
132
  private registerListeners() {
@@ -148,12 +150,12 @@ export default class LatencyController implements ComponentAPI {
148
150
  data: MediaAttachingData,
149
151
  ) {
150
152
  this.media = data.media;
151
- this.media.addEventListener('timeupdate', this.onTimeupdate);
153
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
152
154
  }
153
155
 
154
156
  private onMediaDetaching() {
155
157
  if (this.media) {
156
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
158
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
157
159
  this.media = null;
158
160
  }
159
161
  }
@@ -170,10 +172,10 @@ export default class LatencyController implements ComponentAPI {
170
172
  ) {
171
173
  this.levelDetails = details;
172
174
  if (details.advanced) {
173
- this.onTimeupdate();
175
+ this.timeupdate();
174
176
  }
175
177
  if (!details.live && this.media) {
176
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
178
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
177
179
  }
178
180
  }
179
181
 
@@ -183,13 +185,13 @@ export default class LatencyController implements ComponentAPI {
183
185
  }
184
186
  this.stallCount++;
185
187
  if (this.levelDetails?.live) {
186
- this.hls.logger.warn(
187
- '[latency-controller]: Stall detected, adjusting target latency',
188
+ logger.warn(
189
+ '[playback-rate-controller]: Stall detected, adjusting target latency',
188
190
  );
189
191
  }
190
192
  }
191
193
 
192
- private onTimeupdate = () => {
194
+ private timeupdate() {
193
195
  const { media, levelDetails } = this;
194
196
  if (!media || !levelDetails) {
195
197
  return;
@@ -240,7 +242,7 @@ export default class LatencyController implements ComponentAPI {
240
242
  } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
241
243
  media.playbackRate = 1;
242
244
  }
243
- };
245
+ }
244
246
 
245
247
  private estimateLiveEdge(): number | null {
246
248
  const { levelDetails } = this;
@@ -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) {
@@ -509,30 +521,6 @@ export default class LevelController extends BasePlaylistController {
509
521
  this._startLevel = newLevel;
510
522
  }
511
523
 
512
- get pathwayPriority(): string[] | null {
513
- if (this.steering) {
514
- return this.steering.pathwayPriority;
515
- }
516
-
517
- return null;
518
- }
519
-
520
- set pathwayPriority(pathwayPriority: string[]) {
521
- if (this.steering) {
522
- const pathwaysList = this.steering.pathways();
523
- const filteredPathwayPriority = pathwayPriority.filter((pathwayId) => {
524
- return pathwaysList.indexOf(pathwayId) !== -1;
525
- });
526
- if (pathwayPriority.length < 1) {
527
- this.warn(
528
- `pathwayPriority ${pathwayPriority} should contain at least one pathway from list: ${pathwaysList}`,
529
- );
530
- return;
531
- }
532
- this.steering.pathwayPriority = filteredPathwayPriority;
533
- }
534
- }
535
-
536
524
  protected onError(event: Events.ERROR, data: ErrorData) {
537
525
  if (data.fatal || !data.context) {
538
526
  return;
@@ -584,13 +572,7 @@ export default class LevelController extends BasePlaylistController {
584
572
  if (curLevel.fragmentError === 0) {
585
573
  curLevel.loadError = 0;
586
574
  }
587
- // Ignore matching details populated by loading a Media Playlist directly
588
- let previousDetails = curLevel.details;
589
- if (previousDetails === data.details && previousDetails.advanced) {
590
- previousDetails = undefined;
591
- }
592
-
593
- this.playlistLoaded(level, data, previousDetails);
575
+ this.playlistLoaded(level, data, curLevel.details);
594
576
  } else if (data.deliveryDirectives?.skip) {
595
577
  // received a delta playlist update that cannot be merged
596
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
  ) {
@@ -365,7 +364,7 @@ export class SubtitleStreamController
365
364
  payload.byteLength > 0 &&
366
365
  decryptData?.key &&
367
366
  decryptData.iv &&
368
- isFullSegmentEncryption(decryptData.method)
367
+ decryptData.method === 'AES-128'
369
368
  ) {
370
369
  const startTime = performance.now();
371
370
  // decrypt the subtitles
@@ -374,7 +373,6 @@ export class SubtitleStreamController
374
373
  new Uint8Array(payload),
375
374
  decryptData.key.buffer,
376
375
  decryptData.iv.buffer,
377
- getAesModeFromFullSegmentMethod(decryptData.method),
378
376
  )
379
377
  .catch((err) => {
380
378
  hls.trigger(Events.ERROR, {
@@ -425,9 +423,15 @@ export class SubtitleStreamController
425
423
  config.maxBufferHole,
426
424
  );
427
425
  const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
426
+
427
+ const mainBufferInfo = this.getFwdBufferInfo(
428
+ this.media,
429
+ PlaylistLevelType.MAIN,
430
+ );
428
431
  const trackDetails = track.details as LevelDetails;
429
432
  const maxBufLen =
430
- this.hls.maxBufferLength + trackDetails.levelTargetDuration;
433
+ this.getMaxBufferLength(mainBufferInfo?.len) +
434
+ trackDetails.levelTargetDuration;
431
435
 
432
436
  if (bufferLen > maxBufLen) {
433
437
  return;
@@ -483,6 +487,14 @@ export class SubtitleStreamController
483
487
  }
484
488
  }
485
489
 
490
+ protected getMaxBufferLength(mainBufferLength?: number): number {
491
+ const maxConfigBuffer = super.getMaxBufferLength();
492
+ if (!mainBufferLength) {
493
+ return maxConfigBuffer;
494
+ }
495
+ return Math.max(maxConfigBuffer, mainBufferLength);
496
+ }
497
+
486
498
  protected loadFragment(
487
499
  frag: Fragment,
488
500
  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(
@@ -215,8 +221,6 @@ export class TimelineController implements ComponentAPI {
215
221
  canReuseVttTextTrack(textTrack, {
216
222
  name: label,
217
223
  lang: language,
218
- characteristics:
219
- 'transcribes-spoken-dialog,describes-music-and-sound',
220
224
  attrs: {} as any,
221
225
  })
222
226
  ) {
@@ -305,7 +309,6 @@ export class TimelineController implements ComponentAPI {
305
309
  delete captionsTracks[trackName];
306
310
  });
307
311
  this.nonNativeCaptionsTracks = {};
308
- this.media = null;
309
312
  }
310
313
 
311
314
  private onManifestLoading() {
@@ -407,7 +410,7 @@ export class TimelineController implements ComponentAPI {
407
410
  .filter((t) => t !== null)
408
411
  .map((t) => (t as TextTrack).label);
409
412
  if (unusedTextTracks.length) {
410
- this.hls.logger.warn(
413
+ logger.warn(
411
414
  `Media element contains unused subtitle tracks: ${unusedTextTracks.join(
412
415
  ', ',
413
416
  )}. Replace media element for each source to clear TextTracks and captions menu.`,
@@ -542,7 +545,7 @@ export class TimelineController implements ComponentAPI {
542
545
  });
543
546
  },
544
547
  (error) => {
545
- hls.logger.log(`Failed to parse IMSC1: ${error}`);
548
+ logger.log(`Failed to parse IMSC1: ${error}`);
546
549
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
547
550
  success: false,
548
551
  frag: frag,
@@ -589,7 +592,7 @@ export class TimelineController implements ComponentAPI {
589
592
  this._fallbackToIMSC1(frag, payload);
590
593
  }
591
594
  // Something went wrong while parsing. Trigger event with success false.
592
- hls.logger.log(`Failed to parse VTT cue: ${error}`);
595
+ logger.log(`Failed to parse VTT cue: ${error}`);
593
596
  if (missingInitPTS && maxAvCC > frag.cc) {
594
597
  return;
595
598
  }
@@ -661,7 +664,9 @@ export class TimelineController implements ComponentAPI {
661
664
  event: Events.FRAG_PARSING_USERDATA,
662
665
  data: FragParsingUserdataData,
663
666
  ) {
664
- if (!this.enabled || !this.config.enableCEA708Captions) {
667
+ this.initCea608Parsers();
668
+ const { cea608Parser1, cea608Parser2 } = this;
669
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
665
670
  return;
666
671
  }
667
672
  const { frag, samples } = data;
@@ -676,12 +681,9 @@ export class TimelineController implements ComponentAPI {
676
681
  for (let i = 0; i < samples.length; i++) {
677
682
  const ccBytes = samples[i].bytes;
678
683
  if (ccBytes) {
679
- if (!this.cea608Parser1) {
680
- this.initCea608Parsers();
681
- }
682
684
  const ccdatas = this.extractCea608Data(ccBytes);
683
- this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
684
- this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
685
+ cea608Parser1.addData(samples[i].pts, ccdatas[0]);
686
+ cea608Parser2.addData(samples[i].pts, ccdatas[1]);
685
687
  }
686
688
  }
687
689
  }