hls.js 1.6.0-rc.2 → 1.6.0-rc.2.0.canary.11083

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
@@ -134,5 +134,5 @@
134
134
  "url-toolkit": "2.2.5",
135
135
  "wrangler": "3.114.2"
136
136
  },
137
- "version": "1.6.0-rc.2"
137
+ "version": "1.6.0-rc.2.0.canary.11083"
138
138
  }
@@ -46,6 +46,7 @@ class AbrController extends Logger implements AbrComponentAPI {
46
46
  private fragCurrent: Fragment | null = null;
47
47
  private partCurrent: Part | null = null;
48
48
  private bitrateTestDelay: number = 0;
49
+ private rebufferNotice: number = -1;
49
50
 
50
51
  public bwEstimator: EwmaBandWidthEstimator;
51
52
 
@@ -647,6 +648,7 @@ class AbrController extends Logger implements AbrComponentAPI {
647
648
  bwUpFactor,
648
649
  );
649
650
  if (bestLevel >= 0) {
651
+ this.rebufferNotice = -1;
650
652
  return bestLevel;
651
653
  }
652
654
  }
@@ -688,11 +690,14 @@ class AbrController extends Logger implements AbrComponentAPI {
688
690
  bwFactor,
689
691
  bwUpFactor,
690
692
  );
691
- this.info(
692
- `${
693
- bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
694
- }, optimal quality level ${bestLevel}`,
695
- );
693
+ if (this.rebufferNotice !== bestLevel) {
694
+ this.rebufferNotice = bestLevel;
695
+ this.info(
696
+ `${
697
+ bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
698
+ }, optimal quality level ${bestLevel}`,
699
+ );
700
+ }
696
701
  if (bestLevel > -1) {
697
702
  return bestLevel;
698
703
  }
@@ -1310,7 +1310,14 @@ export default class BaseStreamController
1310
1310
  : levelDetails.fragmentEnd;
1311
1311
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
1312
1312
  }
1313
- const programFrag = this.filterReplacedPrimary(frag, levelDetails);
1313
+ let programFrag = this.filterReplacedPrimary(frag, levelDetails);
1314
+ if (!programFrag && frag) {
1315
+ const curSNIdx = frag.sn - levelDetails.startSN;
1316
+ programFrag = this.filterReplacedPrimary(
1317
+ fragments[curSNIdx + 1] || null,
1318
+ levelDetails,
1319
+ );
1320
+ }
1314
1321
  return this.mapToInitFragWhenRequired(programFrag);
1315
1322
  }
1316
1323
 
@@ -1715,7 +1715,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1715
1715
  }
1716
1716
 
1717
1717
  // Schedule buffer control
1718
- private checkBuffer() {
1718
+ private checkBuffer(starved?: boolean) {
1719
1719
  const items = this.schedule.items;
1720
1720
  if (!items) {
1721
1721
  return;
@@ -1726,8 +1726,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1726
1726
  this.timelinePos,
1727
1727
  0,
1728
1728
  );
1729
-
1730
- this.updateBufferedPos(bufferInfo.end, items, bufferInfo.len === 0);
1729
+ if (starved) {
1730
+ this.bufferedPos = this.timelinePos;
1731
+ }
1732
+ starved ||= bufferInfo.len < 1;
1733
+ this.updateBufferedPos(bufferInfo.end, items, starved);
1731
1734
  }
1732
1735
 
1733
1736
  private updateBufferedPos(
@@ -1788,10 +1791,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1788
1791
  } else if (
1789
1792
  bufferIsEmpty &&
1790
1793
  playingItem &&
1791
- !this.itemsMatch(playingItem, bufferingItem) &&
1792
- bufferEndIndex === playingIndex
1794
+ !this.itemsMatch(playingItem, bufferingItem)
1793
1795
  ) {
1794
- this.bufferedToItem(playingItem);
1796
+ if (bufferEndIndex === playingIndex) {
1797
+ this.bufferedToItem(playingItem);
1798
+ } else if (bufferEndIndex === playingIndex + 1) {
1799
+ this.bufferedToItem(items[bufferEndIndex]);
1800
+ }
1795
1801
  }
1796
1802
  }
1797
1803
 
