hls.js 1.5.2-0.canary.9934 → 1.5.3

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 (60) hide show
  1. package/dist/hls-demo.js +0 -5
  2. package/dist/hls-demo.js.map +1 -1
  3. package/dist/hls.js +757 -883
  4. package/dist/hls.js.d.ts +47 -56
  5. package/dist/hls.js.map +1 -1
  6. package/dist/hls.light.js +477 -600
  7. package/dist/hls.light.js.map +1 -1
  8. package/dist/hls.light.min.js +1 -1
  9. package/dist/hls.light.min.js.map +1 -1
  10. package/dist/hls.light.mjs +335 -446
  11. package/dist/hls.light.mjs.map +1 -1
  12. package/dist/hls.min.js +1 -1
  13. package/dist/hls.min.js.map +1 -1
  14. package/dist/hls.mjs +572 -681
  15. package/dist/hls.mjs.map +1 -1
  16. package/dist/hls.worker.js +1 -1
  17. package/dist/hls.worker.js.map +1 -1
  18. package/package.json +11 -11
  19. package/src/config.ts +2 -3
  20. package/src/controller/abr-controller.ts +22 -22
  21. package/src/controller/audio-stream-controller.ts +14 -11
  22. package/src/controller/audio-track-controller.ts +1 -1
  23. package/src/controller/base-playlist-controller.ts +7 -7
  24. package/src/controller/base-stream-controller.ts +29 -47
  25. package/src/controller/buffer-controller.ts +11 -10
  26. package/src/controller/cap-level-controller.ts +2 -1
  27. package/src/controller/cmcd-controller.ts +3 -25
  28. package/src/controller/content-steering-controller.ts +6 -8
  29. package/src/controller/eme-controller.ts +22 -9
  30. package/src/controller/error-controller.ts +8 -6
  31. package/src/controller/fps-controller.ts +3 -2
  32. package/src/controller/gap-controller.ts +16 -43
  33. package/src/controller/latency-controller.ts +11 -9
  34. package/src/controller/level-controller.ts +17 -5
  35. package/src/controller/stream-controller.ts +31 -24
  36. package/src/controller/subtitle-stream-controller.ts +14 -13
  37. package/src/controller/subtitle-track-controller.ts +3 -5
  38. package/src/controller/timeline-controller.ts +30 -23
  39. package/src/crypt/aes-crypto.ts +2 -21
  40. package/src/crypt/decrypter.ts +18 -32
  41. package/src/crypt/fast-aes-key.ts +5 -24
  42. package/src/demux/audio/adts.ts +4 -9
  43. package/src/demux/sample-aes.ts +0 -2
  44. package/src/demux/transmuxer-interface.ts +12 -4
  45. package/src/demux/transmuxer-worker.ts +4 -4
  46. package/src/demux/transmuxer.ts +3 -16
  47. package/src/demux/tsdemuxer.ts +17 -12
  48. package/src/events.ts +0 -7
  49. package/src/hls.ts +20 -33
  50. package/src/loader/fragment-loader.ts +2 -9
  51. package/src/loader/key-loader.ts +0 -2
  52. package/src/loader/level-key.ts +9 -10
  53. package/src/remux/mp4-remuxer.ts +4 -20
  54. package/src/task-loop.ts +2 -5
  55. package/src/types/demuxer.ts +0 -1
  56. package/src/types/events.ts +0 -4
  57. package/src/utils/codecs.ts +4 -33
  58. package/src/utils/logger.ts +24 -53
  59. package/src/crypt/decrypter-aes-mode.ts +0 -4
  60. package/src/utils/encryption-methods-util.ts +0 -21
@@ -3,7 +3,7 @@ import { Level } from '../types/level';
3
3
  import { reassignFragmentLevelIndexes } from '../utils/level-helper';
4
4
  import { AttrList } from '../utils/attr-list';
5
5
  import { ErrorActionFlags, NetworkErrorAction } from './error-controller';
