hls.js 1.5.10-0.canary.10328 → 1.5.10

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 (88) hide show
  1. package/README.md +3 -4
  2. package/dist/hls-demo.js +38 -41
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2194 -3476
  5. package/dist/hls.js.d.ts +85 -108
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +3137 -3784
  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 +1256 -1928
  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 +4182 -5487
  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 +36 -36
  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 +14 -51
  30. package/src/controller/content-steering-controller.ts +15 -29
  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 -37
  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 +21 -19
  43. package/src/crypt/aes-crypto.ts +2 -21
  44. package/src/crypt/decrypter.ts +16 -32
  45. package/src/crypt/fast-aes-key.ts +5 -28
  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 +38 -75
  59. package/src/demux/video/avc-video-parser.ts +119 -208
  60. package/src/demux/video/base-video-parser.ts +18 -147
  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 +38 -61
  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 -4
  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/cea-608-parser.ts +3 -1
  78. package/src/utils/codecs.ts +5 -34
  79. package/src/utils/fetch-loader.ts +1 -1
  80. package/src/utils/imsc1-ttml-parser.ts +1 -1
  81. package/src/utils/keysystem-util.ts +6 -1
  82. package/src/utils/logger.ts +23 -58
  83. package/src/utils/mp4-tools.ts +3 -5
  84. package/src/utils/webvtt-parser.ts +1 -1
  85. package/src/crypt/decrypter-aes-mode.ts +0 -4
  86. package/src/demux/video/hevc-video-parser.ts +0 -749
  87. package/src/utils/encryption-methods-util.ts +0 -21
  88. package/src/utils/utf8-utils.ts +0 -18
@@ -1,15 +1,15 @@
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
- import type { Fragment, Part } from '../loader/fragment';
12
+ import type { Fragment } from '../loader/fragment';
13
13
  import type { BufferCreatedData, MediaAttachedData } from '../types/events';
