hls.js 1.5.6 → 1.5.7-0.canary.10015

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 (69) hide show
  1. package/README.md +1 -0
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2077 -1166
  5. package/dist/hls.js.d.ts +65 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1149 -858
  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 +985 -695
  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 +1758 -862
  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 +21 -21
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +20 -8
  25. package/src/controller/base-stream-controller.ts +149 -33
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +27 -6
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +12 -18
  36. package/src/controller/stream-controller.ts +24 -31
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +71 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/loader/playlist-loader.ts +4 -5
  60. package/src/remux/mp4-generator.ts +196 -1
  61. package/src/remux/mp4-remuxer.ts +23 -7
  62. package/src/task-loop.ts +5 -2
  63. package/src/types/component-api.ts +2 -0
  64. package/src/types/demuxer.ts +3 -0
  65. package/src/types/events.ts +4 -0
  66. package/src/utils/codecs.ts +33 -4
  67. package/src/utils/encryption-methods-util.ts +21 -0
  68. package/src/utils/logger.ts +54 -24
  69. package/src/utils/mp4-tools.ts +3 -1
@@ -1,5 +1,4 @@
1
1
  import { Events } from '../events';
2
- import { logger } from '../utils/logger';
3
2
  import type { ComponentAPI } from '../types/component-api';
4
3
  import type Hls from '../hls';
5
4
  import type { MediaAttachingData } from '../types/events';
@@ -84,13 +83,13 @@ class FPSController implements ComponentAPI {
84
83
  totalDroppedFrames: droppedFrames,
85
84
  });
