hls.js 1.5.9 → 1.5.10-0.canary.10320

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 +4 -3
  2. package/dist/hls-demo.js +41 -38
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +3477 -2194
  5. package/dist/hls.js.d.ts +108 -85
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2401 -1754
  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 +1989 -1315
  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 +2863 -1557
  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 +3 -2
  21. package/src/controller/abr-controller.ts +24 -20
  22. package/src/controller/audio-stream-controller.ts +68 -74
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +27 -10
  25. package/src/controller/base-stream-controller.ts +160 -38
  26. package/src/controller/buffer-controller.ts +230 -92
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +3 -2
  29. package/src/controller/cmcd-controller.ts +51 -14
  30. package/src/controller/content-steering-controller.ts +29 -15
  31. package/src/controller/eme-controller.ts +10 -23
  32. package/src/controller/error-controller.ts +6 -8
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-tracker.ts +15 -11
  35. package/src/controller/gap-controller.ts +43 -16
  36. package/src/controller/id3-track-controller.ts +7 -7
  37. package/src/controller/latency-controller.ts +9 -11
  38. package/src/controller/level-controller.ts +37 -19
  39. package/src/controller/stream-controller.ts +37 -32
  40. package/src/controller/subtitle-stream-controller.ts +28 -40
  41. package/src/controller/subtitle-track-controller.ts +5 -3
  42. package/src/controller/timeline-controller.ts +19 -21
  43. package/src/crypt/aes-crypto.ts +21 -2
  44. package/src/crypt/decrypter-aes-mode.ts +4 -0
  45. package/src/crypt/decrypter.ts +32 -16
  46. package/src/crypt/fast-aes-key.ts +28 -5
  47. package/src/demux/audio/aacdemuxer.ts +2 -2
  48. package/src/demux/audio/ac3-demuxer.ts +4 -3
  49. package/src/demux/audio/adts.ts +9 -4
  50. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  51. package/src/demux/audio/mp3demuxer.ts +4 -3
  52. package/src/demux/audio/mpegaudio.ts +1 -1
  53. package/src/demux/mp4demuxer.ts +7 -7
  54. package/src/demux/sample-aes.ts +2 -0
  55. package/src/demux/transmuxer-interface.ts +4 -12
  56. package/src/demux/transmuxer-worker.ts +4 -4
  57. package/src/demux/transmuxer.ts +16 -3
  58. package/src/demux/tsdemuxer.ts +71 -37
  59. package/src/demux/video/avc-video-parser.ts +208 -119
  60. package/src/demux/video/base-video-parser.ts +147 -18
  61. package/src/demux/video/exp-golomb.ts +0 -208
  62. package/src/demux/video/hevc-video-parser.ts +749 -0
  63. package/src/empty-es.js +5 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +61 -38
  67. package/src/loader/fragment-loader.ts +10 -3
  68. package/src/loader/key-loader.ts +3 -1
  69. package/src/loader/level-key.ts +10 -9
  70. package/src/loader/playlist-loader.ts +4 -5
  71. package/src/remux/mp4-generator.ts +196 -1
  72. package/src/remux/mp4-remuxer.ts +24 -8
  73. package/src/task-loop.ts +5 -2
  74. package/src/types/component-api.ts +3 -1
  75. package/src/types/demuxer.ts +4 -0
  76. package/src/types/events.ts +4 -0
  77. package/src/types/remuxer.ts +1 -1
  78. package/src/utils/buffer-helper.ts +12 -31
  79. package/src/utils/cea-608-parser.ts +1 -3
  80. package/src/utils/codecs.ts +34 -5
  81. package/src/utils/encryption-methods-util.ts +21 -0
  82. package/src/utils/fetch-loader.ts +1 -1
  83. package/src/utils/imsc1-ttml-parser.ts +1 -1
  84. package/src/utils/keysystem-util.ts +1 -6
  85. package/src/utils/logger.ts +58 -23
  86. package/src/utils/mp4-tools.ts +5 -3
  87. package/src/utils/utf8-utils.ts +18 -0
  88. package/src/utils/webvtt-parser.ts +1 -1
  89. package/src/demux/id3.ts +0 -411