14
14
  import type {
15
15
  FragmentLoaderContext,
@@ -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
 
@@ -209,11 +205,11 @@ export default class CMCDController implements ComponentAPI {
209
205
  */
210
206
  private applyFragmentData = (context: FragmentLoaderContext) => {
211
207
  try {
212
- const { frag, part } = context;
213
- const level = this.hls.levels[frag.level];
214
- const ot = this.getObjectType(frag);
208
+ const fragment = context.frag;
209
+ const level = this.hls.levels[fragment.level];
210
+ const ot = this.getObjectType(fragment);
215
211
  const data: Cmcd = {
216
- d: (part || frag).duration * 1000,
212
+ d: fragment.duration * 1000,
217
213
  ot,
218
214
  };
219
215
 
@@ -227,45 +223,12 @@ export default class CMCDController implements ComponentAPI {
227
223
  data.bl = this.getBufferLength(ot);
228
224
  }
229
225
 
230
- const next = part ? this.getNextPart(part) : this.getNextFrag(frag);
231
-
232
- if (next?.url && next.url !== frag.url) {
233
- data.nor = next.url;
234
- }
235
-
236
226
  this.apply(context, data);
237
227
  } catch (error) {
238
- this.hls.logger.warn('Could not generate segment CMCD data.', error);
228
+ logger.warn('Could not generate segment CMCD data.', error);
239
229
  }
240
230
  };
241
231
 
242
- private getNextFrag(fragment: Fragment): Fragment | undefined {
243
- const levelDetails = this.hls.levels[fragment.level]?.details;
244
- if (levelDetails) {
245
- const index = (fragment.sn as number) - levelDetails.startSN;
246
- return levelDetails.fragments[index + 1];
247
- }
248
-
249
- return undefined;
250
- }
251
-
252
- private getNextPart(part: Part): Part | undefined {
253
- const { index, fragment } = part;
254
- const partList = this.hls.levels[fragment.level]?.details?.partList;
255
-
256
- if (partList) {
257
- const { sn } = fragment;
258
- for (let i = partList.length - 1; i >= 0; i--) {
259
- const p = partList[i];
260
- if (p.index === index && p.fragment.sn === sn) {
261
- return partList[i + 1];
262
- }
263
- }
264
- }
265
-
266
- return undefined;
267
- }
268
-
269
232
  /**
270
233
  * The CMCD object type.
271
234
  */
@@ -324,7 +287,7 @@ export default class CMCDController implements ComponentAPI {
324
287
  * Get the buffer length for a media type in milliseconds
325
288
  */
326
289
  private getBufferLength(type: CmcdObjectType) {
327
- const media = this.media;
290
+ const media = this.hls.media;
328
291
  const buffer =
329
292
  type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
330
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,15 +48,13 @@ 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 = '.';
59
- private _pathwayPriority: string[] | null = null;
57
+ private pathwayPriority: string[] | null = null;
60
58
  private timeToLoad: number = 300;
61
59
  private reloadTimer: number = -1;
62
60
  private updated: number = 0;
@@ -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
 
@@ -92,23 +90,6 @@ export default class ContentSteeringController
92
90
  hls.off(Events.ERROR, this.onError, this);
93
91
  }
94
92
 
95
- pathways() {
96
- return (this.levels || []).reduce((pathways, level) => {
97
- if (pathways.indexOf(level.pathwayId) === -1) {
98
- pathways.push(level.pathwayId);
99
- }
100
- return pathways;
101
- }, [] as string[]);
102
- }
103
-
104
- get pathwayPriority(): string[] | null {
105
- return this._pathwayPriority;
106
- }
107
-
108
- set pathwayPriority(pathwayPriority: string[]) {
109
- this.updatePathwayPriority(pathwayPriority);
110
- }
111
-
112
93
  startLoad() {
113
94
  this.started = true;
114
95
  this.clearTimeout();
@@ -195,7 +176,7 @@ export default class ContentSteeringController
195
176
  errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost
196
177
  ) {
197
178
  const levels = this.levels;
198
- let pathwayPriority = this._pathwayPriority;
179
+ let pathwayPriority = this.pathwayPriority;
199
180
  let errorPathway = this.pathwayId;
200
181
  if (data.context) {
201
182
  const { groupId, pathwayId, type } = data.context;
@@ -210,14 +191,19 @@ export default class ContentSteeringController
210
191
  }
211
192
  if (!pathwayPriority && levels) {
212
193
  // If PATHWAY-PRIORITY was not provided, list pathways for error handling
213
- pathwayPriority = this.pathways();
194
+ pathwayPriority = levels.reduce((pathways, level) => {
195
+ if (pathways.indexOf(level.pathwayId) === -1) {
196
+ pathways.push(level.pathwayId);
197
+ }
198
+ return pathways;
199
+ }, [] as string[]);
214
200
  }
215
201
  if (pathwayPriority && pathwayPriority.length > 1) {
216
202
  this.updatePathwayPriority(pathwayPriority);
217
203
  errorAction.resolved = this.pathwayId !== errorPathway;
218
204
  }
219
205
  if (!errorAction.resolved) {
220
- this.warn(
206
+ logger.warn(
221
207
  `Could not resolve ${data.details} ("${
222
208
  data.error.message
223
209
  }") with content-steering for Pathway: ${errorPathway} levels: ${
@@ -259,7 +245,7 @@ export default class ContentSteeringController
259
245
  }
260
246
 
261
247
  private updatePathwayPriority(pathwayPriority: string[]) {
262
- this._pathwayPriority = pathwayPriority;
248
+ this.pathwayPriority = pathwayPriority;
263
249
  let levels: Level[] | undefined;
264
250
 
265
251
  // Evaluate if we should remove the pathway from the penalized list
@@ -456,7 +442,7 @@ export default class ContentSteeringController
456
442
  ) => {
457
443
  this.log(`Loaded steering manifest: "${url}"`);
458
444
  const steeringData = response.data as SteeringManifest;
459
- if (steeringData?.VERSION !== 1) {
445
+ if (steeringData.VERSION !== 1) {
460
446
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
461
447
  return;
462
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(