hls.js 1.5.17 → 1.5.18

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.
package/package.json CHANGED
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.22.4"
132
132
  },
133
- "version": "1.5.17"
133
+ "version": "1.5.18"
134
134
  }
@@ -281,12 +281,14 @@ class AudioStreamController
281
281
  const { hls, levels, media, trackId } = this;
282
282
  const config = hls.config;
283
283
 
284
- // 1. if video not attached AND
284
+ // 1. if buffering is suspended
285
+ // 2. if video not attached AND
285
286
  // start fragment already requested OR start frag prefetch not enabled
286
- // 2. if tracks or track not loaded and selected
287
+ // 3. if tracks or track not loaded and selected
287
288
  // then exit loop
288
289
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
289
290
  if (
291
+ !this.buffering ||
290
292
  (!media && (this.startFragRequested || !config.startFragPrefetch)) ||
291
293
  !levels?.[trackId]
292
294
  ) {
@@ -97,6 +97,7 @@ export default class BaseStreamController
97
97
  protected startFragRequested: boolean = false;
98
98
  protected decrypter: Decrypter;
99
99
  protected initPTS: RationalTimestamp[] = [];
100
+ protected buffering: boolean = true;
100
101
  protected onvseeking: EventListener | null = null;
101
102
  protected onvended: EventListener | null = null;
102
103
 
@@ -150,6 +151,14 @@ export default class BaseStreamController
150
151
  this.state = State.STOPPED;
151
152
  }
152
153
 
154
+ public pauseBuffering() {
155
+ this.buffering = false;
156
+ }
157
+
158
+ public resumeBuffering() {
159
+ this.buffering = true;
160
+ }
161
+
153
162
  protected _streamEnded(
154
163
  bufferInfo: BufferInfo,
155
164
  levelDetails: LevelDetails,
@@ -309,6 +309,7 @@ export default class BufferController implements ComponentAPI {
309
309
  this.resetBuffer(type);
310
310
  });
311
311
  this._initSourceBuffer();
312
+ this.hls.resumeBuffering();
312
313
  }
313
314
 
314
315
  private resetBuffer(type: SourceBufferName) {
@@ -237,7 +237,7 @@ export default class StreamController
237
237
  return;
238
238
  }
239
239
 
240
- const level = hls.nextLoadLevel;
240
+ const level = this.buffering ? hls.nextLoadLevel : hls.loadLevel;
241
241
  if (!levels?.[level]) {
242
242
  return;
243
243
  }
@@ -262,6 +262,9 @@ export default class StreamController
262
262
  this.state = State.ENDED;
263
263
  return;
264
264
  }
265
+ if (!this.buffering) {
266
+ return;
267
+ }
265
268
 
266
269
  // set next load level : this will trigger a playlist load if needed
