hls.js 1.6.3-0.canary.11251 → 1.6.3-0.canary.11252

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.
@@ -15,6 +15,7 @@ import {
15
15
  ALIGNED_END_THRESHOLD_SECONDS,
16
16
  eventAssetToString,
17
17
  generateAssetIdentifier,
18
+ getNextAssetIndex,
18
19
  type InterstitialAssetId,
19
20
  type InterstitialAssetItem,
20
21
  type InterstitialEvent,
@@ -1008,11 +1009,8 @@ export default class InterstitialsController
1008
1009
  index: number,
1009
1010
  assetListIndex: number,
1010
1011
  ) {
1011
- const nextAssetIndex = assetListIndex + 1;
1012
- if (
1013
- !interstitial.isAssetPastPlayoutLimit(nextAssetIndex) &&
1014
- !interstitial.assetList[nextAssetIndex].error
1015
- ) {
1012
+ const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex);
1013
+ if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) {
1016
1014
  // Advance to next asset list item
1017
1015
  this.setSchedulePosition(index, nextAssetIndex);
1018
1016
  } else {
@@ -1046,7 +1044,7 @@ export default class InterstitialsController
1046
1044
  if (interstitial) {
1047
1045
  const itemIndex = schedule.findEventIndex(parentIdentifier);
1048
1046
  const assetListIndex = schedule.findAssetIndex(interstitial, time);
1049
- this.setSchedulePosition(itemIndex, assetListIndex);
1047
+ this.advanceAfterAssetEnded(interstitial, itemIndex, assetListIndex - 1);
1050
1048
  }
1051
1049
  }
1052
1050
 
@@ -1072,15 +1070,15 @@ export default class InterstitialsController
1072
1070
  (assetListIndex !== undefined &&
1073
1071
  assetId !== interstitial.assetList?.[assetListIndex].identifier))
