hls.js 1.5.13 → 1.5.14-0.canary.10415

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 (103) hide show
  1. package/README.md +4 -3
  2. package/dist/hls-demo.js +41 -38
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +4211 -2666
  5. package/dist/hls.js.d.ts +179 -110
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2841 -1921
  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 +2569 -1639
  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 +3572 -2017
  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 +38 -38
  20. package/src/config.ts +5 -2
  21. package/src/controller/abr-controller.ts +39 -25
  22. package/src/controller/audio-stream-controller.ts +156 -136
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +27 -10
  25. package/src/controller/base-stream-controller.ts +234 -89
  26. package/src/controller/buffer-controller.ts +250 -97
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +3 -2
  29. package/src/controller/cmcd-controller.ts +51 -14
  30. package/src/controller/content-steering-controller.ts +29 -15
  31. package/src/controller/eme-controller.ts +10 -23
  32. package/src/controller/error-controller.ts +28 -22
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-finders.ts +44 -16
  35. package/src/controller/fragment-tracker.ts +58 -25
  36. package/src/controller/gap-controller.ts +43 -16
  37. package/src/controller/id3-track-controller.ts +45 -35
  38. package/src/controller/latency-controller.ts +18 -13
  39. package/src/controller/level-controller.ts +37 -19
  40. package/src/controller/stream-controller.ts +100 -83
  41. package/src/controller/subtitle-stream-controller.ts +35 -47
  42. package/src/controller/subtitle-track-controller.ts +5 -3
  43. package/src/controller/timeline-controller.ts +20 -22
  44. package/src/crypt/aes-crypto.ts +21 -2
  45. package/src/crypt/decrypter-aes-mode.ts +4 -0
  46. package/src/crypt/decrypter.ts +32 -16
  47. package/src/crypt/fast-aes-key.ts +28 -5
  48. package/src/demux/audio/aacdemuxer.ts +2 -2
  49. package/src/demux/audio/ac3-demuxer.ts +4 -3
  50. package/src/demux/audio/adts.ts +9 -4
  51. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  52. package/src/demux/audio/mp3demuxer.ts +4 -3
  53. package/src/demux/audio/mpegaudio.ts +1 -1
  54. package/src/demux/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +2 -0
  56. package/src/demux/transmuxer-interface.ts +8 -16
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +16 -3
  59. package/src/demux/tsdemuxer.ts +75 -38
  60. package/src/demux/video/avc-video-parser.ts +210 -121
  61. package/src/demux/video/base-video-parser.ts +135 -2
  62. package/src/demux/video/exp-golomb.ts +0 -208
  63. package/src/demux/video/hevc-video-parser.ts +749 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +84 -47
  67. package/src/loader/date-range.ts +71 -5
  68. package/src/loader/fragment-loader.ts +23 -21
  69. package/src/loader/fragment.ts +8 -4
  70. package/src/loader/key-loader.ts +3 -1
  71. package/src/loader/level-details.ts +6 -6
  72. package/src/loader/level-key.ts +10 -9
  73. package/src/loader/m3u8-parser.ts +138 -144
  74. package/src/loader/playlist-loader.ts +5 -7
  75. package/src/remux/mp4-generator.ts +196 -1
  76. package/src/remux/mp4-remuxer.ts +32 -62
  77. package/src/remux/passthrough-remuxer.ts +1 -1
  78. package/src/task-loop.ts +5 -2
  79. package/src/types/component-api.ts +3 -1
  80. package/src/types/demuxer.ts +3 -0
  81. package/src/types/events.ts +19 -6
  82. package/src/types/fragment-tracker.ts +2 -2
  83. package/src/types/media-playlist.ts +9 -1
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +96 -9
  86. package/src/utils/buffer-helper.ts +12 -31
  87. package/src/utils/cea-608-parser.ts +1 -3
  88. package/src/utils/codecs.ts +34 -5
  89. package/src/utils/encryption-methods-util.ts +21 -0
  90. package/src/utils/fetch-loader.ts +1 -1
  91. package/src/utils/hash.ts +10 -0
  92. package/src/utils/hdr.ts +4 -7
  93. package/src/utils/imsc1-ttml-parser.ts +1 -1
  94. package/src/utils/keysystem-util.ts +1 -6
  95. package/src/utils/level-helper.ts +71 -44
  96. package/src/utils/logger.ts +58 -23
  97. package/src/utils/mp4-tools.ts +5 -3
  98. package/src/utils/rendition-helper.ts +100 -74
  99. package/src/utils/utf8-utils.ts +18 -0
  100. package/src/utils/variable-substitution.ts +0 -19
  101. package/src/utils/webvtt-parser.ts +2 -12
  102. package/src/demux/id3.ts +0 -411
  103. package/src/types/general.ts +0 -6