@@ -1930,7 +1936,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1930
1936
  if (neverLoaded) {
1931
1937
  const timelineStart = interstitial.timelineStart;
1932
1938
  if (interstitial.appendInPlace) {
1933
- this.flushFrontBuffer(timelineStart + 0.25);
1939
+ const playingItem = this.playingItem;
1940
+ if (
1941
+ !this.isInterstitial(playingItem) &&
1942
+ playingItem?.nextEvent?.identifier === interstitial.identifier
1943
+ ) {
1944
+ this.flushFrontBuffer(timelineStart + 0.25);
1945
+ }
1934
1946
  }
1935
1947
  let hlsStartOffset;
1936
1948
  let liveStartPosition = 0;
@@ -1998,6 +2010,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1998
2010
  if (!requiredTracks) {
1999
2011
  return;
2000
2012
  }
2013
+ this.log(`Removing front buffer starting at ${startOffset}`);
2001
2014
  const sourceBufferNames = Object.keys(requiredTracks);
2002
2015
  sourceBufferNames.forEach((type: SourceBufferName) => {
2003
2016
  this.hls.trigger(Events.BUFFER_FLUSHING, {
@@ -2087,12 +2100,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2087
2100
  }
2088
2101
  }
2089
2102
  }
2103
+ const assetId = assetItem.identifier;
2090
2104
  const playerConfig: Partial<HlsConfig> = {
2091
2105
  ...userConfig,
2092
2106
  autoStartLoad: true,
2093
2107
  startFragPrefetch: true,
2094
2108
  primarySessionId: primary.sessionId,
2095
- assetPlayerId: assetItem.identifier,
2109
+ assetPlayerId: assetId,
2096
2110
  abrEwmaDefaultEstimate: primary.bandwidthEstimate,
2097
2111
  interstitialsController: undefined,
2098
2112
  startPosition,
@@ -2114,6 +2128,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2114
2128
  contentId: hash(assetItem.uri),
2115
2129
  });
2116
2130
  }
2131
+ if (this.getAssetPlayer(assetId)) {
2132
+ this.warn(
2133
+ `Duplicate date range identifier ${interstitial} and asset ${assetId}`,
2134
+ );
2135
+ }
2117
2136
  const player = new HlsAssetPlayer(
2118
2137
  this.HlsPlayerClass,
2119
2138
  playerConfig,
@@ -2122,7 +2141,6 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2122
2141
  );
2123
2142
  this.playerQueue.push(player);
2124
2143
  interstitial.assetList[assetListIndex] = assetItem;
2125
- const assetId = assetItem.identifier;
2126
2144
  // Listen for LevelDetails and PTS change to update duration
2127
2145
  const updateAssetPlayerDetails = (details: LevelDetails) => {
2128
2146
  if (details.live) {
@@ -2181,14 +2199,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2181
2199
  }
2182
2200
  };
2183
2201
  player.on(Events.BUFFER_CODECS, onBufferCodecs);
2184
- const bufferedToEnd = (name: Events.BUFFERED_TO_END) => {
2202
+ const bufferedToEnd = () => {
2185
2203
  const inQueuPlayer = this.getAssetPlayer(assetId);
2186
2204
  this.log(`buffered to end of asset ${inQueuPlayer}`);
2187
2205
  if (!inQueuPlayer) {
2188
2206
  return;
2189
2207
  }
2190
- inQueuPlayer.off(Events.BUFFERED_TO_END, bufferedToEnd);
2191
-
2192
2208
  // Preload at end of asset
2193
2209
  const scheduleIndex = this.schedule.findEventIndex(
2194
2210
  interstitial.identifier,
@@ -2202,7 +2218,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2202
2218
  !interstitial.isAssetPastPlayoutLimit(nextAssetIndex) &&
2203
2219
  !interstitial.assetList[nextAssetIndex].error
2204
2220
  ) {
2205
- this.bufferedToItem(item, assetListIndex + 1);
2221
+ this.bufferedToItem(item, nextAssetIndex);
2206
2222
  } else {
2207
2223
  const nextItem = this.schedule.items?.[scheduleIndex + 1];
2208
2224
  if (nextItem) {
@@ -2228,6 +2244,30 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2228
2244
  player.once(Events.MEDIA_ENDED, endedWithAssetIndex(assetListIndex));
2229
2245
  player.once(Events.PLAYOUT_LIMIT_REACHED, endedWithAssetIndex(Infinity));
2230
2246
  player.on(Events.ERROR, (event: Events.ERROR, data: ErrorData) => {
2247
+ const inQueuPlayer = this.getAssetPlayer(assetId);
2248
+ if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) {
2249
+ if (inQueuPlayer?.media) {
2250
+ const assetCurrentTime = inQueuPlayer.currentTime;
2251
+ const distanceFromEnd = inQueuPlayer.duration - assetCurrentTime;
2252
+ if (
2253
+ assetCurrentTime &&
2254
+ interstitial.appendInPlace &&
2255
+ distanceFromEnd / inQueuPlayer.media.playbackRate < 0.5
2256
+ ) {
2257
+ this.log(
2258
+ `Advancing buffer past end of asset ${assetId} ${interstitial} at ${inQueuPlayer.media.currentTime}`,
2259
+ );
2260
+ bufferedToEnd();
2261
+ } else {
2262
+ this.warn(
2263
+ `Stalled at ${assetCurrentTime} of ${assetCurrentTime + distanceFromEnd} in asset ${assetId} ${interstitial}`,
2264
+ );
2265
+ this.onTimeupdate();
2266
+ this.checkBuffer(true);
2267
+ }
2268
+ }
2269
+ return;
2270
+ }
2231
2271
  this.handleAssetItemError(
2232
2272
  data,
2233
2273
  interstitial,
@@ -2336,10 +2376,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2336
2376
  });
2337
2377
  }
2338
2378
 
2339
- if (!player.bufferedInPlaceToEnd(media)) {
2340
- // detach media and attach to interstitial player if it does not have another element attached
2341
- this.bufferAssetPlayer(player, media);
2342
- }
2379
+ // detach media and attach to interstitial player if it does not have another element attached
2380
+ this.bufferAssetPlayer(player, media);
2343
2381
  }
2344
2382
 
2345
2383
  private bufferAssetPlayer(player: HlsAssetPlayer, media: HTMLMediaElement) {
@@ -2569,6 +2607,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2569
2607
  if (interstitial) {
2570
2608
  this.primaryFallback(interstitial);
2571
2609
  }
2610
+ break;
2611
+ }
2612
+ case ErrorDetails.BUFFER_STALLED_ERROR: {
2613
+ this.onTimeupdate();
2614
+ this.checkBuffer(true);
2615
+ break;
2572
2616
  }
2573
2617
  }
2574
2618
  }