1074
1072
  ) {
1075
- const assetListIndex = interstitial.findAssetIndex(playingAsset);
1073
+ const playingAssetListIndex = interstitial.findAssetIndex(playingAsset);
1076
1074
  this.log(
1077
- `INTERSTITIAL_ASSET_ENDED ${assetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`,
1075
+ `INTERSTITIAL_ASSET_ENDED ${playingAssetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`,
1078
1076
  );
1079
1077
  this.endedAsset = playingAsset;
1080
1078
  this.playingAsset = null;
1081
1079
  this.hls.trigger(Events.INTERSTITIAL_ASSET_ENDED, {
1082
1080
  asset: playingAsset,
1083
- assetListIndex,
1081
+ assetListIndex: playingAssetListIndex,
1084
1082
  event: interstitial,
1085
1083
  schedule: scheduleItems.slice(0),
1086
1084
  scheduleIndex: index,
@@ -1166,6 +1164,15 @@ export default class InterstitialsController
1166
1164
  interstitial,
1167
1165
  this.timelinePos,
1168
1166
  );
1167
+ const assetIndexCandidate = getNextAssetIndex(
1168
+ interstitial,
1169
+ assetListIndex - 1,
1170
+ );
1171
+ if (interstitial.isAssetPastPlayoutLimit(assetIndexCandidate)) {
1172
+ this.advanceAfterAssetEnded(interstitial, index, assetListIndex);
1173
+ return;
1174
+ }
1175
+ assetListIndex = assetIndexCandidate;
1169
1176
  }
1170
1177
  // Ensure Interstitial is enqueued
1171
1178
  const waitingItem = this.waitingItem;
@@ -1315,7 +1322,7 @@ export default class InterstitialsController
1315
1322
  if (!scheduleItems) {
1316
1323
  return;
1317
1324
  }
1318
- this.log(`resumed ${segmentToString(scheduledItem)}`);
1325
+ this.log(`INTERSTITIALS_PRIMARY_RESUMED ${segmentToString(scheduledItem)}`);
1319
1326
  this.hls.trigger(Events.INTERSTITIALS_PRIMARY_RESUMED, {
1320
1327
  schedule: scheduleItems.slice(0),
1321
1328
  scheduleIndex: index,
@@ -1577,7 +1584,7 @@ export default class InterstitialsController
1577
1584
  const interstitialsUpdated = !!(
1578
1585
  interstitialEvents.length || removedIds.length
1579
1586
  );
1580
- if (interstitialsUpdated) {
1587
+ if (interstitialsUpdated || previousItems) {
1581
1588
  this.log(
1582
1589
  `INTERSTITIALS_UPDATED (${
1583
1590
  interstitialEvents.length
@@ -1874,16 +1881,16 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
1874
1881
  item.start,
1875
1882
  Math.min(item.end, this.timelinePos),
1876
1883
  );
1884
+ const timeRemaining = bufferingPlayer
1885
+ ? bufferingPlayer.remaining
1886
+ : bufferingLast
1887
+ ? bufferingLast.end - this.timelinePos
1888
+ : 0;
1889
+ this.log(
1890
+ `INTERSTITIALS_BUFFERED_TO_BOUNDARY ${segmentToString(item)}` +
1891
+ (bufferingLast ? ` (${timeRemaining.toFixed(2)} remaining)` : ''),
1892
+ );
1877
1893
  if (!this.playbackDisabled) {
1878
- const timeRemaining = bufferingPlayer
1879
- ? bufferingPlayer.remaining
1880
- : bufferingLast
1881
- ? bufferingLast.end - this.timelinePos
1882
- : 0;
1883
- this.log(
1884
- `buffered to boundary ${segmentToString(item)}` +
1885
- (bufferingLast ? ` (${timeRemaining.toFixed(2)} remaining)` : ''),
1886
- );
1887
1894
  if (isInterstitial) {
1888
1895
  // primary fragment loading will exit early in base-stream-controller while `bufferingItem` is set to an Interstitial block
1889
1896
  item.event.assetList.forEach((asset) => {
@@ -2114,7 +2121,6 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2114
2121
  assetItem: InterstitialAssetItem,
2115
2122
  assetListIndex: number,
2116
2123
  ): HlsAssetPlayer {
2117
- this.log(`create HLSAssetPlayer for ${eventAssetToString(assetItem)}`);
2118
2124
  const primary = this.hls;
2119
2125
  const userConfig = primary.userConfig;
2120
2126
  let videoPreference = userConfig.videoPreference;
@@ -2250,15 +2256,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2250
2256
  const scheduleIndex = this.schedule.findEventIndex(
2251
2257
  interstitial.identifier,
2252
2258
  );
2253
- const assetListIndex = interstitial.findAssetIndex(assetItem);
2254
- const nextAssetIndex = assetListIndex + 1;
2255
2259
  const item = this.schedule.items?.[scheduleIndex];
2256
2260
  if (this.isInterstitial(item)) {
2257
- if (
2258
- assetListIndex !== -1 &&
2259
- !interstitial.isAssetPastPlayoutLimit(nextAssetIndex) &&
2260
- !interstitial.assetList[nextAssetIndex].error
2261
- ) {
2261
+ const assetListIndex = interstitial.findAssetIndex(assetItem);
2262
+ const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex);
2263
+ if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) {
2262
2264
  this.bufferedToItem(item, nextAssetIndex);
2263
2265
  } else {
2264
2266
  const nextItem = this.schedule.items?.[scheduleIndex + 1];
@@ -2337,7 +2339,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2337
2339
  error.message,
2338
2340
  );
2339
2341
  });
2340
-
2342
+ this.log(
2343
+ `INTERSTITIAL_ASSET_PLAYER_CREATED ${eventAssetToString(assetItem)}`,
2344
+ );
2341
2345
  this.hls.trigger(Events.INTERSTITIAL_ASSET_PLAYER_CREATED, {
2342
2346
  asset: assetItem,
2343
2347
  assetListIndex,
@@ -2358,6 +2362,17 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2358
2362
  interstitial.reset();
2359
2363
  }
2360
2364
 
2365
+ private resetAssetPlayer(assetId: InterstitialAssetId) {
2366
+ // Reset asset player so that it's timeline can be adjusted without reloading the MVP
2367
+ const playerIndex = this.getAssetPlayerQueueIndex(assetId);
2368
+ if (playerIndex !== -1) {
2369
+ this.log(`reset asset player "${assetId}" after error`);
2370
+ const player = this.playerQueue[playerIndex];
2371
+ this.transferMediaFromPlayer(player, null);
2372
+ player.resetDetails();
2373
+ }
2374
+ }
2375
+
2361
2376
  private clearAssetPlayer(
2362
2377
  assetId: InterstitialAssetId,
2363
2378
  toSegment: InterstitialScheduleItem | null,
@@ -2365,7 +2380,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2365
2380
  const playerIndex = this.getAssetPlayerQueueIndex(assetId);
2366
2381
  if (playerIndex !== -1) {
2367
2382
  this.log(
2368
- `clearAssetPlayer "${assetId}" toSegment: ${toSegment ? segmentToString(toSegment) : toSegment}`,
2383
+ `clear asset player "${assetId}" toSegment: ${toSegment ? segmentToString(toSegment) : toSegment}`,
2369
2384
  );
2370
2385
  const player = this.playerQueue[playerIndex];
2371
2386
  this.transferMediaFromPlayer(player, toSegment);
@@ -2405,7 +2420,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2405
2420
  delete playingAsset.error;
2406
2421
  }
2407
2422
  this.log(
2408
- `INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${player}`,
2423
+ `INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${eventAssetToString(assetItem)}`,
2409
2424
  );
2410
2425
  this.hls.trigger(Events.INTERSTITIAL_ASSET_STARTED, {
2411
2426
  asset: assetItem,
@@ -2456,7 +2471,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2456
2471
  !isCompatibleTrackChange(activeTracks, player.tracks)
2457
2472
  ) {
2458
2473
  const error = new Error(
2459
- `Asset "${assetId}" SourceBuffer tracks ('${Object.keys(player.tracks)}') are not compatible with primary content tracks ('${Object.keys(activeTracks)}')`,
2474
+ `Asset ${eventAssetToString(assetItem)} SourceBuffer tracks ('${Object.keys(player.tracks)}') are not compatible with primary content tracks ('${Object.keys(activeTracks)}')`,
2460
2475
  );
2461
2476
  const errorData: ErrorData = {
2462
2477
  fatal: true,
@@ -2489,13 +2504,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2489
2504
  if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) {
2490
2505
  return;
2491
2506
  }
2492
-
2493
- const assetItem = interstitial.assetList[assetListIndex] || null;
2494
- let player: HlsAssetPlayer | null = null;
2495
- if (assetItem) {
2496
- const playerIndex = this.getAssetPlayerQueueIndex(assetItem.identifier);
2497
- player = this.playerQueue[playerIndex] || null;
2498
- }
2507
+ const assetItem = interstitial.assetList[assetListIndex];
2508
+ this.warn(
2509
+ `INTERSTITIAL_ASSET_ERROR ${assetItem ? eventAssetToString(assetItem) : assetItem} ${data.error}`,
2510
+ );
2511
+ const assetId = assetItem?.identifier;
2512
+ const playerIndex = this.getAssetPlayerQueueIndex(assetId);
2513
+ const player = this.playerQueue[playerIndex] || null;
2499
2514
  const items = this.schedule.items;
2500
2515
  const interstitialAssetError = Object.assign({}, data, {
2501
2516
  fatal: false,
@@ -2507,17 +2522,15 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2507
2522
  scheduleIndex,
2508
2523
  player,
2509
2524
  });
2510
- this.warn(`Asset item error: ${data.error}`);
2511
2525
  this.hls.trigger(Events.INTERSTITIAL_ASSET_ERROR, interstitialAssetError);
2512
2526
  if (!data.fatal) {
2513
2527
  return;
2514
2528
  }
2515
2529
 
2530
+ const playingAsset = this.playingAsset;
2516
2531
  const error = new Error(errorMessage);
2517
2532
  if (assetItem) {
2518
- if (this.playingAsset !== assetItem) {
2519
- this.clearAssetPlayer(assetItem.identifier, null);
2520
- }
2533
+ this.clearAssetPlayer(assetId, null);
2521
2534
  assetItem.error = error;
2522
2535
  }
2523
2536
 
@@ -2525,11 +2538,17 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2525
2538
  if (!interstitial.assetList.some((asset) => !asset.error)) {
2526
2539
  interstitial.error = error;
2527
2540
  } else if (interstitial.appendInPlace) {
2528
- // Skip entire interstitial since moving up subsequent assets is error prone
2529
- interstitial.error = error;
2541
+ // Reset level details and reload/parse media playlists to align with updated schedule
2542
+ for (let i = assetListIndex; i < interstitial.assetList.length; i++) {
2543
+ this.resetAssetPlayer(interstitial.assetList[i].identifier);
2544
+ }
2545
+ this.updateSchedule();
2546
+ }
2547
+ if (interstitial.error) {
2548
+ this.primaryFallback(interstitial);
2549
+ } else if (playingAsset && playingAsset.identifier === assetId) {
2550
+ this.advanceAfterAssetEnded(interstitial, scheduleIndex, assetListIndex);
2530
2551
  }
2531
-
2532
- this.primaryFallback(interstitial);
2533
2552
  }
2534
2553
 
2535
2554
  private primaryFallback(interstitial: InterstitialEvent) {
@@ -2551,16 +2570,15 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
2551
2570
  timelinePos = this.hls.startPosition;
2552
2571
  }
2553
2572
  const newPlayingItem = this.updateItem(playingItem, timelinePos);
2554
- if (!this.itemsMatch(playingItem, newPlayingItem)) {
2555
- const scheduleIndex = this.schedule.findItemIndexAtTime(timelinePos);
2556
- this.setSchedulePosition(scheduleIndex);
2557
- } else {
2573
+ if (this.itemsMatch(playingItem, newPlayingItem)) {
2558
2574
  this.clearInterstitial(interstitial, null);
2559
2575
  }
2560
2576
  if (interstitial.appendInPlace) {
2561
2577
  this.attachPrimary(flushStart, null);
2562
2578
  this.flushFrontBuffer(flushStart);
2563
2579
  }
2580
+ const scheduleIndex = this.schedule.findItemIndexAtTime(timelinePos);
2581
+ this.setSchedulePosition(scheduleIndex);
2564
2582
  } else {
2565
2583
  this.checkStart();
2566
2584
  }
@@ -118,14 +118,17 @@ export class InterstitialEvent {
118
118
  }
119
119
 
120
120
  public isAssetPastPlayoutLimit(assetIndex: number): boolean {
121
- if (assetIndex >= this.assetList.length) {
121
+ if (assetIndex > 0 && assetIndex >= this.assetList.length) {
122
122
  return true;
123
123
  }
124
124
  const playoutLimit = this.playoutLimit;
125
125
  if (assetIndex <= 0 || isNaN(playoutLimit)) {
126
126
  return false;
127
127
  }
128
- const assetOffset = this.assetList[assetIndex].startOffset;
128
+ if (playoutLimit === 0) {
129
+ return true;
130
+ }
131
+ const assetOffset = this.assetList[assetIndex]?.startOffset || 0;
129
132
  return assetOffset > playoutLimit;
130
133
  }
131
134
 
@@ -313,6 +316,16 @@ export function getInterstitialUrl(
313
316
  return url;
314
317
  }
315
318
 
319
+ export function getNextAssetIndex(
320
+ interstitial: InterstitialEvent,
321
+ assetListIndex: number,
322
+ ): number {
323
+ while (interstitial.assetList[++assetListIndex]?.error) {
324
+ /* no-op */
325
+ }
326
+ return assetListIndex;
327
+ }
328
+
316
329
  function eventToString(interstitial: InterstitialEvent): string {
317
330
  return `["${interstitial.identifier}" ${interstitial.cue.pre ? '<pre>' : interstitial.cue.post ? '<post>' : ''}${interstitial.timelineStart.toFixed(2)}-${interstitial.resumeTime.toFixed(2)}]`;
318
331
  }