hls.js 1.5.4 → 1.5.5-0.canary.9977

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 (67) 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 +1930 -1095
  5. package/dist/hls.js.d.ts +63 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1609 -778
  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 +1363 -542
  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 +1635 -815
  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 +18 -18
  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 +7 -7
  25. package/src/controller/base-stream-controller.ts +56 -29
  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 +25 -3
  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 +5 -17
  36. package/src/controller/stream-controller.ts +25 -32
  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 +63 -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/remux/mp4-generator.ts +196 -1
  60. package/src/remux/mp4-remuxer.ts +23 -7
  61. package/src/task-loop.ts +5 -2
  62. package/src/types/component-api.ts +2 -0
  63. package/src/types/demuxer.ts +3 -0
  64. package/src/types/events.ts +4 -0
  65. package/src/utils/codecs.ts +33 -4
  66. package/src/utils/encryption-methods-util.ts +21 -0
  67. package/src/utils/logger.ts +53 -24
@@ -5,6 +5,7 @@ import { CmcdObjectType } from '@svta/common-media-library/cmcd/CmcdObjectType';
5
5
  import { CmcdStreamingFormat } from '@svta/common-media-library/cmcd/CmcdStreamingFormat';
6
6
  import { appendCmcdHeaders } from '@svta/common-media-library/cmcd/appendCmcdHeaders';
7
7
  import { appendCmcdQuery } from '@svta/common-media-library/cmcd/appendCmcdQuery';
8
+ import type { CmcdEncodeOptions } from '@svta/common-media-library/cmcd/CmcdEncodeOptions';
8
9
  import { uuid } from '@svta/common-media-library/utils/uuid';
9
10
  import { BufferHelper } from '../utils/buffer-helper';
10
11
  import { logger } from '../utils/logger';
@@ -165,7 +166,7 @@ export default class CMCDController implements ComponentAPI {
165
166
  data.su = this.buffering;
166
167
  }
167
168
 
168
- // TODO: Implement rtp, nrr, nor, dl
169
+ // TODO: Implement rtp, nrr, dl
169
170
 
170
171
  const { includeKeys } = this;
171
172
  if (includeKeys) {
@@ -175,14 +176,18 @@ export default class CMCDController implements ComponentAPI {
175
176
  }, {});
176
177
  }
177
178
 
179
+ const options: CmcdEncodeOptions = {
180
+ baseUrl: context.url,
181
+ };
182
+
178
183
  if (this.useHeaders) {
179
184
  if (!context.headers) {
180
185
  context.headers = {};
181
186
  }
182
187
 
183
- appendCmcdHeaders(context.headers, data);
188
+ appendCmcdHeaders(context.headers, data, options);
184
189
  } else {
185
- context.url = appendCmcdQuery(context.url, data);
190
+ context.url = appendCmcdQuery(context.url, data, options);
186
191
  }
187
192
  }
188
193
 
@@ -223,12 +228,29 @@ export default class CMCDController implements ComponentAPI {
223
228
  data.bl = this.getBufferLength(ot);
224
229
  }
225
230
 
231
+ const next = this.getNextFrag(fragment);
232
+ if (next) {
233
+ if (next.url && next.url !== fragment.url) {
234
+ data.nor = next.url;
235
+ }
236
+ }
237
+
226
238
  this.apply(context, data);
227
239
  } catch (error) {
228
240
  logger.warn('Could not generate segment CMCD data.', error);
229
241
  }
230
242
  };
231
243
 
244
+ private getNextFrag(fragment: Fragment): Fragment | undefined {
245
+ const levelDetails = this.hls.levels[fragment.level]?.details;
246
+ if (levelDetails) {
247
+ const index = (fragment.sn as number) - levelDetails.startSN;
248
+ return levelDetails.fragments[index + 1];
249
+ }
250
+
251
+ return undefined;
252
+ }
253
+
232
254
  /**
233
255
  * The CMCD object type.
234
256
  */