86
85
  if (droppedFPS > 0) {
87
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
86
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
88
87
  if (
89
88
  currentDropped >
90
89
  hls.config.fpsDroppedMonitoringThreshold * currentDecoded
91
90
  ) {
92
91
  let currentLevel = hls.currentLevel;
93
- logger.warn(
92
+ hls.logger.warn(
94
93
  'drop FPS ratio greater than max allowed value for currentLevel: ' +
95
94
  currentLevel,
96
95
  );
@@ -1,20 +1,22 @@
1
- import type { BufferInfo } from '../utils/buffer-helper';
1
+ import { State } from './base-stream-controller';
2
2
  import { BufferHelper } from '../utils/buffer-helper';
3
3
  import { ErrorTypes, ErrorDetails } from '../errors';
4
4
  import { PlaylistLevelType } from '../types/loader';
5
5
  import { Events } from '../events';
6
- import { logger } from '../utils/logger';
6
+ import { Logger } from '../utils/logger';
7
7
  import type Hls from '../hls';
8
+ import type { BufferInfo } from '../utils/buffer-helper';
8
9
  import type { HlsConfig } from '../config';
9
10
  import type { Fragment } from '../loader/fragment';
10
11
  import type { FragmentTracker } from './fragment-tracker';
12
+ import type { LevelDetails } from '../loader/level-details';
11
13
 
12
14
  export const STALL_MINIMUM_DURATION_MS = 250;
13
15
  export const MAX_START_GAP_JUMP = 2.0;
14
16
  export const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
15
17
  export const SKIP_BUFFER_RANGE_START = 0.05;
16
18
 
17
- export default class GapController {
19
+ export default class GapController extends Logger {
18
20
  private config: HlsConfig;
19
21
  private media: HTMLMediaElement | null = null;
20
22
  private fragmentTracker: FragmentTracker;
@@ -24,8 +26,15 @@ export default class GapController {
24
26
  private stalled: number | null = null;
25
27
  private moved: boolean = false;
26
28
  private seeking: boolean = false;
29
+ private ended: number = 0;
27
30
 
28
- constructor(config, media, fragmentTracker, hls) {
31
+ constructor(
32
+ config: HlsConfig,
33
+ media: HTMLMediaElement,
34
+ fragmentTracker: FragmentTracker,
35
+ hls: Hls,
36
+ ) {
37
+ super('gap-controller', hls.logger);
29
38
  this.config = config;
30
39
  this.media = media;
31
40
  this.fragmentTracker = fragmentTracker;
@@ -44,7 +53,12 @@ export default class GapController {
44
53
  *
45
54
  * @param lastCurrentTime - Previously read playhead position
46
55
  */
47
- public poll(lastCurrentTime: number, activeFrag: Fragment | null) {
56
+ public poll(
57
+ lastCurrentTime: number,
58
+ activeFrag: Fragment | null,
59
+ levelDetails: LevelDetails | undefined,
60
+ state: string,
61
+ ) {
48
62
  const { config, media, stalled } = this;
49
63
  if (media === null) {
50
64
  return;
@@ -57,6 +71,7 @@ export default class GapController {
57
71
 
58
72
  // The playhead is moving, no-op
59
73
  if (currentTime !== lastCurrentTime) {
74
+ this.ended = 0;
60
75
  this.moved = true;
61
76
  if (!seeking) {
62
77
  this.nudgeRetry = 0;
@@ -65,7 +80,7 @@ export default class GapController {
65
80
  // The playhead is now moving, but was previously stalled
66
81
  if (this.stallReported) {
67
82
  const stalledDuration = self.performance.now() - stalled;
68
- logger.warn(
83
+ this.warn(
69
84
  `playback not stuck anymore @${currentTime}, after ${Math.round(
70
85
  stalledDuration,
71
86
  )}ms`,
@@ -128,12 +143,9 @@ export default class GapController {
128
143
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
129
144
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
130
145
  // that begins over 1 target duration after the video start position.
131
- const level = this.hls.levels
132
- ? this.hls.levels[this.hls.currentLevel]
133
- : null;
134
- const isLive = level?.details?.live;
146
+ const isLive = !!levelDetails?.live;
135
147
  const maxStartGapJump = isLive
136
- ? level!.details!.targetduration * 2
148
+ ? levelDetails!.targetduration * 2
137
149
  : MAX_START_GAP_JUMP;
138
150
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
139
151
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
@@ -153,6 +165,21 @@ export default class GapController {
153
165
 
154
166
  const stalledDuration = tnow - stalled;
155
167
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
168
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
169
+ if (
170
+ state === State.ENDED &&
171
+ !(levelDetails && levelDetails.live) &&
172
+ Math.abs(currentTime - (levelDetails?.edge || 0)) < 1
173
+ ) {
174
+ if (stalledDuration < 1000 || this.ended) {
175
+ return;
176
+ }
177
+ this.ended = currentTime;
178
+ this.hls.trigger(Events.MEDIA_ENDED, {
179
+ stalled: true,
180
+ });
181
+ return;
182
+ }
156
183
  // Report stalling after trying to fix
157
184
  this._reportStall(bufferInfo);
158
185
  if (!this.media) {
@@ -206,7 +233,7 @@ export default class GapController {
206
233
  bufferInfo.nextStart - currentTime < config.maxBufferHole)) &&
207
234
  stalledDurationMs > config.highBufferWatchdogPeriod * 1000
208
235
  ) {
209
- logger.warn('Trying to nudge playhead over buffer-hole');
236
+ this.warn('Trying to nudge playhead over buffer-hole');
210
237
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
211
238
  // We only try to jump the hole if it's under the configured size
212
239
  // Reset stalled so to rearm watchdog timer
@@ -230,7 +257,7 @@ export default class GapController {
230
257
  media.currentTime
231
258
  } due to low buffer (${JSON.stringify(bufferInfo)})`,
232
259
  );
233
- logger.warn(error.message);
260
+ this.warn(error.message);
234
261
  hls.trigger(Events.ERROR, {
235
262
  type: ErrorTypes.MEDIA_ERROR,
236
263
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -305,7 +332,7 @@ export default class GapController {
305
332
  startTime + SKIP_BUFFER_RANGE_START,
306
333
  currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS,
307
334
  );
308
- logger.warn(
335
+ this.warn(
309
336
  `skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`,
310
337
  );
311
338
  this.moved = true;
@@ -348,7 +375,7 @@ export default class GapController {
348
375
  const error = new Error(
349
376
  `Nudging 'currentTime' from ${currentTime} to ${targetTime}`,
350
377
  );
351
- logger.warn(error.message);
378
+ this.warn(error.message);
352
379
  media.currentTime = targetTime;
353
380
  hls.trigger(Events.ERROR, {
354
381
  type: ErrorTypes.MEDIA_ERROR,
@@ -360,7 +387,7 @@ export default class GapController {
360
387
  const error = new Error(
361
388
  `Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`,
362
389
  );
363
- logger.error(error.message);
390
+ this.error(error.message);
364
391
  hls.trigger(Events.ERROR, {
365
392
  type: ErrorTypes.MEDIA_ERROR,
366
393
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -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;
@@ -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) {
@@ -568,7 +556,13 @@ export default class LevelController extends BasePlaylistController {
568
556
  if (curLevel.fragmentError === 0) {
569
557
  curLevel.loadError = 0;
570
558
  }
571
- this.playlistLoaded(level, data, curLevel.details);
559
+ // Ignore matching details populated by loading a Media Playlist directly
560
+ let previousDetails = curLevel.details;
561
+ if (previousDetails === data.details && previousDetails.advanced) {
562
+ previousDetails = undefined;
563
+ }
564
+
565
+ this.playlistLoaded(level, data, previousDetails);
572
566
  } else if (data.deliveryDirectives?.skip) {
573
567
  // received a delta playlist update that cannot be merged
574
568
  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
 
@@ -516,10 +510,8 @@ export default class StreamController
516
510
  ) {
517
511
  super.onMediaAttached(event, data);
518
512
  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);
513
+ media.addEventListener('playing', this.onMediaPlaying);
514
+ media.addEventListener('seeked', this.onMediaSeeked);
523
515
  this.gapController = new GapController(
524
516
  this.config,
525
517
  media,
@@ -530,10 +522,9 @@ export default class StreamController
530
522
 
531
523
  protected onMediaDetaching() {
532
524
  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;
525
+ if (media) {
526
+ media.removeEventListener('playing', this.onMediaPlaying);
527
+ media.removeEventListener('seeked', this.onMediaSeeked);
537
528
  this.videoBuffer = null;
538
529
  }
539
530
  this.fragPlaying = null;
@@ -544,12 +535,12 @@ export default class StreamController
544
535
  super.onMediaDetaching();
545
536
  }
546
537
 
547
- private onMediaPlaying() {
538
+ private onMediaPlaying = () => {
548
539
  // tick to speed up FRAG_CHANGED triggering
549
540
  this.tick();
550
- }
541
+ };
551
542
 
552
- private onMediaSeeked() {
543
+ private onMediaSeeked = () => {
553
544
  const media = this.media;
554
545
  const currentTime = media ? media.currentTime : null;
555
546
  if (Number.isFinite(currentTime)) {
@@ -569,9 +560,9 @@ export default class StreamController
569
560
 
570
561
  // tick to speed up FRAG_CHANGED triggering
571
562
  this.tick();
572
- }
563
+ };
573
564
 
574
- private onManifestLoading() {
565
+ protected onManifestLoading() {
575
566
  // reset buffer on manifest loading
576
567
  this.log('Trigger BUFFER_RESET');
577
568
  this.hls.trigger(Events.BUFFER_RESET, undefined);
@@ -884,7 +875,7 @@ export default class StreamController
884
875
  this.fragBufferedComplete(frag, part);
885
876
  }
886
877
 
887
- private onError(event: Events.ERROR, data: ErrorData) {
878
+ protected onError(event: Events.ERROR, data: ErrorData) {
888
879
  if (data.fatal) {
889
880
  this.state = State.ERROR;
890
881
  return;
@@ -942,8 +933,10 @@ export default class StreamController
942
933
 
943
934
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
944
935
  // 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);
936
+ const state = this.state;
937
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
938
+ const levelDetails = this.getLevelDetails();
939
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
947
940
  }
948
941
 
949
942
  this.lastCurrentTime = media.currentTime;
@@ -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);
@@ -360,7 +358,7 @@ export class SubtitleStreamController
360
358
  payload.byteLength > 0 &&
361
359
  decryptData?.key &&
362
360
  decryptData.iv &&
363
- decryptData.method === 'AES-128'
361
+ isFullSegmentEncryption(decryptData.method)
364
362
  ) {
365
363
  const startTime = performance.now();
366
364
  // decrypt the subtitles
@@ -369,6 +367,7 @@ export class SubtitleStreamController
369
367
  new Uint8Array(payload),
370
368
  decryptData.key.buffer,
371
369
  decryptData.iv.buffer,
370
+ getAesModeFromFullSegmentMethod(decryptData.method),
372
371
  )
373
372
  .catch((err) => {
374
373
  hls.trigger(Events.ERROR, {
@@ -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';
@@ -136,17 +135,12 @@ export class TimelineController implements ComponentAPI {
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(
@@ -410,7 +404,7 @@ export class TimelineController implements ComponentAPI {
410
404
  .filter((t) => t !== null)
411
405
  .map((t) => (t as TextTrack).label);
412
406
  if (unusedTextTracks.length) {
413
- logger.warn(
407
+ this.hls.logger.warn(
414
408
  `Media element contains unused subtitle tracks: ${unusedTextTracks.join(
415
409
  ', ',
416
410
  )}. Replace media element for each source to clear TextTracks and captions menu.`,
@@ -468,21 +462,19 @@ export class TimelineController implements ComponentAPI {
468
462
  }
469
463
 
470
464
  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
- }
477
465
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
478
- if (data.frag.type === PlaylistLevelType.MAIN) {
466
+ if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
467
+ const { cea608Parser1, cea608Parser2, lastSn } = this;
468
+ if (!cea608Parser1 || !cea608Parser2) {
469
+ return;
470
+ }
479
471
  const { cc, sn } = data.frag;
480
- const partIndex = data?.part?.index ?? -1;
472
+ const partIndex = data.part?.index ?? -1;
481
473
  if (
482
474
  !(
483
475
  sn === lastSn + 1 ||
484
- (sn === lastSn && partIndex === lastPartIndex + 1) ||
485
- cc === lastCc
476
+ (sn === lastSn && partIndex === this.lastPartIndex + 1) ||
477
+ cc === this.lastCc
486
478
  )
487
479
  ) {
488
480
  cea608Parser1.reset();
@@ -550,7 +542,7 @@ export class TimelineController implements ComponentAPI {
550
542
  });
551
543
  },
552
544
  (error) => {
553
- logger.log(`Failed to parse IMSC1: ${error}`);
545
+ hls.logger.log(`Failed to parse IMSC1: ${error}`);
554
546
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
555
547
  success: false,
556
548
  frag: frag,
@@ -597,7 +589,7 @@ export class TimelineController implements ComponentAPI {
597
589
  this._fallbackToIMSC1(frag, payload);
598
590
  }
599
591
  // Something went wrong while parsing. Trigger event with success false.
600
- logger.log(`Failed to parse VTT cue: ${error}`);
592
+ hls.logger.log(`Failed to parse VTT cue: ${error}`);
601
593
  if (missingInitPTS && maxAvCC > frag.cc) {
602
594
  return;
603
595
  }
@@ -669,9 +661,7 @@ export class TimelineController implements ComponentAPI {
669
661
  event: Events.FRAG_PARSING_USERDATA,
670
662
  data: FragParsingUserdataData,
671
663
  ) {
672
- this.initCea608Parsers();
673
- const { cea608Parser1, cea608Parser2 } = this;
674
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
664
+ if (!this.enabled || !this.config.enableCEA708Captions) {
675
665
  return;
676
666
  }
677
667
  const { frag, samples } = data;
@@ -686,9 +676,12 @@ export class TimelineController implements ComponentAPI {
686
676
  for (let i = 0; i < samples.length; i++) {
687
677
  const ccBytes = samples[i].bytes;
688
678
  if (ccBytes) {
679
+ if (!this.cea608Parser1) {
680
+ this.initCea608Parsers();
681
+ }
689
682
  const ccdatas = this.extractCea608Data(ccBytes);
690
- cea608Parser1.addData(samples[i].pts, ccdatas[0]);
691
- 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]);
692
685
  }
693
686
  }
694
687
  }