@@ -1,15 +1,15 @@
1
1
  import { Events } from '../events';
2
- import Hls from '../hls';
2
+ import type 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';
8
9
  import { uuid } from '@svta/common-media-library/utils/uuid';
9
10
  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 } from '../loader/fragment';
12
+ import type { Fragment, Part } 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 = null;
84
+ this.onWaiting = this.onPlaying = this.media = 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, nor, dl
168
+ // TODO: Implement rtp, nrr, dl
169
169
 
170
170
  const { includeKeys } = this;
171
171
  if (includeKeys) {
@@ -175,14 +175,18 @@ export default class CMCDController implements ComponentAPI {
175
175
  }, {});
176
176
  }
177
177
 
178
+ const options: CmcdEncodeOptions = {
179
+ baseUrl: context.url,
180
+ };
181
+
178
182
  if (this.useHeaders) {
179
183
  if (!context.headers) {
180
184
  context.headers = {};
181
185
  }
182
186
 
183
- appendCmcdHeaders(context.headers, data);
187
+ appendCmcdHeaders(context.headers, data, options);
184
188
  } else {
185
- context.url = appendCmcdQuery(context.url, data);
189
+ context.url = appendCmcdQuery(context.url, data, options);
186
190
  }
187
191
  }
188
192
 
@@ -196,7 +200,7 @@ export default class CMCDController implements ComponentAPI {
196
200
  su: !this.initialized,
197
201
  });
198
202
  } catch (error) {
199
- logger.warn('Could not generate manifest CMCD data.', error);
203
+ this.hls.logger.warn('Could not generate manifest CMCD data.', error);
200
204
  }
201
205
  };
202
206
 
