hls.js 1.5.8-0.canary.10172 → 1.5.8

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 (87) hide show
  1. package/README.md +3 -4
  2. package/dist/hls-demo.js +3 -12
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2366 -3626
  5. package/dist/hls.js.d.ts +84 -98
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1643 -2278
  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 +1258 -1903
  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 +1531 -2794
  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 +30 -30
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +20 -24
  22. package/src/controller/audio-stream-controller.ts +74 -68
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +10 -27
  25. package/src/controller/base-stream-controller.ts +38 -160
  26. package/src/controller/buffer-controller.ts +92 -230
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -3
  29. package/src/controller/cmcd-controller.ts +9 -30
  30. package/src/controller/content-steering-controller.ts +6 -8
  31. package/src/controller/eme-controller.ts +23 -10
  32. package/src/controller/error-controller.ts +8 -6
  33. package/src/controller/fps-controller.ts +3 -8
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/id3-track-controller.ts +7 -7
  37. package/src/controller/latency-controller.ts +11 -9
  38. package/src/controller/level-controller.ts +19 -13
  39. package/src/controller/stream-controller.ts +32 -37
  40. package/src/controller/subtitle-stream-controller.ts +40 -28
  41. package/src/controller/subtitle-track-controller.ts +3 -5
  42. package/src/controller/timeline-controller.ts +31 -25
  43. package/src/crypt/aes-crypto.ts +2 -21
  44. package/src/crypt/decrypter.ts +18 -32
  45. package/src/crypt/fast-aes-key.ts +5 -24
  46. package/src/demux/audio/aacdemuxer.ts +2 -2
  47. package/src/demux/audio/ac3-demuxer.ts +3 -4
  48. package/src/demux/audio/adts.ts +4 -9
  49. package/src/demux/audio/base-audio-demuxer.ts +14 -16
  50. package/src/demux/audio/mp3demuxer.ts +3 -4
  51. package/src/demux/audio/mpegaudio.ts +1 -1
  52. package/src/demux/id3.ts +411 -0
  53. package/src/demux/mp4demuxer.ts +7 -7
  54. package/src/demux/sample-aes.ts +0 -2
  55. package/src/demux/transmuxer-interface.ts +12 -4
  56. package/src/demux/transmuxer-worker.ts +4 -4
  57. package/src/demux/transmuxer.ts +3 -16
  58. package/src/demux/tsdemuxer.ts +37 -71
  59. package/src/demux/video/avc-video-parser.ts +119 -208
  60. package/src/demux/video/base-video-parser.ts +2 -134
  61. package/src/demux/video/exp-golomb.ts +208 -0
  62. package/src/events.ts +1 -8
  63. package/src/exports-named.ts +1 -1
  64. package/src/hls.ts +37 -49
  65. package/src/loader/fragment-loader.ts +3 -10
  66. package/src/loader/key-loader.ts +1 -3
  67. package/src/loader/level-key.ts +9 -10
  68. package/src/loader/playlist-loader.ts +5 -4
  69. package/src/remux/mp4-generator.ts +1 -196
  70. package/src/remux/mp4-remuxer.ts +8 -24
  71. package/src/task-loop.ts +2 -5
  72. package/src/types/component-api.ts +1 -3
  73. package/src/types/demuxer.ts +0 -3
  74. package/src/types/events.ts +0 -4
  75. package/src/types/remuxer.ts +1 -1
  76. package/src/utils/buffer-helper.ts +31 -12
  77. package/src/utils/codecs.ts +5 -34
  78. package/src/utils/fetch-loader.ts +1 -1
  79. package/src/utils/imsc1-ttml-parser.ts +1 -1
  80. package/src/utils/keysystem-util.ts +6 -1
  81. package/src/utils/logger.ts +23 -58
  82. package/src/utils/mp4-tools.ts +3 -5
  83. package/src/utils/webvtt-parser.ts +1 -1
  84. package/src/crypt/decrypter-aes-mode.ts +0 -4
  85. package/src/demux/video/hevc-video-parser.ts +0 -749
  86. package/src/utils/encryption-methods-util.ts +0 -21
  87. package/src/utils/utf8-utils.ts +0 -18
@@ -1,13 +1,13 @@
1
1
  import { Events } from '../events';
2
- import type Hls from '../hls';
2
+ import Hls from '../hls';
3
3
  import { Cmcd } from '@svta/common-media-library/cmcd/Cmcd';
4
4
  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';
9
8
  import { uuid } from '@svta/common-media-library/utils/uuid';
10
9
  import { BufferHelper } from '../utils/buffer-helper';
10
+ import { logger } from '../utils/logger';
11
11
  import type { ComponentAPI } from '../types/component-api';
12
12
  import type { Fragment } from '../loader/fragment';
13
13
  import type { BufferCreatedData, MediaAttachedData } from '../types/events';