267
270
  if (hls.loadLevel !== level && hls.manualLevel === -1) {
package/src/hls.ts CHANGED
@@ -430,9 +430,13 @@ export default class Hls implements HlsEventEmitter {
430
430
  startLoad(startPosition: number = -1) {
431
431
  logger.log(`startLoad(${startPosition})`);
432
432
  this.started = true;
433
- this.networkControllers.forEach((controller) => {
434
- controller.startLoad(startPosition);
435
- });
433
+ this.resumeBuffering();
434
+ for (let i = 0; i < this.networkControllers.length; i++) {
435
+ this.networkControllers[i].startLoad(startPosition);
436
+ if (!this.started || !this.networkControllers) {
437
+ break;
438
+ }
439
+ }
436
440
  }
437
441
 
438
442
  /**
@@ -441,32 +445,35 @@ export default class Hls implements HlsEventEmitter {
441
445
  stopLoad() {
442
446
  logger.log('stopLoad');
443
447
  this.started = false;
444
- this.networkControllers.forEach((controller) => {
445
- controller.stopLoad();
446
- });
448
+ for (let i = 0; i < this.networkControllers.length; i++) {
449
+ this.networkControllers[i].stopLoad();
450
+ if (this.started || !this.networkControllers) {
451
+ break;
452
+ }
453
+ }
447
454
  }
448
455
 
449
456
  /**
450
- * Resumes stream controller segment loading if previously started.
457
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
451
458
  */
452
459
  resumeBuffering() {
453
- if (this.started) {
454
- this.networkControllers.forEach((controller) => {
455
- if ('fragmentLoader' in controller) {
456
- controller.startLoad(-1);
457
- }
458
- });
459
- }
460
+ logger.log(`resume buffering`);
461
+ this.networkControllers.forEach((controller) => {
462
+ if (controller.resumeBuffering) {
463
+ controller.resumeBuffering();
464
+ }
465
+ });
460
466
  }
461
467
 
462
468
  /**
463
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
469
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
464
470
  * This allows for media buffering to be paused without interupting playlist loading.
465
471
  */
466
472
  pauseBuffering() {
473
+ logger.log(`pause buffering`);
467
474
  this.networkControllers.forEach((controller) => {
468
- if ('fragmentLoader' in controller) {
469
- controller.stopLoad();
475
+ if (controller.pauseBuffering) {
476
+ controller.pauseBuffering();
470
477
  }
471
478
  });
472
479
  }
@@ -100,20 +100,24 @@ export default class MP4Remuxer implements Remuxer {
100
100
  this.videoTrackConfig = undefined;
101
101
  }
102
102
 
103
- getVideoStartPts(videoSamples) {
103
+ getVideoStartPts(videoSamples: VideoSample[]) {
104
+ // Get the minimum PTS value relative to the first sample's PTS, normalized for 33-bit wrapping
104
105
  let rolloverDetected = false;
106
+ const firstPts = videoSamples[0].pts;
105
107
  const startPTS = videoSamples.reduce((minPTS, sample) => {
106
- const delta = sample.pts - minPTS;
108
+ let pts = sample.pts;
109
+ let delta = pts - minPTS;
107
110
  if (delta < -4294967296) {
108
111
  // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation
109
112
  rolloverDetected = true;
110
- return normalizePts(minPTS, sample.pts);
111
- } else if (delta > 0) {
113
+ pts = normalizePts(pts, firstPts);
114
+ delta = pts - minPTS;
115
+ }
116
+ if (delta > 0) {
112
117
  return minPTS;
113
- } else {
114
- return sample.pts;
115
118
  }
116
- }, videoSamples[0].pts);
119
+ return pts;
120
+ }, firstPts);
117
121
  if (rolloverDetected) {
118
122
  logger.debug('PTS rollover detected');
119
123
  }
@@ -15,4 +15,6 @@ export interface AbrComponentAPI extends ComponentAPI {
15
15
  export interface NetworkComponentAPI extends ComponentAPI {
16
16
  startLoad(startPosition: number): void;
17
17
  stopLoad(): void;
18
+ pauseBuffering?(): void;
19
+ resumeBuffering?(): void;
18
20
  }
@@ -203,59 +203,62 @@ class XhrLoader implements Loader<LoaderContext> {
203
203
  xhr.onprogress = null;
204
204
  const status = xhr.status;
205
205
  // http status between 200 to 299 are all successful
206
- const useResponse = xhr.responseType !== 'text';
207
- if (
208
- status >= 200 &&
209
- status < 300 &&
210
- ((useResponse && xhr.response) || xhr.responseText !== null)
211
- ) {
212
- stats.loading.end = Math.max(
213
- self.performance.now(),
214
- stats.loading.first,
215
- );
216
- const data = useResponse ? xhr.response : xhr.responseText;
217
- const len =
218
- xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
219
- stats.loaded = stats.total = len;
220
- stats.bwEstimate =
221
- (stats.total * 8000) / (stats.loading.end - stats.loading.first);
222
- if (!this.callbacks) {
223
- return;
224
- }
225
- const onProgress = this.callbacks.onProgress;
226
- if (onProgress) {
227
- onProgress(stats, context, data, xhr);
228
- }
229
- if (!this.callbacks) {
206
+ const useResponseText =
207
+ xhr.responseType === 'text' ? xhr.responseText : null;
208
+ if (status >= 200 && status < 300) {
209
+ const data = useResponseText ?? xhr.response;
210
+ if (data != null) {
211
+ stats.loading.end = Math.max(
212
+ self.performance.now(),
213
+ stats.loading.first,
214
+ );
215
+ const len =
216
+ xhr.responseType === 'arraybuffer'
217
+ ? data.byteLength
218
+ : data.length;
219
+ stats.loaded = stats.total = len;
220
+ stats.bwEstimate =
221
+ (stats.total * 8000) / (stats.loading.end - stats.loading.first);
222
+ if (!this.callbacks) {
223
+ return;
224
+ }
225
+ const onProgress = this.callbacks.onProgress;
226
+ if (onProgress) {
227
+ onProgress(stats, context, data, xhr);
228
+ }
229
+ if (!this.callbacks) {
230
+ return;
231
+ }
232
+ const response: LoaderResponse = {
233
+ url: xhr.responseURL,
234
+ data: data,
235
+ code: status,
236
+ };
237
+
238
+ this.callbacks.onSuccess(response, stats, context, xhr);
230
239
  return;
231
240
  }
232
- const response: LoaderResponse = {
233
- url: xhr.responseURL,
234
- data: data,
235
- code: status,
236
- };
241
+ }
237
242
 
238
- this.callbacks.onSuccess(response, stats, context, xhr);
243
+ // Handle bad status or nullish response
244
+ const retryConfig = config.loadPolicy.errorRetry;
245
+ const retryCount = stats.retry;
246
+ // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
247
+ const response: LoaderResponse = {
248
+ url: context.url,
249
+ data: undefined,
250
+ code: status,
251
+ };
252
+ if (shouldRetry(retryConfig, retryCount, false, response)) {
253
+ this.retry(retryConfig);
239
254
  } else {
240
- const retryConfig = config.loadPolicy.errorRetry;
241
- const retryCount = stats.retry;
242
- // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
243
- const response: LoaderResponse = {
244
- url: context.url,
245
- data: undefined,
246
- code: status,
247
- };
248
- if (shouldRetry(retryConfig, retryCount, false, response)) {
249
- this.retry(retryConfig);
250
- } else {
251
- logger.error(`${status} while loading ${context.url}`);
252
- this.callbacks!.onError(
253
- { code: status, text: xhr.statusText },
254
- context,
255
- xhr,
256
- stats,
257
- );
258
- }
255
+ logger.error(`${status} while loading ${context.url}`);
256
+ this.callbacks!.onError(
257
+ { code: status, text: xhr.statusText },
258
+ context,
259
+ xhr,
260
+ stats,
261
+ );
259
262
  }
260
263
  }
261
264
  }