@@ -205,11 +209,11 @@ export default class CMCDController implements ComponentAPI {
205
209
  */
206
210
  private applyFragmentData = (context: FragmentLoaderContext) => {
207
211
  try {
208
- const fragment = context.frag;
209
- const level = this.hls.levels[fragment.level];
210
- const ot = this.getObjectType(fragment);
212
+ const { frag, part } = context;
213
+ const level = this.hls.levels[frag.level];
214
+ const ot = this.getObjectType(frag);
211
215
  const data: Cmcd = {
212
- d: fragment.duration * 1000,
216
+ d: (part || frag).duration * 1000,
213
217
  ot,
214
218
  };
215
219
 
@@ -223,12 +227,45 @@ export default class CMCDController implements ComponentAPI {
223
227
  data.bl = this.getBufferLength(ot);
224
228
  }
225
229
 
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
+
226
236
  this.apply(context, data);
227
237
  } catch (error) {
228
- logger.warn('Could not generate segment CMCD data.', error);
238
+ this.hls.logger.warn('Could not generate segment CMCD data.', error);
229
239
  }
230
240
  };
231
241
 
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
+
232
269
  /**
233
270
  * The CMCD object type.
234
271
  */
@@ -287,7 +324,7 @@ export default class CMCDController implements ComponentAPI {
287
324
  * Get the buffer length for a media type in milliseconds
288
325
  */
289
326
  private getBufferLength(type: CmcdObjectType) {
290
- const media = this.hls.media;
327
+ const media = this.media;
291
328
  const buffer =
292
329
  type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
293
330
 
@@ -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,13 +48,15 @@ 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 = '.';
57
- private pathwayPriority: string[] | null = null;
59
+ private _pathwayPriority: string[] | null = null;
58
60
  private timeToLoad: number = 300;
59
61
  private reloadTimer: number = -1;
60
62
  private updated: number = 0;
@@ -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
 
@@ -90,6 +92,23 @@ export default class ContentSteeringController implements NetworkComponentAPI {
90
92
  hls.off(Events.ERROR, this.onError, this);
91
93
  }
92
94
 
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
+
93
112
  startLoad() {
94
113
  this.started = true;
95
114
  this.clearTimeout();
@@ -176,7 +195,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
176
195
  errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost
177
196
  ) {
178
197
  const levels = this.levels;
179
- let pathwayPriority = this.pathwayPriority;
198
+ let pathwayPriority = this._pathwayPriority;
180
199
  let errorPathway = this.pathwayId;
181
200
  if (data.context) {
182
201
  const { groupId, pathwayId, type } = data.context;
@@ -191,19 +210,14 @@ export default class ContentSteeringController implements NetworkComponentAPI {
191
210
  }
192
211
  if (!pathwayPriority && levels) {
193
212
  // If PATHWAY-PRIORITY was not provided, list pathways for error handling
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[]);
213
+ pathwayPriority = this.pathways();
200
214
  }
201
215
  if (pathwayPriority && pathwayPriority.length > 1) {
202
216
  this.updatePathwayPriority(pathwayPriority);
203
217
  errorAction.resolved = this.pathwayId !== errorPathway;
204
218
  }
205
219
  if (!errorAction.resolved) {
206
- logger.warn(
220
+ this.warn(
207
221
  `Could not resolve ${data.details} ("${
208
222
  data.error.message
209
223
  }") with content-steering for Pathway: ${errorPathway} levels: ${
@@ -245,7 +259,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
245
259
  }
246
260
 
247
261
  private updatePathwayPriority(pathwayPriority: string[]) {
248
- this.pathwayPriority = pathwayPriority;
262
+ this._pathwayPriority = pathwayPriority;
249
263
  let levels: Level[] | undefined;
250
264
 
251
265
  // Evaluate if we should remove the pathway from the penalized list
@@ -442,7 +456,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
442
456
  ) => {
443
457
  this.log(`Loaded steering manifest: "${url}"`);
444
458
  const steeringData = response.data as SteeringManifest;
445
- if (steeringData.VERSION !== 1) {
459
+ if (steeringData?.VERSION !== 1) {
446
460
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
447
461
  return;
448
462
  }
@@ -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/keysystem-util';
22
+ import { strToUtf8array } from '../utils/utf8-utils';
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,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,12 +8,12 @@ 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';
15
15
  import type { ErrorData } from '../types/events';
16
- import type { Fragment } from '../loader/fragment';
16
+ import type { Fragment, MediaFragment } from '../loader/fragment';
17
17
  import type { LevelDetails } from '../loader/level-details';
18
18
 
19
19
  export const enum NetworkErrorAction {
@@ -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
 
@@ -129,10 +127,7 @@ export default class ErrorController implements NetworkComponentAPI {
129
127
  case ErrorDetails.FRAG_PARSING_ERROR:
130
128
  // ignore empty segment errors marked as gap
131
129
  if (data.frag?.gap) {
132
- data.errorAction = {
133
- action: NetworkErrorAction.DoNothing,
134
- flags: ErrorActionFlags.None,
135
- };
130
+ data.errorAction = createDoNothingErrorAction();
136
131
  return;
137
132
  }
138
133
  // falls through
@@ -220,10 +215,13 @@ export default class ErrorController implements NetworkComponentAPI {
220
215
  case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
221
216
  case ErrorDetails.REMUX_ALLOC_ERROR:
222
217
  case ErrorDetails.BUFFER_APPEND_ERROR:
223
- data.errorAction = this.getLevelSwitchAction(
224
- data,
225
- data.level ?? hls.loadLevel,
226
- );
218
+ // Buffer-controller can set errorAction when append errors can be ignored or resolved locally
219
+ if (!data.errorAction) {
220
+ data.errorAction = this.getLevelSwitchAction(
221
+ data,
222
+ data.level ?? hls.loadLevel,
223
+ );
224
+ }
227
225
  return;
228
226
  case ErrorDetails.INTERNAL_EXCEPTION:
229
227
  case ErrorDetails.BUFFER_APPENDING_ERROR:
@@ -232,10 +230,7 @@ export default class ErrorController implements NetworkComponentAPI {
232
230
  case ErrorDetails.BUFFER_STALLED_ERROR:
233
231
  case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
234
232
  case ErrorDetails.BUFFER_NUDGE_ON_STALL:
235
- data.errorAction = {
236
- action: NetworkErrorAction.DoNothing,
237
- flags: ErrorActionFlags.None,
238
- };
233
+ data.errorAction = createDoNothingErrorAction();
239
234
  return;
240
235
  }
241
236
 
@@ -389,7 +384,7 @@ export default class ErrorController implements NetworkComponentAPI {
389
384
  const levelDetails = levels[candidate].details;
390
385
  if (levelDetails) {
391
386
  const fragCandidate = findFragmentByPTS(
392
- data.frag,
387
+ data.frag as MediaFragment,
393
388
  levelDetails.fragments,
394
389
  data.frag.start,
395
390
  );
@@ -513,3 +508,14 @@ export default class ErrorController implements NetworkComponentAPI {
513
508
  }
514
509
  }
515
510
  }
511
+
512
+ export function createDoNothingErrorAction(resolved?: boolean): IErrorAction {
513
+ const errorAction: IErrorAction = {
514
+ action: NetworkErrorAction.DoNothing,
515
+ flags: ErrorActionFlags.None,
516
+ };
517
+ if (resolved) {
518
+ errorAction.resolved = true;
519
+ }
520
+ return errorAction;
521
+ }
@@ -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';
@@ -28,10 +27,12 @@ class FPSController implements ComponentAPI {
28
27
 
29
28
  protected registerListeners() {
30
29
  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);
35
36
  }
36
37
 
37
38
  destroy() {
@@ -65,6 +66,10 @@ class FPSController implements ComponentAPI {
65
66
  }
66
67
  }
67
68
 
69
+ private onMediaDetaching() {
70
+ this.media = null;
71
+ }
72
+
68
73
  checkFPS(
69
74
  video: HTMLVideoElement,
70
75
  decodedFrames: number,
@@ -84,13 +89,13 @@ class FPSController implements ComponentAPI {
84
89
  totalDroppedFrames: droppedFrames,
85
90
  });
86
91
  if (droppedFPS > 0) {
87
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
92
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
88
93
  if (
89
94
  currentDropped >
90
95
  hls.config.fpsDroppedMonitoringThreshold * currentDecoded
91
96
  ) {
92
97
  let currentLevel = hls.currentLevel;
93
- logger.warn(
98
+ hls.logger.warn(
94
99
  'drop FPS ratio greater than max allowed value for currentLevel: ' +
95
100
  currentLevel,
96
101
  );
@@ -1,5 +1,6 @@
1
1
  import BinarySearch from '../utils/binary-search';
2
- import { Fragment } from '../loader/fragment';
2
+ import type { Fragment, MediaFragment } from '../loader/fragment';
3
+ import type { LevelDetails } from '../loader/level-details';
3
4
 
4
5
  /**
5
6
  * Returns first fragment whose endPdt value exceeds the given PDT, or null.
@@ -8,10 +9,10 @@ import { Fragment } from '../loader/fragment';
8
9
  * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
9
10
  */
10
11
  export function findFragmentByPDT(
11
- fragments: Array<Fragment>,
12
+ fragments: MediaFragment[],
12
13
  PDTValue: number | null,
13
14
  maxFragLookUpTolerance: number,
14
- ): Fragment | null {
15
+ ): MediaFragment | null {
15
16
  if (
16
17
  PDTValue === null ||
17
18
  !Array.isArray(fragments) ||
@@ -54,23 +55,27 @@ export function findFragmentByPDT(
54
55
  * @returns a matching fragment or null
55
56
  */
56
57
  export function findFragmentByPTS(
57
- fragPrevious: Fragment | null,
58
- fragments: Array<Fragment>,
58
+ fragPrevious: MediaFragment | null,
59
+ fragments: MediaFragment[],
59
60
  bufferEnd: number = 0,
60
61
  maxFragLookUpTolerance: number = 0,
61
62
  nextFragLookupTolerance: number = 0.005,
62
- ): Fragment | null {
63
- let fragNext: Fragment | null = null;
63
+ ): MediaFragment | null {
64
+ let fragNext: MediaFragment | null = null;
64
65
  if (fragPrevious) {
65
- fragNext =
66
- fragments[
67
- (fragPrevious.sn as number) - (fragments[0].sn as number) + 1
68
- ] || null;
66
+ fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
69
67
  // check for buffer-end rounding error
70
- const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
68
+ const bufferEdgeError = (fragPrevious.endDTS as number) - bufferEnd;
71
69
  if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
72
70
  bufferEnd += 0.0000015;
73
71
  }
72
+ if (
73
+ fragNext &&
74
+ fragPrevious.level !== fragNext.level &&
75
+ fragNext.end <= fragPrevious.end
76
+ ) {
77
+ fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
78
+ }
74
79
  } else if (bufferEnd === 0 && fragments[0].start === 0) {
75
80
  fragNext = fragments[0];
76
81
  }
@@ -135,7 +140,7 @@ function fragmentWithinFastStartSwitch(
135
140
  export function fragmentWithinToleranceTest(
136
141
  bufferEnd = 0,
137
142
  maxFragLookUpTolerance = 0,
138
- candidate: Fragment,
143
+ candidate: MediaFragment,
139
144
  ) {
140
145
  // eagerly accept an accurate match (no tolerance)
141
146
  if (
@@ -189,7 +194,7 @@ export function fragmentWithinToleranceTest(
189
194
  export function pdtWithinToleranceTest(
190
195
  pdtBufferEnd: number,
191
196
  maxFragLookUpTolerance: number,
192
- candidate: Fragment,
197
+ candidate: MediaFragment,
193
198
  ): boolean {
194
199
  const candidateLookupTolerance =
195
200
  Math.min(
@@ -203,9 +208,9 @@ export function pdtWithinToleranceTest(
203
208
  }
204
209
 
205
210
  export function findFragWithCC(
206
- fragments: Fragment[],
211
+ fragments: MediaFragment[],
207
212
  cc: number,
208
- ): Fragment | null {
213
+ ): MediaFragment | null {
209
214
  return BinarySearch.search(fragments, (candidate) => {
210
215
  if (candidate.cc < cc) {
211
216
  return 1;
@@ -216,3 +221,26 @@ export function findFragWithCC(
216
221
  }
217
222
  });
218
223
  }
224
+
225
+ export function findNearestWithCC(
226
+ details: LevelDetails | undefined,
227
+ cc: number,
228
+ fragment: MediaFragment,
229
+ ): MediaFragment | null {
230
+ if (details) {
231
+ if (details.startCC <= cc && details.endCC >= cc) {
232
+ const start = fragment.start;
233
+ const end = fragment.end;
234
+ return BinarySearch.search(details.fragments, (candidate) => {
235
+ if (candidate.cc < cc || candidate.end <= start) {
236
+ return 1;
237
+ } else if (candidate.cc > cc || candidate.start >= end) {
238
+ return -1;
239
+ } else {
240
+ return 0;
241
+ }
242
+ });
243
+ }
244
+ }
245
+ return null;
246
+ }