@@ -81,7 +81,7 @@ export default class CMCDController implements ComponentAPI {
81
81
  // @ts-ignore
82
82
  this.hls = this.config = this.audioBuffer = this.videoBuffer = null;
83
83
  // @ts-ignore
84
- this.onWaiting = this.onPlaying = this.media = null;
84
+ this.onWaiting = this.onPlaying = null;
85
85
  }
86
86
 
87
87
  private onMediaAttached(
@@ -165,7 +165,7 @@ export default class CMCDController implements ComponentAPI {
165
165
  data.su = this.buffering;
166
166
  }
167
167
 
168
- // TODO: Implement rtp, nrr, dl
168
+ // TODO: Implement rtp, nrr, nor, dl
169
169
 
170
170
  const { includeKeys } = this;
171
171
  if (includeKeys) {
@@ -175,18 +175,14 @@ export default class CMCDController implements ComponentAPI {
175
175
  }, {});
176
176
  }
177
177
 
178
- const options: CmcdEncodeOptions = {
179
- baseUrl: context.url,
180
- };
181
-
182
178
  if (this.useHeaders) {
183
179
  if (!context.headers) {
184
180
  context.headers = {};
185
181
  }
186
182
 
187
- appendCmcdHeaders(context.headers, data, options);
183
+ appendCmcdHeaders(context.headers, data);
188
184
  } else {
189
- context.url = appendCmcdQuery(context.url, data, options);
185
+ context.url = appendCmcdQuery(context.url, data);
190
186
  }
191
187
  }
192
188
 
@@ -200,7 +196,7 @@ export default class CMCDController implements ComponentAPI {
200
196
  su: !this.initialized,
201
197
  });
202
198
  } catch (error) {
203
- this.hls.logger.warn('Could not generate manifest CMCD data.', error);
199
+ logger.warn('Could not generate manifest CMCD data.', error);
204
200
  }
205
201
  };
206
202
 
@@ -227,29 +223,12 @@ export default class CMCDController implements ComponentAPI {
227
223
  data.bl = this.getBufferLength(ot);
228
224
  }
229
225
 
230
- const next = this.getNextFrag(fragment);
231
- if (next) {
232
- if (next.url && next.url !== fragment.url) {
233
- data.nor = next.url;
234
- }
235
- }
236
-
237
226
  this.apply(context, data);
238
227
  } catch (error) {
239
- this.hls.logger.warn('Could not generate segment CMCD data.', error);
228
+ logger.warn('Could not generate segment CMCD data.', error);
240
229
  }
241
230
  };
242
231
 
243
- private getNextFrag(fragment: Fragment): Fragment | undefined {
244
- const levelDetails = this.hls.levels[fragment.level]?.details;
245
- if (levelDetails) {
246
- const index = (fragment.sn as number) - levelDetails.startSN;
247
- return levelDetails.fragments[index + 1];
248
- }
249
-
250
- return undefined;
251
- }
252
-
253
232
  /**
254
233
  * The CMCD object type.
255
234
  */