6
- import { Logger } from '../utils/logger';
6
+ import { logger } from '../utils/logger';
7
7
  import {
8
8
  PlaylistContextType,
9
9
  type Loader,
@@ -48,11 +48,9 @@ export type UriReplacement = {
48
48
 
49
49
  const PATHWAY_PENALTY_DURATION_MS = 300000;
50
50
 
51
- export default class ContentSteeringController
52
- extends Logger
53
- implements NetworkComponentAPI
54
- {
51
+ export default class ContentSteeringController implements NetworkComponentAPI {
55
52
  private readonly hls: Hls;
53
+ private log: (msg: any) => void;
56
54
  private loader: Loader<LoaderContext> | null = null;
57
55
  private uri: string | null = null;
58
56
  private pathwayId: string = '.';
@@ -68,8 +66,8 @@ export default class ContentSteeringController
68
66
  private penalizedPathways: { [pathwayId: string]: number } = {};
69
67
 
70
68
  constructor(hls: Hls) {
71
- super('content-steering', hls.logger);
72
69
  this.hls = hls;
70
+ this.log = logger.log.bind(logger, `[content-steering]:`);
73
71
  this.registerListeners();
74
72
  }
75
73
 
@@ -205,7 +203,7 @@ export default class ContentSteeringController
205
203
  errorAction.resolved = this.pathwayId !== errorPathway;
206
204
  }
207
205
  if (!errorAction.resolved) {
208
- this.warn(
206
+ logger.warn(
209
207
  `Could not resolve ${data.details} ("${
210
208
  data.error.message
211
209
  }") with content-steering for Pathway: ${errorPathway} levels: ${
@@ -444,7 +442,7 @@ export default class ContentSteeringController
444
442
  ) => {
445
443
  this.log(`Loaded steering manifest: "${url}"`);
446
444
  const steeringData = response.data as SteeringManifest;
447
- if (steeringData?.VERSION !== 1) {
445
+ if (steeringData.VERSION !== 1) {
448
446
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
449
447
  return;
450
448
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Events } from '../events';
7
7
  import { ErrorTypes, ErrorDetails } from '../errors';
8
- import { Logger } from '../utils/logger';
8
+ import { logger } from '../utils/logger';
9
9
  import {
10
10
  getKeySystemsForConfig,
11
11
  getSupportedMediaKeySystemConfigurations,
@@ -41,6 +41,9 @@ import type {
41
41
  LoaderConfiguration,
42
42
  LoaderContext,
43
43
  } from '../types/loader';
44
+
45
+ const LOGGER_PREFIX = '[eme]';
46
+
44
47
  interface KeySystemAccessPromises {
45
48
  keySystemAccess: Promise<MediaKeySystemAccess>;
46
49
  mediaKeys?: Promise<MediaKeys>;
@@ -65,7 +68,7 @@ export interface MediaKeySessionContext {
65
68
  * @class
66
69
  * @constructor
67
70
  */
68
- class EMEController extends Logger implements ComponentAPI {
71
+ class EMEController implements ComponentAPI {
69
72
  public static CDMCleanupPromise: Promise<void> | void;
70
73
 
71
74
  private readonly hls: Hls;
@@ -87,9 +90,15 @@ class EMEController extends Logger implements ComponentAPI {
87
90
  private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
88
91
  ? [EMEController.CDMCleanupPromise]
89
92
  : [];
93
+ private onMediaEncrypted = this._onMediaEncrypted.bind(this);
94
+ private onWaitingForKey = this._onWaitingForKey.bind(this);
95
+
96
+ private debug: (msg: any) => void = logger.debug.bind(logger, LOGGER_PREFIX);
97
+ private log: (msg: any) => void = logger.log.bind(logger, LOGGER_PREFIX);
98
+ private warn: (msg: any) => void = logger.warn.bind(logger, LOGGER_PREFIX);
99
+ private error: (msg: any) => void = logger.error.bind(logger, LOGGER_PREFIX);
90
100
 
91
101
  constructor(hls: Hls) {
92
- super('eme', hls.logger);
93
102
  this.hls = hls;
94
103
  this.config = hls.config;
95
104
  this.registerListeners();
@@ -104,9 +113,13 @@ class EMEController extends Logger implements ComponentAPI {
104
113
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
105
114
  config.drmSystems = config.drmSystemOptions = {};
106
115
  // @ts-ignore
107
- this.hls = this.config = this.keyIdToKeySessionPromise = null;
116
+ this.hls =
117
+ this.onMediaEncrypted =
118
+ this.onWaitingForKey =
119
+ this.keyIdToKeySessionPromise =
120
+ null as any;
108
121
  // @ts-ignore
109
- this.onMediaEncrypted = this.onWaitingForKey = null;
122
+ this.config = null;
110
123
  }
111
124
 
112
125
  private registerListeners() {
@@ -510,7 +523,7 @@ class EMEController extends Logger implements ComponentAPI {
510
523
  return this.attemptKeySystemAccess(keySystemsToAttempt);
511
524
  }
512
525
 
513
- private onMediaEncrypted = (event: MediaEncryptedEvent) => {
526
+ private _onMediaEncrypted(event: MediaEncryptedEvent) {
514
527
  const { initDataType, initData } = event;
515
528
  this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
516
529
 
@@ -626,11 +639,11 @@ class EMEController extends Logger implements ComponentAPI {
626
639
  );
627
640
  }
628
641
  keySessionContextPromise.catch((error) => this.handleError(error));
629
- };
642
+ }
630
643
 
631
- private onWaitingForKey = (event: Event) => {
644
+ private _onWaitingForKey(event: Event) {
632
645
  this.log(`"${event.type}" event`);
633
- };
646
+ }
634
647
 
635
648
  private attemptSetMediaKeys(
636
649
  keySystem: KeySystems,
@@ -8,7 +8,7 @@ import {
8
8
  } from '../utils/error-helper';
9
9
  import { findFragmentByPTS } from './fragment-finders';
10
10
  import { HdcpLevel, HdcpLevels } from '../types/level';
11
- import { Logger } from '../utils/logger';
11
+ import { logger } from '../utils/logger';
12
12
  import type Hls from '../hls';
13
13
  import type { RetryConfig } from '../config';
14
14
  import type { NetworkComponentAPI } from '../types/component-api';
@@ -50,17 +50,19 @@ type PenalizedRendition = {
50
50
 
51
51
  type PenalizedRenditions = { [key: number]: PenalizedRendition };
52
52
 
53
- export default class ErrorController
54
- extends Logger
55
- implements NetworkComponentAPI
56
- {
53
+ export default class ErrorController implements NetworkComponentAPI {
57
54
  private readonly hls: Hls;
58
55
  private playlistError: number = 0;
59
56
  private penalizedRenditions: PenalizedRenditions = {};
57
+ private log: (msg: any) => void;
58
+ private warn: (msg: any) => void;
59
+ private error: (msg: any) => void;
60
60
 
61
61
  constructor(hls: Hls) {
62
- super('error-controller', hls.logger);
63
62
  this.hls = hls;
63
+ this.log = logger.log.bind(logger, `[info]:`);
64
+ this.warn = logger.warn.bind(logger, `[warning]:`);
65
+ this.error = logger.error.bind(logger, `[error]:`);
64
66
  this.registerListeners();
65
67
  }
66
68
 
@@ -1,4 +1,5 @@
1
1
  import { Events } from '../events';
2
+ import { logger } from '../utils/logger';
2
3
  import type { ComponentAPI } from '../types/component-api';
3
4
  import type Hls from '../hls';
4
5
  import type { MediaAttachingData } from '../types/events';
@@ -83,13 +84,13 @@ class FPSController implements ComponentAPI {
83
84
  totalDroppedFrames: droppedFrames,
84
85
  });
85
86
  if (droppedFPS > 0) {
86
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
87
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
87
88
  if (
88
89
  currentDropped >
89
90
  hls.config.fpsDroppedMonitoringThreshold * currentDecoded
90
91
  ) {
91
92
  let currentLevel = hls.currentLevel;
92
- hls.logger.warn(
93
+ logger.warn(
93
94
  'drop FPS ratio greater than max allowed value for currentLevel: ' +
94
95
  currentLevel,
95
96
  );
@@ -1,22 +1,20 @@
1
- import { State } from './base-stream-controller';
1
+ import type { BufferInfo } from '../utils/buffer-helper';
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';
9
8
  import type { HlsConfig } from '../config';
10
9
  import type { Fragment } from '../loader/fragment';
11
10
  import type { FragmentTracker } from './fragment-tracker';
12
- import type { LevelDetails } from '../loader/level-details';
13
11
 
14
12
  export const STALL_MINIMUM_DURATION_MS = 250;
15
13
  export const MAX_START_GAP_JUMP = 2.0;
16
14
  export const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
17
15
  export const SKIP_BUFFER_RANGE_START = 0.05;
18
16
 
19
- export default class GapController extends Logger {
17
+ export default class GapController {
20
18
  private config: HlsConfig;
21
19
  private media: HTMLMediaElement | null = null;
22
20
  private fragmentTracker: FragmentTracker;
@@ -26,15 +24,8 @@ export default class GapController extends Logger {
26
24
  private stalled: number | null = null;
27
25
  private moved: boolean = false;
28
26
  private seeking: boolean = false;
29
- private ended: number = 0;
30
27
 
31
- constructor(
32
- config: HlsConfig,
33
- media: HTMLMediaElement,
34
- fragmentTracker: FragmentTracker,
35
- hls: Hls,
36
- ) {
37
- super('gap-controller', hls.logger);
28
+ constructor(config, media, fragmentTracker, hls) {
38
29
  this.config = config;
39
30
  this.media = media;
40
31
  this.fragmentTracker = fragmentTracker;
@@ -53,12 +44,7 @@ export default class GapController extends Logger {
53
44
  *
54
45
  * @param lastCurrentTime - Previously read playhead position
55
46
  */
56
- public poll(
57
- lastCurrentTime: number,
58
- activeFrag: Fragment | null,
59
- levelDetails: LevelDetails | undefined,
60
- state: string,
61
- ) {
47
+ public poll(lastCurrentTime: number, activeFrag: Fragment | null) {
62
48
  const { config, media, stalled } = this;
63
49
  if (media === null) {
64
50
  return;
@@ -71,7 +57,6 @@ export default class GapController extends Logger {
71
57
 
72
58
  // The playhead is moving, no-op
73
59
  if (currentTime !== lastCurrentTime) {
74
- this.ended = 0;
75
60
  this.moved = true;
76
61
  if (!seeking) {
77
62
  this.nudgeRetry = 0;
@@ -80,7 +65,7 @@ export default class GapController extends Logger {
80
65
  // The playhead is now moving, but was previously stalled
81
66
  if (this.stallReported) {
82
67
  const stalledDuration = self.performance.now() - stalled;
83
- this.warn(
68
+ logger.warn(
84
69
  `playback not stuck anymore @${currentTime}, after ${Math.round(
85
70
  stalledDuration,
86
71
  )}ms`,
@@ -143,9 +128,12 @@ export default class GapController extends Logger {
143
128
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
144
129
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
145
130
  // that begins over 1 target duration after the video start position.
146
- const isLive = !!levelDetails?.live;
131
+ const level = this.hls.levels
132
+ ? this.hls.levels[this.hls.currentLevel]
133
+ : null;
134
+ const isLive = level?.details?.live;
147
135
  const maxStartGapJump = isLive
148
- ? levelDetails!.targetduration * 2
136
+ ? level!.details!.targetduration * 2
149
137
  : MAX_START_GAP_JUMP;
150
138
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
151
139
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
@@ -165,21 +153,6 @@ export default class GapController extends Logger {
165
153
 
166
154
  const stalledDuration = tnow - stalled;
167
155
  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
- }
183
156
  // Report stalling after trying to fix
184
157
  this._reportStall(bufferInfo);
185
158
  if (!this.media) {
@@ -233,7 +206,7 @@ export default class GapController extends Logger {
233
206
  bufferInfo.nextStart - currentTime < config.maxBufferHole)) &&
234
207
  stalledDurationMs > config.highBufferWatchdogPeriod * 1000
235
208
  ) {
236
- this.warn('Trying to nudge playhead over buffer-hole');
209
+ logger.warn('Trying to nudge playhead over buffer-hole');
237
210
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
238
211
  // We only try to jump the hole if it's under the configured size
239
212
  // Reset stalled so to rearm watchdog timer
@@ -257,7 +230,7 @@ export default class GapController extends Logger {
257
230
  media.currentTime
258
231
  } due to low buffer (${JSON.stringify(bufferInfo)})`,
259
232
  );
260
- this.warn(error.message);
233
+ logger.warn(error.message);
261
234
  hls.trigger(Events.ERROR, {
262
235
  type: ErrorTypes.MEDIA_ERROR,
263
236
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -332,7 +305,7 @@ export default class GapController extends Logger {
332
305
  startTime + SKIP_BUFFER_RANGE_START,
333
306
  currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS,
334
307
  );
335
- this.warn(
308
+ logger.warn(
336
309
  `skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`,
337
310
  );
338
311
  this.moved = true;
@@ -375,7 +348,7 @@ export default class GapController extends Logger {
375
348
  const error = new Error(
376
349
  `Nudging 'currentTime' from ${currentTime} to ${targetTime}`,
377
350
  );
378
- this.warn(error.message);
351
+ logger.warn(error.message);
379
352
  media.currentTime = targetTime;
380
353
  hls.trigger(Events.ERROR, {
381
354
  type: ErrorTypes.MEDIA_ERROR,
@@ -387,7 +360,7 @@ export default class GapController extends Logger {
387
360
  const error = new Error(
388
361
  `Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`,
389
362
  );
390
- this.error(error.message);
363
+ logger.error(error.message);
391
364
  hls.trigger(Events.ERROR, {
392
365
  type: ErrorTypes.MEDIA_ERROR,
393
366
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -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;
@@ -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) {
@@ -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
 
@@ -509,8 +515,10 @@ export default class StreamController
509
515
  ) {
510
516
  super.onMediaAttached(event, data);
511
517
  const media = data.media;
512
- media.addEventListener('playing', this.onMediaPlaying);
513
- media.addEventListener('seeked', this.onMediaSeeked);
518
+ this.onvplaying = this.onMediaPlaying.bind(this);
519
+ this.onvseeked = this.onMediaSeeked.bind(this);
520
+ media.addEventListener('playing', this.onvplaying as EventListener);
521
+ media.addEventListener('seeked', this.onvseeked as EventListener);
514
522
  this.gapController = new GapController(
515
523
  this.config,
516
524
  media,
@@ -521,9 +529,10 @@ export default class StreamController
521
529
 
522
530
  protected onMediaDetaching() {
523
531
  const { media } = this;
524
- if (media) {
525
- media.removeEventListener('playing', this.onMediaPlaying);
526
- media.removeEventListener('seeked', this.onMediaSeeked);
532
+ if (media && this.onvplaying && this.onvseeked) {
533
+ media.removeEventListener('playing', this.onvplaying);
534
+ media.removeEventListener('seeked', this.onvseeked);
535
+ this.onvplaying = this.onvseeked = null;
527
536
  this.videoBuffer = null;
528
537
  }
529
538
  this.fragPlaying = null;
@@ -534,12 +543,12 @@ export default class StreamController
534
543
  super.onMediaDetaching();
535
544
  }
536
545
 
537
- private onMediaPlaying = () => {
546
+ private onMediaPlaying() {
538
547
  // tick to speed up FRAG_CHANGED triggering
539
548
  this.tick();
540
- };
549
+ }
541
550
 
542
- private onMediaSeeked = () => {
551
+ private onMediaSeeked() {
543
552
  const media = this.media;
544
553
  const currentTime = media ? media.currentTime : null;
545
554
  if (Number.isFinite(currentTime)) {
@@ -559,9 +568,9 @@ export default class StreamController
559
568
 
560
569
  // tick to speed up FRAG_CHANGED triggering
561
570
  this.tick();
562
- };
571
+ }
563
572
 
564
- protected onManifestLoading() {
573
+ private onManifestLoading() {
565
574
  // reset buffer on manifest loading
566
575
  this.log('Trigger BUFFER_RESET');
567
576
  this.hls.trigger(Events.BUFFER_RESET, undefined);
@@ -874,7 +883,7 @@ export default class StreamController
874
883
  this.fragBufferedComplete(frag, part);
875
884
  }
876
885
 
877
- protected onError(event: Events.ERROR, data: ErrorData) {
886
+ private onError(event: Events.ERROR, data: ErrorData) {
878
887
  if (data.fatal) {
879
888
  this.state = State.ERROR;
880
889
  return;
@@ -932,10 +941,8 @@ export default class StreamController
932
941
 
933
942
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
934
943
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
935
- const state = this.state;
936
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
937
- const levelDetails = this.getLevelDetails();
938
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
944
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
945
+ gapController.poll(this.lastCurrentTime, activeFrag);
939
946
  }
940
947
 
941
948
  this.lastCurrentTime = media.currentTime;