@@ -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,9 +48,11 @@ export type UriReplacement = {
48
48
 
49
49
  const PATHWAY_PENALTY_DURATION_MS = 300000;
50
50
 
51
- export default class ContentSteeringController implements NetworkComponentAPI {
51
+ export default class ContentSteeringController
52
+ extends Logger
53
+ implements NetworkComponentAPI
54
+ {
52
55
  private readonly hls: Hls;
53
- private log: (msg: any) => void;
54
56
  private loader: Loader<LoaderContext> | null = null;
55
57
  private uri: string | null = null;
56
58
  private pathwayId: string = '.';
@@ -66,8 +68,8 @@ export default class ContentSteeringController implements NetworkComponentAPI {
66
68
  private penalizedPathways: { [pathwayId: string]: number } = {};
67
69
 
68
70
  constructor(hls: Hls) {
71
+ super('content-steering', hls.logger);
69
72
  this.hls = hls;
70
- this.log = logger.log.bind(logger, `[content-steering]:`);
71
73
  this.registerListeners();
72
74
  }
73
75
 
@@ -203,7 +205,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
203
205
  errorAction.resolved = this.pathwayId !== errorPathway;
204
206
  }
205
207
  if (!errorAction.resolved) {
206
- logger.warn(
208
+ this.warn(
207
209
  `Could not resolve ${data.details} ("${
208
210
  data.error.message
209
211
  }") with content-steering for Pathway: ${errorPathway} levels: ${
@@ -442,7 +444,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
442
444
  ) => {
443
445
  this.log(`Loaded steering manifest: "${url}"`);
444
446
  const steeringData = response.data as SteeringManifest;
445
- if (steeringData.VERSION !== 1) {
447
+ if (steeringData?.VERSION !== 1) {
446
448
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
447
449
  return;
448
450
  }
@@ -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,9 +41,6 @@ import type {
41
41
  LoaderConfiguration,
42
42
  LoaderContext,
43
43
  } from '../types/loader';
44
-
45
- const LOGGER_PREFIX = '[eme]';
46
-
47
44
  interface KeySystemAccessPromises {
48
45
  keySystemAccess: Promise<MediaKeySystemAccess>;
49
46
  mediaKeys?: Promise<MediaKeys>;
@@ -68,7 +65,7 @@ export interface MediaKeySessionContext {
68
65
  * @class
69
66
  * @constructor
70
67
  */
71
- class EMEController implements ComponentAPI {
68
+ class EMEController extends Logger implements ComponentAPI {
72
69
  public static CDMCleanupPromise: Promise<void> | void;
73
70
 
74
71
  private readonly hls: Hls;
@@ -90,15 +87,9 @@ class EMEController implements ComponentAPI {
90
87
  private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
91
88
  ? [EMEController.CDMCleanupPromise]
92
89
  : [];
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);
100
90
 
101
91
  constructor(hls: Hls) {
92
+ super('eme', hls.logger);
102
93
  this.hls = hls;
103
94
  this.config = hls.config;
104
95
  this.registerListeners();
@@ -113,13 +104,9 @@ class EMEController implements ComponentAPI {
113
104
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
114
105
  config.drmSystems = config.drmSystemOptions = {};
115
106
  // @ts-ignore
116
- this.hls =
117
- this.onMediaEncrypted =
118
- this.onWaitingForKey =
119
- this.keyIdToKeySessionPromise =
120
- null as any;
107
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
121
108
  // @ts-ignore
122
- this.config = null;
109
+ this.onMediaEncrypted = this.onWaitingForKey = null;
123
110
  }
124
111
 
125
112
  private registerListeners() {
@@ -523,7 +510,7 @@ class EMEController implements ComponentAPI {
523
510
  return this.attemptKeySystemAccess(keySystemsToAttempt);
524
511
  }
525
512
 
526
- private _onMediaEncrypted(event: MediaEncryptedEvent) {
513
+ private onMediaEncrypted = (event: MediaEncryptedEvent) => {
527
514
  const { initDataType, initData } = event;
528
515
  this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
529
516
 
@@ -639,11 +626,11 @@ class EMEController implements ComponentAPI {
639
626
  );
640
627
  }
641
628
  keySessionContextPromise.catch((error) => this.handleError(error));
642
- }
629
+ };
643
630
 
644
- private _onWaitingForKey(event: Event) {
631
+ private onWaitingForKey = (event: Event) => {
645
632
  this.log(`"${event.type}" event`);
646
- }
633
+ };
647
634
 
648
635
  private attemptSetMediaKeys(
649
636
  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,19 +50,17 @@ type PenalizedRendition = {
50
50
 
51
51
  type PenalizedRenditions = { [key: number]: PenalizedRendition };
52
52
 
53
- export default class ErrorController implements NetworkComponentAPI {
53
+ export default class ErrorController
54
+ extends Logger
55
+ implements NetworkComponentAPI
56
+ {
54
57
  private readonly hls: Hls;
55
58
  private playlistError: number = 0;
56
59
  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);
62
63
  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]:`);
66
64
  this.registerListeners();
67
65
  }
68
66
 
@@ -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) {