@@ -308,7 +287,7 @@ export default class CMCDController implements ComponentAPI {
308
287
  * Get the buffer length for a media type in milliseconds
309
288
  */
310
289
  private getBufferLength(type: CmcdObjectType) {
311
- const media = this.media;
290
+ const media = this.hls.media;
312
291
  const buffer =
313
292
  type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
314
293
 
@@ -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,
@@ -19,7 +19,7 @@ import {
19
19
  KeySystems,
20
20
  requestMediaKeySystemAccess,
21
21
  } from '../utils/mediakeys-helper';
22
- import { strToUtf8array } from '../utils/utf8-utils';
22
+ import { strToUtf8array } from '../utils/keysystem-util';
23
23
  import { base64Decode } from '../utils/numeric-encoding-utils';
24
24
  import { DecryptData, LevelKey } from '../loader/level-key';
25
25
  import Hex from '../utils/hex';
@@ -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';
@@ -27,12 +28,10 @@ class FPSController implements ComponentAPI {
27
28
 
28
29
  protected registerListeners() {
29
30
  this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
30
- this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
31
31
  }
32
32
 
33
33
  protected unregisterListeners() {
34
34
  this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
35
- this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
36
35
  }
37
36
 
38
37
  destroy() {
@@ -66,10 +65,6 @@ class FPSController implements ComponentAPI {
66
65
  }
67
66
  }
68
67
 
69
- private onMediaDetaching() {
70
- this.media = null;
71
- }
72
-
73
68
  checkFPS(
74
69
  video: HTMLVideoElement,
75
70
  decodedFrames: number,
@@ -89,13 +84,13 @@ class FPSController implements ComponentAPI {
89
84
  totalDroppedFrames: droppedFrames,
90
85
  });
91
86
  if (droppedFPS > 0) {
92
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
87
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
93
88
  if (
94
89
  currentDropped >
95
90
  hls.config.fpsDroppedMonitoringThreshold * currentDecoded
96
91
  ) {
97
92
  let currentLevel = hls.currentLevel;
98
- hls.logger.warn(
93
+ logger.warn(
99
94
  'drop FPS ratio greater than max allowed value for currentLevel: ' +
100
95
  currentLevel,
101
96
  );
@@ -107,23 +107,12 @@ export class FragmentTracker implements ComponentAPI {
107
107
  public getBufferedFrag(
108
108
  position: number,
109
109
  levelType: PlaylistLevelType,
110
- ): Fragment | null {
111
- return this.getFragAtPos(position, levelType, true);
112
- }
113
-
114
- public getFragAtPos(
115
- position: number,
116
- levelType: PlaylistLevelType,
117
- buffered?: boolean,
118
110
  ): Fragment | null {
119
111
  const { fragments } = this;
120
112
  const keys = Object.keys(fragments);
121
113
  for (let i = keys.length; i--; ) {
122
114
  const fragmentEntity = fragments[keys[i]];
123
- if (
124
- fragmentEntity?.body.type === levelType &&
125
- (!buffered || fragmentEntity.buffered)
126
- ) {
115
+ if (fragmentEntity?.body.type === levelType && fragmentEntity.buffered) {
127
116
  const frag = fragmentEntity.body;
128
117
  if (frag.start <= position && position <= frag.end) {
129
118
  return frag;
@@ -412,7 +401,7 @@ export class FragmentTracker implements ComponentAPI {
412
401
  event: Events.BUFFER_APPENDED,
413
402
  data: BufferAppendedData,
414
403
  ) {
415
- const { frag, part, timeRanges, type } = data;
404
+ const { frag, part, timeRanges } = data;
416
405
  if (frag.sn === 'initSegment') {
417
406
  return;
418
407
  }
@@ -426,8 +415,15 @@ export class FragmentTracker implements ComponentAPI {
426
415
  }
427
416
  // Store the latest timeRanges loaded in the buffer
428
417
  this.timeRanges = timeRanges;
429
- const timeRange = timeRanges[type] as TimeRanges;
430
- this.detectEvictedFragments(type, timeRange, playlistType, part);
418
+ Object.keys(timeRanges).forEach((elementaryStream: SourceBufferName) => {
419
+ const timeRange = timeRanges[elementaryStream] as TimeRanges;
420
+ this.detectEvictedFragments(
421
+ elementaryStream,
422
+ timeRange,
423
+ playlistType,
424
+ part,
425
+ );
426
+ });
431
427
  }
432
428
 
433
429
  private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
@@ -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?.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,
@@ -4,6 +4,7 @@ import {
4
4
  clearCurrentCues,
5
5
  removeCuesInRange,
6
6
  } from '../utils/texttrack-utils';
7
+ import * as ID3 from '../demux/id3';
7
8
  import {
8
9
  DateRange,
9
10
  isDateRangeCueAttribute,
@@ -18,8 +19,6 @@ import type {
18
19
  } from '../types/events';
19
20
  import type { ComponentAPI } from '../types/component-api';
20
21
  import type Hls from '../hls';
21
- import { getId3Frames } from '@svta/common-media-library/id3/getId3Frames';
22
- import { isId3TimestampFrame } from '@svta/common-media-library/id3/isId3TimestampFrame';
23
22
 
24
23
  declare global {
25
24
  interface Window {
@@ -138,10 +137,11 @@ class ID3TrackController implements ComponentAPI {
138
137
  }
139
138
 
140
139
  protected onMediaDetaching(): void {
141
- if (this.id3Track) {
142
- clearCurrentCues(this.id3Track);
143
- this.id3Track = null;
140
+ if (!this.id3Track) {
141
+ return;
144
142
  }
143
+ clearCurrentCues(this.id3Track);
144
+ this.id3Track = null;
145
145
  this.media = null;
146
146
  this.dateRangeCuesAppended = {};
147
147
  }
@@ -211,7 +211,7 @@ class ID3TrackController implements ComponentAPI {
211
211
  continue;
212
212
  }
213
213
 
214
- const frames = getId3Frames(samples[i].data);
214
+ const frames = ID3.getID3Frames(samples[i].data);
215
215
  if (frames) {
216
216
  const startTime = samples[i].pts;
217
217
  let endTime: number = startTime + samples[i].duration;
@@ -228,7 +228,7 @@ class ID3TrackController implements ComponentAPI {
228
228
  for (let j = 0; j < frames.length; j++) {
229
229
  const frame = frames[j];
230
230
  // Safari doesn't put the timestamp frame in the TextTrack
231
- if (!isId3TimestampFrame(frame)) {
231
+ if (!ID3.isTimeStampFrame(frame)) {
232
232
  // add a bounds to any unbounded cues
233
233
  this.updateId3CueEnds(startTime, type);
234
234
  const cue = createCueWithDataFields(
@@ -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;