@@ -6,7 +6,6 @@ import type {
6
6
  LevelUpdatedData,
7
7
  MediaAttachingData,
8
8
  } from '../types/events';
9
- import { logger } from '../utils/logger';
10
9
  import type { ComponentAPI } from '../types/component-api';
11
10
  import type Hls from '../hls';
12
11
  import type { HlsConfig } from '../config';
@@ -19,7 +18,6 @@ export default class LatencyController implements ComponentAPI {
19
18
  private currentTime: number = 0;
20
19
  private stallCount: number = 0;
21
20
  private _latency: number | null = null;
22
- private timeupdateHandler = () => this.timeupdate();
23
21
 
24
22
  constructor(hls: Hls) {
25
23
  this.hls = hls;
@@ -126,7 +124,7 @@ export default class LatencyController implements ComponentAPI {
126
124
  this.onMediaDetaching();
127
125
  this.levelDetails = null;
128
126
  // @ts-ignore
129
- this.hls = this.timeupdateHandler = null;
127
+ this.hls = null;
130
128
  }
131
129
 
132
130
  private registerListeners() {
@@ -150,12 +148,12 @@ export default class LatencyController implements ComponentAPI {
150
148
  data: MediaAttachingData,
151
149
  ) {
152
150
  this.media = data.media;
153
- this.media.addEventListener('timeupdate', this.timeupdateHandler);
151
+ this.media.addEventListener('timeupdate', this.onTimeupdate);
154
152
  }
155
153
 
156
154
  private onMediaDetaching() {
157
155
  if (this.media) {
158
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
156
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
159
157
  this.media = null;
160
158
  }
161
159
  }
@@ -172,10 +170,10 @@ export default class LatencyController implements ComponentAPI {
172
170
  ) {
173
171
  this.levelDetails = details;
174
172
  if (details.advanced) {
175
- this.timeupdate();
173
+ this.onTimeupdate();
176
174
  }
177
175
  if (!details.live && this.media) {
178
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
176
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
179
177
  }
180
178
  }
181
179
 
@@ -185,13 +183,13 @@ export default class LatencyController implements ComponentAPI {
185
183
  }
186
184
  this.stallCount++;
187
185
  if (this.levelDetails?.live) {
188
- logger.warn(
189
- '[playback-rate-controller]: Stall detected, adjusting target latency',
186
+ this.hls.logger.warn(
187
+ '[latency-controller]: Stall detected, adjusting target latency',
190
188
  );
191
189
  }
192
190
  }
193
191
 
194
- private timeupdate() {
192
+ private onTimeupdate = () => {
195
193
  const { media, levelDetails } = this;
196
194
  if (!media || !levelDetails) {
197
195
  return;
@@ -242,7 +240,7 @@ export default class LatencyController implements ComponentAPI {
242
240
  } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
243
241
  media.playbackRate = 1;
244
242
  }
245
- }
243
+ };
246
244
 
247
245
  private estimateLiveEdge(): number | null {
248
246
  const { levelDetails } = this;
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  ManifestLoadedData,
3
3
  ManifestParsedData,
4
4
  LevelLoadedData,
@@ -27,8 +27,6 @@ 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
-
32
30
  export default class LevelController extends BasePlaylistController {
33
31
  private _levels: Level[] = [];
34
32
  private _firstLevel: number = -1;
@@ -45,7 +43,7 @@ export default class LevelController extends BasePlaylistController {
45
43
  hls: Hls,
46
44
  contentSteeringController: ContentSteeringController | null,
47
45
  ) {
48
- super(hls, '[level-controller]');
46
+ super(hls, 'level-controller');
49
47
  this.steering = contentSteeringController;
50
48
  this._registerListeners();
51
49
  }
@@ -119,22 +117,12 @@ export default class LevelController extends BasePlaylistController {
119
117
 
120
118
  data.levels.forEach((levelParsed: LevelParsed) => {
121
119
  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
125
120
  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
-
133
121
  if (audioCodec) {
134
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(
135
- audioCodec,
136
- preferManagedMediaSource,
137
- );
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;
138
126
  }
139
127
 
140
128
  if (videoCodec?.indexOf('avc1') === 0) {
@@ -521,6 +509,30 @@ export default class LevelController extends BasePlaylistController {
521
509
  this._startLevel = newLevel;
522
510
  }
523
511
 
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
+
524
536
  protected onError(event: Events.ERROR, data: ErrorData) {
525
537
  if (data.fatal || !data.context) {
526
538
  return;
@@ -572,7 +584,13 @@ export default class LevelController extends BasePlaylistController {
572
584
  if (curLevel.fragmentError === 0) {
573
585
  curLevel.loadError = 0;
574
586
  }
575
- this.playlistLoaded(level, data, curLevel.details);
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);
576
594
  } else if (data.deliveryDirectives?.skip) {
577
595
  // received a delta playlist update that cannot be merged
578
596
  details.deltaUpdateFailed = true;
@@ -49,8 +49,6 @@ 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;
54
52
  private fragLastKbps: number = 0;
55
53
  private couldBacktrack: boolean = false;
56
54
  private backtrackFragment: Fragment | null = null;
@@ -66,17 +64,15 @@ export default class StreamController
66
64
  hls,
67
65
  fragmentTracker,
68
66
  keyLoader,
69
- '[stream-controller]',
67
+ 'stream-controller',
70
68
  PlaylistLevelType.MAIN,
71
69
  );
72
- this._registerListeners();
70
+ this.registerListeners();
73
71
  }
74
72
 
75
- private _registerListeners() {
73
+ protected registerListeners() {
74
+ super.registerListeners();
76
75
  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);
80
76
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
81
77
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
82
78
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
@@ -85,7 +81,6 @@ export default class StreamController
85
81
  this.onFragLoadEmergencyAborted,
86
82
  this,
87
83
  );
88
- hls.on(Events.ERROR, this.onError, this);
89
84
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
90
85
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
91
86
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -94,11 +89,9 @@ export default class StreamController
94
89
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
95
90
  }
96
91
 
97
- protected _unregisterListeners() {
92
+ protected unregisterListeners() {
93
+ super.unregisterListeners();
98
94
  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);
102
95
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
103
96
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
104
97
  hls.off(
@@ -106,7 +99,6 @@ export default class StreamController
106
99
  this.onFragLoadEmergencyAborted,
107
100
  this,
108
101
  );
109
- hls.off(Events.ERROR, this.onError, this);
110
102
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
111
103
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
112
104
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -116,7 +108,9 @@ export default class StreamController
116
108
  }
117
109
 
118
110
  protected onHandlerDestroying() {
119
- this._unregisterListeners();
111
+ // @ts-ignore
112
+ this.onMediaPlaying = this.onMediaSeeked = null;
113
+ this.unregisterListeners();
120
114
  super.onHandlerDestroying();
121
115
  }
122
116
 
@@ -220,6 +214,9 @@ export default class StreamController
220
214
  }
221
215
 
222
216
  private doTickIdle() {
217
+ if (!this.buffering) {
218
+ return;
219
+ }
223
220
  const { hls, levelLastLoaded, levels, media } = this;
224
221
 
225
222
  // if start level not parsed yet OR
@@ -516,10 +513,8 @@ export default class StreamController
516
513
  ) {
517
514
  super.onMediaAttached(event, data);
518
515
  const media = data.media;
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);
516
+ media.addEventListener('playing', this.onMediaPlaying);
517
+ media.addEventListener('seeked', this.onMediaSeeked);
523
518
  this.gapController = new GapController(
524
519
  this.config,
525
520
  media,
@@ -530,12 +525,11 @@ export default class StreamController
530
525
 
531
526
  protected onMediaDetaching() {
532
527
  const { media } = this;
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;
528
+ if (media) {
529
+ media.removeEventListener('playing', this.onMediaPlaying);
530
+ media.removeEventListener('seeked', this.onMediaSeeked);
538
531
  }
532
+ this.videoBuffer = null;
539
533
  this.fragPlaying = null;
540
534
  if (this.gapController) {
541
535
  this.gapController.destroy();
@@ -544,12 +538,12 @@ export default class StreamController
544
538
  super.onMediaDetaching();
545
539
  }
546
540
 
547
- private onMediaPlaying() {
541
+ private onMediaPlaying = () => {
548
542
  // tick to speed up FRAG_CHANGED triggering
549
543
  this.tick();
550
- }
544
+ };
551
545
 
552
- private onMediaSeeked() {
546
+ private onMediaSeeked = () => {
553
547
  const media = this.media;
554
548
  const currentTime = media ? media.currentTime : null;
555
549
  if (Number.isFinite(currentTime)) {
@@ -569,9 +563,9 @@ export default class StreamController
569
563
 
570
564
  // tick to speed up FRAG_CHANGED triggering
571
565
  this.tick();
572
- }
566
+ };
573
567
 
574
- private onManifestLoading() {
568
+ protected onManifestLoading() {
575
569
  // reset buffer on manifest loading
576
570
  this.log('Trigger BUFFER_RESET');
577
571
  this.hls.trigger(Events.BUFFER_RESET, undefined);
@@ -884,7 +878,7 @@ export default class StreamController
884
878
  this.fragBufferedComplete(frag, part);
885
879
  }
886
880
 
887
- private onError(event: Events.ERROR, data: ErrorData) {
881
+ protected onError(event: Events.ERROR, data: ErrorData) {
888
882
  if (data.fatal) {
889
883
  this.state = State.ERROR;
890
884
  return;
@@ -942,8 +936,10 @@ export default class StreamController
942
936
 
943
937
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
944
938
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
945
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
946
- gapController.poll(this.lastCurrentTime, activeFrag);
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);
947
943
  }
948
944
 
949
945
  this.lastCurrentTime = media.currentTime;
@@ -1345,6 +1341,15 @@ export default class StreamController
1345
1341
  );
1346
1342
  }
1347
1343
 
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
+
1348
1353
  private backtrack(frag: Fragment) {
1349
1354
  this.couldBacktrack = true;
1350
1355
  // Causes findFragments to backtrack through fragments to find the keyframe
@@ -9,6 +9,10 @@ import { PlaylistLevelType } from '../types/loader';
9
9
  import { Level } from '../types/level';
10
10
  import { subtitleOptionsIdentical } from '../utils/media-option-attributes';
11
11
  import { ErrorDetails, ErrorTypes } from '../errors';
12
+ import {
13
+ isFullSegmentEncryption,
14
+ getAesModeFromFullSegmentMethod,
15
+ } from '../utils/encryption-methods-util';
12
16
  import type { NetworkComponentAPI } from '../types/component-api';
13
17
  import type Hls from '../hls';
14
18
  import type { FragmentTracker } from './fragment-tracker';
@@ -51,25 +55,22 @@ export class SubtitleStreamController
51
55
  hls,
52
56
  fragmentTracker,
53
57
  keyLoader,
54
- '[subtitle-stream-controller]',
58
+ 'subtitle-stream-controller',
55
59
  PlaylistLevelType.SUBTITLE,
56
60
  );
57
- this._registerListeners();
61
+ this.registerListeners();
58
62
  }
59
63
 
60
64
  protected onHandlerDestroying() {
61
- this._unregisterListeners();
65
+ this.unregisterListeners();
62
66
  super.onHandlerDestroying();
63
67
  this.mainDetails = null;
64
68
  }
65
69
 
66
- private _registerListeners() {
70
+ protected registerListeners() {
71
+ super.registerListeners();
67
72
  const { hls } = this;
68
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
69
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
70
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
71
73
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
72
- hls.on(Events.ERROR, this.onError, this);
73
74
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
74
75
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
75
76
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -78,13 +79,10 @@ export class SubtitleStreamController
78
79
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
79
80
  }
80
81
 
81
- private _unregisterListeners() {
82
+ protected unregisterListeners() {
83
+ super.unregisterListeners();
82
84
  const { hls } = this;
83
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
84
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
85
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
86
85
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
87
- hls.off(Events.ERROR, this.onError, this);
88
86
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
89
87
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
90
88
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -107,21 +105,21 @@ export class SubtitleStreamController
107
105
  this.tick();
108
106
  }
109
107
 
110
- onManifestLoading() {
108
+ protected onManifestLoading() {
111
109
  this.mainDetails = null;
112
110
  this.fragmentTracker.removeAllFragments();
113
111
  }
114
112
 
115
- onMediaDetaching(): void {
113
+ protected onMediaDetaching(): void {
116
114
  this.tracksBuffered = [];
117
115
  super.onMediaDetaching();
118
116
  }
119
117
 
120
- onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
118
+ private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
121
119
  this.mainDetails = data.details;
122
120
  }
123
121
 
124
- onSubtitleFragProcessed(
122
+ private onSubtitleFragProcessed(
125
123
  event: Events.SUBTITLE_FRAG_PROCESSED,
126
124
  data: SubtitleFragProcessed,
127
125
  ) {
@@ -162,7 +160,10 @@ export class SubtitleStreamController
162
160
  this.fragBufferedComplete(frag, null);
163
161
  }
164
162
 
165
- onBufferFlushing(event: Events.BUFFER_FLUSHING, data: BufferFlushingData) {
163
+ private onBufferFlushing(
164
+ event: Events.BUFFER_FLUSHING,
165
+ data: BufferFlushingData,
166
+ ) {
166
167
  const { startOffset, endOffset } = data;
167
168
  if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
168
169
  const endOffsetSubtitles = endOffset - 1;
@@ -191,7 +192,7 @@ export class SubtitleStreamController
191
192
  }
192
193
  }
193
194
 
194
- onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
195
+ private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
195
196
  if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) {
196
197
  if (this.media?.buffered.length) {
197
198
  this.loadedmetadata = true;
@@ -200,7 +201,7 @@ export class SubtitleStreamController
200
201
  }
201
202
 
202
203
  // If something goes wrong, proceed to next frag, if we were processing one.
203
- onError(event: Events.ERROR, data: ErrorData) {
204
+ protected onError(event: Events.ERROR, data: ErrorData) {
204
205
  const frag = data.frag;
205
206
 
206
207
  if (frag?.type === PlaylistLevelType.SUBTITLE) {
@@ -214,7 +215,7 @@ export class SubtitleStreamController
214
215
  }
215
216
 
216
217
  // Got all new subtitle levels.
217
- onSubtitleTracksUpdated(
218
+ private onSubtitleTracksUpdated(
218
219
  event: Events.SUBTITLE_TRACKS_UPDATED,
219
220
  { subtitleTracks }: SubtitleTracksUpdatedData,
220
221
  ) {
@@ -239,7 +240,7 @@ export class SubtitleStreamController
239
240
  this.mediaBuffer = null;
240
241
  }
241
242
 
242
- onSubtitleTrackSwitch(
243
+ private onSubtitleTrackSwitch(
243
244
  event: Events.SUBTITLE_TRACK_SWITCH,
244
245
  data: TrackSwitchedData,
245
246
  ) {
@@ -257,13 +258,13 @@ export class SubtitleStreamController
257
258
  } else {
258
259
  this.mediaBuffer = null;
259
260
  }
260
- if (currentTrack) {
261
+ if (currentTrack && this.state !== State.STOPPED) {
261
262
  this.setInterval(TICK_INTERVAL);
262
263
  }
263
264
  }
264
265
 
265
266
  // Got a new set of subtitle fragments.
266
- onSubtitleTrackLoaded(
267
+ private onSubtitleTrackLoaded(
267
268
  event: Events.SUBTITLE_TRACK_LOADED,
268
269
  data: TrackLoadedData,
269
270
  ) {
@@ -364,7 +365,7 @@ export class SubtitleStreamController
364
365
  payload.byteLength > 0 &&
365
366
  decryptData?.key &&
366
367
  decryptData.iv &&
367
- decryptData.method === 'AES-128'
368
+ isFullSegmentEncryption(decryptData.method)
368
369
  ) {
369
370
  const startTime = performance.now();
370
371
  // decrypt the subtitles
@@ -373,6 +374,7 @@ export class SubtitleStreamController
373
374
  new Uint8Array(payload),
374
375
  decryptData.key.buffer,
375
376
  decryptData.iv.buffer,
377
+ getAesModeFromFullSegmentMethod(decryptData.method),
376
378
  )
377
379
  .catch((err) => {
378
380
  hls.trigger(Events.ERROR, {
@@ -423,15 +425,9 @@ export class SubtitleStreamController
423
425
  config.maxBufferHole,
424
426
  );
425
427
  const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
426
-
427
- const mainBufferInfo = this.getFwdBufferInfo(
428
- this.media,
429
- PlaylistLevelType.MAIN,
430
- );
431
428
  const trackDetails = track.details as LevelDetails;
432
429
  const maxBufLen =
433
- this.getMaxBufferLength(mainBufferInfo?.len) +
434
- trackDetails.levelTargetDuration;
430
+ this.hls.maxBufferLength + trackDetails.levelTargetDuration;
435
431
 
436
432
  if (bufferLen > maxBufLen) {
437
433
  return;
@@ -487,14 +483,6 @@ export class SubtitleStreamController
487
483
  }
488
484
  }
489
485
 
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
-
498
486
  protected loadFragment(
499
487
  frag: Fragment,
500
488
  level: Level,
@@ -35,13 +35,14 @@ class SubtitleTrackController extends BasePlaylistController {
35
35
  private currentTrack: MediaPlaylist | null = null;
36
36
  private selectDefaultTrack: boolean = true;
37
37
  private queuedDefaultTrack: number = -1;
38
- private asyncPollTrackChange: () => void = () => this.pollTrackChange(0);
39
38
  private useTextTrackPolling: boolean = false;
40
39
  private subtitlePollingInterval: number = -1;
41
40
  private _subtitleDisplay: boolean = true;
42
41
 
42
+ private asyncPollTrackChange = () => this.pollTrackChange(0);
43
+
43
44
  constructor(hls: Hls) {
44
- super(hls, '[subtitle-track-controller]');
45
+ super(hls, 'subtitle-track-controller');
45
46
  this.registerListeners();
46
47
  }
47
48
 
@@ -50,7 +51,8 @@ class SubtitleTrackController extends BasePlaylistController {
50
51
  this.tracks.length = 0;
51
52
  this.tracksInGroup.length = 0;
52
53
  this.currentTrack = null;
53
- this.onTextTracksChanged = this.asyncPollTrackChange = null as any;
54
+ // @ts-ignore
55
+ this.onTextTracksChanged = this.asyncPollTrackChange = null;
54
56
  super.destroy();
55
57
  }
56
58
 
@@ -28,7 +28,6 @@ import type {
28
28
  BufferFlushingData,
29
29
  FragLoadingData,
30
30
  } from '../types/events';
31
- import { logger } from '../utils/logger';
32
31
  import type Hls from '../hls';
33
32
  import type { ComponentAPI } from '../types/component-api';
34
33
  import type { HlsConfig } from '../config';
@@ -131,22 +130,17 @@ export class TimelineController implements ComponentAPI {
131
130
  hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
132
131
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
133
132
  // @ts-ignore
134
- this.hls = this.config = null;
133
+ this.hls = this.config = this.media = null;
135
134
  this.cea608Parser1 = this.cea608Parser2 = undefined;
136
135
  }
137
136
 
138
137
  private initCea608Parsers() {
139
- if (
140
- this.config.enableCEA708Captions &&
141
- (!this.cea608Parser1 || !this.cea608Parser2)
142
- ) {
143
- const channel1 = new OutputFilter(this, 'textTrack1');
144
- const channel2 = new OutputFilter(this, 'textTrack2');
145
- const channel3 = new OutputFilter(this, 'textTrack3');
146
- const channel4 = new OutputFilter(this, 'textTrack4');
147
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
148
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
149
- }
138
+ const channel1 = new OutputFilter(this, 'textTrack1');
139
+ const channel2 = new OutputFilter(this, 'textTrack2');
140
+ const channel3 = new OutputFilter(this, 'textTrack3');
141
+ const channel4 = new OutputFilter(this, 'textTrack4');
142
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
143
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
150
144
  }
151
145
 
152
146
  public addCues(
@@ -221,6 +215,8 @@ export class TimelineController implements ComponentAPI {
221
215
  canReuseVttTextTrack(textTrack, {
222
216
  name: label,
223
217
  lang: language,
218
+ characteristics:
219
+ 'transcribes-spoken-dialog,describes-music-and-sound',
224
220
  attrs: {} as any,
225
221
  })
226
222
  ) {
@@ -309,6 +305,7 @@ export class TimelineController implements ComponentAPI {
309
305
  delete captionsTracks[trackName];
310
306
  });
311
307
  this.nonNativeCaptionsTracks = {};
308
+ this.media = null;
312
309
  }
313
310
 
314
311
  private onManifestLoading() {
@@ -410,7 +407,7 @@ export class TimelineController implements ComponentAPI {
410
407
  .filter((t) => t !== null)
411
408
  .map((t) => (t as TextTrack).label);
412
409
  if (unusedTextTracks.length) {
413
- logger.warn(
410
+ this.hls.logger.warn(
414
411
  `Media element contains unused subtitle tracks: ${unusedTextTracks.join(
415
412
  ', ',
416
413
  )}. Replace media element for each source to clear TextTracks and captions menu.`,
@@ -545,7 +542,7 @@ export class TimelineController implements ComponentAPI {
545
542
  });
546
543
  },
547
544
  (error) => {
548
- logger.log(`Failed to parse IMSC1: ${error}`);
545
+ hls.logger.log(`Failed to parse IMSC1: ${error}`);
549
546
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
550
547
  success: false,
551
548
  frag: frag,
@@ -592,7 +589,7 @@ export class TimelineController implements ComponentAPI {
592
589
  this._fallbackToIMSC1(frag, payload);
593
590
  }
594
591
  // Something went wrong while parsing. Trigger event with success false.
595
- logger.log(`Failed to parse VTT cue: ${error}`);
592
+ hls.logger.log(`Failed to parse VTT cue: ${error}`);
596
593
  if (missingInitPTS && maxAvCC > frag.cc) {
597
594
  return;
598
595
  }
@@ -664,9 +661,7 @@ export class TimelineController implements ComponentAPI {
664
661
  event: Events.FRAG_PARSING_USERDATA,
665
662
  data: FragParsingUserdataData,
666
663
  ) {
667
- this.initCea608Parsers();
668
- const { cea608Parser1, cea608Parser2 } = this;
669
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
664
+ if (!this.enabled || !this.config.enableCEA708Captions) {
670
665
  return;
671
666
  }
672
667
  const { frag, samples } = data;
@@ -681,9 +676,12 @@ export class TimelineController implements ComponentAPI {
681
676
  for (let i = 0; i < samples.length; i++) {
682
677
  const ccBytes = samples[i].bytes;
683
678
  if (ccBytes) {
679
+ if (!this.cea608Parser1) {
680
+ this.initCea608Parsers();
681
+ }
684
682
  const ccdatas = this.extractCea608Data(ccBytes);
685
- cea608Parser1.addData(samples[i].pts, ccdatas[0]);
686
- cea608Parser2.addData(samples[i].pts, ccdatas[1]);
683
+ this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
684
+ this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
687
685
  }
688
686
  }
689
687
  }