@@ -628,7 +628,7 @@ export class InterstitialsSchedule extends Logger {
628
628
  return !playlists.some((playlistType) => {
629
629
  const details = mediaSelection[playlistType].details;
630
630
  const playlistEnd = details.edge;
631
- if (resumeTime > playlistEnd) {
631
+ if (resumeTime >= playlistEnd) {
632
632
  // Live playback - resumption segments are not yet available
633
633
  this.log(
634
634
  `"${interstitial.identifier}" resumption ${resumeTime} past ${playlistType} playlist end ${playlistEnd}`,
@@ -12,6 +12,7 @@ import ErrorController from './controller/error-controller';
12
12
  import FPSController from './controller/fps-controller';
13
13
  import SubtitleTrackController from './controller/subtitle-track-controller';
14
14
  import Hls from './hls';
15
+ import M3U8Parser from './loader/m3u8-parser';
15
16
  import Cues from './utils/cues';
16
17
  import FetchLoader from './utils/fetch-loader';
17
18
  import XhrLoader from './utils/xhr-loader';
@@ -36,6 +37,7 @@ export {
36
37
  XhrLoader,
37
38
  FetchLoader,
38
39
  Cues,
40
+ M3U8Parser,
39
41
  };
40
42
 
41
43
  export { Events } from './events';
@@ -275,22 +275,20 @@ export function parseInitSegment(initSegment: Uint8Array): InitData {
275
275
  soun: ElementaryStreamTypes.AUDIO as const,
276
276
  vide: ElementaryStreamTypes.VIDEO as const,
277
277
  }[hdlrType];
278
+ // Parse codec details
279
+ const stsdBox = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
280
+ const stsd = parseStsd(stsdBox);
278
281
  if (type) {
279
- // Parse codec details
280
- const stsdBox = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
281
- const stsd = parseStsd(stsdBox);
282
- if (type) {
283
- // Add 'audio', 'video', and 'audiovideo' track records that will map to SourceBuffers
284
- result[trackId] = { timescale, type, stsd };
285
- result[type] = { timescale, id: trackId, ...stsd };
286
- } else {
287
- // Add 'meta' and other track records required by `offsetStartDTS`
288
- result[trackId] = {
289
- timescale,
290
- type: hdlrType as HdlrType,
291
- stsd,
292
- };
293
- }
282
+ // Add 'audio', 'video', and 'audiovideo' track records that will map to SourceBuffers
283
+ result[trackId] = { timescale, type, stsd };
284
+ result[type] = { timescale, id: trackId, ...stsd };
285
+ } else {
286
+ // Add 'meta' and other track records required by `offsetStartDTS`
287
+ result[trackId] = {
288
+ timescale,
289
+ type: hdlrType as HdlrType,
290
+ stsd,
291
+ };
294
292
  }
295
293
  }
296
294
  }