hls.js 1.6.0-beta.3.0.canary.10980 → 1.6.0-beta.3.0.canary.10982

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.
@@ -31,6 +31,7 @@ import { Logger } from '../utils/logger';
31
31
  import { isCompatibleTrackChange } from '../utils/mediasource-helper';
32
32
  import { getBasicSelectionOption } from '../utils/rendition-helper';
33
33
  import { stringify } from '../utils/safe-json-stringify';
34
+ import type { InterstitialPlayer } from './interstitial-player';
34
35
  import type { HlsConfig } from '../config';
35
36
  import type Hls from '../hls';
36
37
  import type { LevelDetails } from '../loader/level-details';
@@ -55,18 +56,16 @@ import type { MediaPlaylist, MediaSelection } from '../types/media-playlist';
55
56
 
56
57
  export interface InterstitialsManager {
57
58
  events: InterstitialEvent[];
58
- playerQueue: HlsAssetPlayer[];
59
59
  schedule: InterstitialScheduleItem[];
60
- bufferingPlayer: HlsAssetPlayer | null;
60
+ interstitialPlayer: InterstitialPlayer | null;
61
+ playerQueue: HlsAssetPlayer[];
61
62
  bufferingAsset: InterstitialAssetItem | null;
62
63
  bufferingItem: InterstitialScheduleItem | null;
63
64
  bufferingIndex: number;
64
65
  playingAsset: InterstitialAssetItem | null;
65
66
  playingItem: InterstitialScheduleItem | null;
66
67
  playingIndex: number;
67
- waitingIndex: number;
68
68
  primary: PlayheadTimes;
69
- playout: PlayheadTimes;
70
69
  integrated: PlayheadTimes;
71
70
  skip: () => void;
72
71
  }
@@ -76,7 +75,6 @@ export type PlayheadTimes = {
76
75
  currentTime: number;
77
76
  duration: number;
78
77
  seekableStart: number;
79
- seekTo: (time: number) => void;
80
78
  };
81
79
 
82
80
  function playWithCatch(media: HTMLMediaElement | null) {
@@ -122,7 +120,9 @@ export default class InterstitialsController
122
120
  private playingItem: InterstitialScheduleItem | null = null;
123
121
  private bufferingItem: InterstitialScheduleItem | null = null;
124
122
  private waitingItem: InterstitialScheduleEventItem | null = null;
123
+ private endedItem: InterstitialScheduleItem | null = null;
125
124
  private playingAsset: InterstitialAssetItem | null = null;
125
+ private endedAsset: InterstitialAssetItem | null = null;
126
126
  private bufferingAsset: InterstitialAssetItem | null = null;
127
127
  private shouldPlay: boolean = false;
128
128
 
@@ -259,7 +259,7 @@ export default class InterstitialsController
259
259
  event: Events.MEDIA_ATTACHED,
260
260
  data: MediaAttachedData,
261
261
  ) {
262
- const playingItem = this.playingItem;
262
+ const playingItem = this.effectivePlayingItem;
263
263
  const detachedMedia = this.detachedData;
264
264
  this.detachedData = null;
265
265
  if (playingItem === null) {
@@ -276,7 +276,9 @@ export default class InterstitialsController
276
276
  this.playingItem =
277
277
  this.bufferingItem =
278
278
  this.waitingItem =
279
+ this.endedItem =
279
280
  this.playingAsset =
281
+ this.endedAsset =
280
282
  this.bufferingAsset =
281
283
  null;
282
284
  }
@@ -298,11 +300,13 @@ export default class InterstitialsController
298
300
  if (this.detachedData) {
299
301
  const player = this.getBufferingPlayer();
300
302
  if (player) {
301
- this.playingAsset = null;
302
- this.bufferingAsset = null;
303
- this.bufferingItem = null;
304
- this.waitingItem = null;
305
- this.detachedData = null;
303
+ this.playingAsset =
304
+ this.endedAsset =
305
+ this.bufferingAsset =
306
+ this.bufferingItem =
307
+ this.waitingItem =
308
+ this.detachedData =
309
+ null;
306
310
  player.detachMedia();
307
311
  }
308
312
  this.shouldPlay = false;
@@ -311,12 +315,11 @@ export default class InterstitialsController
311
315
 
312
316
  public get interstitialsManager(): InterstitialsManager | null {
313
317
  if (!this.manager) {
314
- if (!this.hls || !this.schedule.events) {
318
+ if (!this.hls) {
315
319
  return null;
316
320
  }
317
321
  const c = this;
318
322
  const effectiveBufferingItem = () => c.bufferingItem || c.waitingItem;
319
- const effectivePlayingItem = () => c.playingItem || c.waitingItem;
320
323
  const getAssetPlayer = (asset: InterstitialAssetItem | null) =>
321
324
  asset ? c.getAssetPlayer(asset.identifier) : asset;
322
325
  const getMappedTime = (
@@ -375,7 +378,7 @@ export default class InterstitialsController
375
378
  if (value === Number.MAX_VALUE) {
376
379
  return getMappedDuration('primary');
377
380
  }
378
- return value;
381
+ return Math.max(value, 0);
379
382
  };
380
383
  const getMappedDuration = (timelineType: TimelineType): number => {
381
384
  if (c.primaryDetails?.live) {
@@ -385,12 +388,12 @@ export default class InterstitialsController
385
388
  return c.schedule.durations[timelineType];
386
389
  };
387
390
  const seekTo = (time: number, timelineType: TimelineType) => {
388
- const item = effectivePlayingItem();
391
+ const item = c.effectivePlayingItem;
389
392
  if (item?.event?.restrictions.skip) {
390
393
  return;
391
394
  }
392
395
  c.log(`seek to ${time} "${timelineType}"`);
393
- const playingItem = effectivePlayingItem();
396
+ const playingItem = c.effectivePlayingItem;
394
397
  const targetIndex = c.schedule.findItemIndexAtTime(time, timelineType);
395
398
  const targetItem = c.schedule.items?.[targetIndex];
396
399
  const playingInterstitial = playingItem?.event;
@@ -399,7 +402,7 @@ export default class InterstitialsController
399
402
  if (playingItem && (appendInPlace || seekInItem)) {
400
403
  // seek in asset player or primary media (appendInPlace)
401
404
  const assetPlayer = getAssetPlayer(c.playingAsset);
402
- const media = assetPlayer?.media || c.hls.media;
405
+ const media = assetPlayer?.media || c.primaryMedia;
403
406
  if (media) {
404
407
  const currentTime =
405
408
  timelineType === 'primary'
@@ -481,6 +484,68 @@ export default class InterstitialsController
481
484
  }
482
485
  }
483
486
  };
487
+ const getActiveInterstitial = () => {
488
+ const playingItem = c.effectivePlayingItem;
489
+ if (c.isInterstitial(playingItem)) {
490
+ return playingItem;
491
+ }
492
+ const bufferingItem = effectiveBufferingItem();
493
+ if (c.isInterstitial(bufferingItem)) {
494
+ return bufferingItem;
495
+ }
496
+ return null;
497
+ };
498
+ const interstitialPlayer: InterstitialPlayer = {
499
+ get currentTime() {
500
+ const interstitialItem = getActiveInterstitial();
501
+ const playingItem = c.effectivePlayingItem;
502
+ if (playingItem && playingItem === interstitialItem) {
503
+ return (
504
+ getMappedTime(
505
+ playingItem,
506
+ 'playout',
507
+ c.effectivePlayingAsset,
508
+ 'timelinePos',
509
+ 'currentTime',
510
+ ) - playingItem.playout.start
511
+ );
512
+ }
513
+ return 0;
514
+ },
515
+ set currentTime(time: number) {
516
+ const interstitialItem = getActiveInterstitial();
517
+ const playingItem = c.effectivePlayingItem;
518
+ if (playingItem && playingItem === interstitialItem) {
519
+ seekTo(time + playingItem.playout.start, 'playout');
520
+ }
521
+ },
522
+ get duration() {
523
+ const interstitialItem = getActiveInterstitial();
524
+ if (interstitialItem) {
525
+ return (
526
+ interstitialItem.playout.end - interstitialItem.playout.start
527
+ );
528
+ }
529
+ return 0;
530
+ },
531
+ get assetPlayers() {
532
+ const assetList = getActiveInterstitial()?.event.assetList;
533
+ if (assetList) {
534
+ return assetList.map((asset) => c.getAssetPlayer(asset.identifier));
535
+ }
536
+ return [];
537
+ },
538
+ get playingIndex() {
539
+ const interstitial = getActiveInterstitial()?.event;
540
+ if (interstitial && c.effectivePlayingAsset) {
541
+ return interstitial.findAssetIndex(c.effectivePlayingAsset);
542
+ }
543
+ return -1;
544
+ },
545
+ get scheduleItem() {
546
+ return getActiveInterstitial();
547
+ },
548
+ };
484
549
  this.manager = {
485
550
  get events() {
486
551
  return c.schedule?.events?.slice(0) || [];
@@ -488,80 +553,56 @@ export default class InterstitialsController
488
553
  get schedule() {
489
554
  return c.schedule?.items?.slice(0) || [];
490
555
  },
556
+ get interstitialPlayer() {
557
+ if (getActiveInterstitial()) {
558
+ return interstitialPlayer;
559
+ }
560
+ return null;
561
+ },
491
562
  get playerQueue() {
492
563
  return c.playerQueue.slice(0);
493
564
  },
494
- get bufferingPlayer() {
495
- return c.getBufferingPlayer();
496
- },
497
565
  get bufferingAsset() {
498
566
  return c.bufferingAsset;
499
567
  },
500
568
  get bufferingItem() {
501
- return c.bufferingItem;
502
- },
503
- get playingAsset() {
504
- return c.playingAsset;
505
- },
506
- get playingItem() {
507
- return c.playingItem;
569
+ return effectiveBufferingItem();
508
570
  },
509
571
  get bufferingIndex() {
510
572
  const item = effectiveBufferingItem();
511
573
  return c.findItemIndex(item);
512
574
  },
575
+ get playingAsset() {
576
+ return c.effectivePlayingAsset;
577
+ },
578
+ get playingItem() {
579
+ return c.effectivePlayingItem;
580
+ },
513
581
  get playingIndex() {
514
- const item = effectivePlayingItem();
582
+ const item = c.effectivePlayingItem;
515
583
  return c.findItemIndex(item);
516
584
  },
517
- get waitingIndex() {
518
- return c.findItemIndex(c.waitingItem);
519
- },
520
585
  primary: {
521
586
  get bufferedEnd() {
522
587
  return getBufferedEnd();
523
588
  },
524
589
  get currentTime() {
525
590
  const timelinePos = c.timelinePos;
591
+ const playingItem = c.effectivePlayingItem;
592
+ if (playingItem?.event?.appendInPlace) {
593
+ return playingItem.start;
594
+ }
526
595
  return timelinePos > 0 ? timelinePos : 0;
527
596
  },
597
+ set currentTime(time: number) {
598
+ seekTo(time, 'primary');
599
+ },
528
600
  get duration() {
529
601
  return getMappedDuration('primary');
530
602
  },
531
603
  get seekableStart() {
532
604
  return c.primaryDetails?.fragmentStart || 0;
533
605
  },
534
- seekTo: (time) => seekTo(time, 'primary'),
535
- },
536
- playout: {
537
- get bufferedEnd() {
538
- return getMappedTime(
539
- effectiveBufferingItem(),
540
- 'playout',
541
- c.bufferingAsset,
542
- 'bufferedPos',
543
- 'bufferedEnd',
544
- );
545
- },
546
- get currentTime() {
547
- return getMappedTime(
548
- effectivePlayingItem(),
549
- 'playout',
550
- c.playingAsset,
551
- 'timelinePos',
552
- 'currentTime',
553
- );
554
- },
555
- get duration() {
556
- return getMappedDuration('playout');
557
- },
558
- get seekableStart() {
559
- return findMappedTime(
560
- c.primaryDetails?.fragmentStart || 0,
561
- 'playout',
562
- );
563
- },
564
- seekTo: (time) => seekTo(time, 'playout'),
565
606
  },
566
607
  integrated: {
567
608
  get bufferedEnd() {
@@ -575,13 +616,16 @@ export default class InterstitialsController
575
616
  },
576
617
  get currentTime() {
577
618
  return getMappedTime(
578
- effectivePlayingItem(),
619
+ c.effectivePlayingItem,
579
620
  'integrated',
580
- c.playingAsset,
621
+ c.effectivePlayingAsset,
581
622
  'timelinePos',
582
623
  'currentTime',
583
624
  );
584
625
  },
626
+ set currentTime(time: number) {
627
+ seekTo(time, 'integrated');
628
+ },
585
629
  get duration() {
586
630
  return getMappedDuration('integrated');
587
631
  },
@@ -591,10 +635,9 @@ export default class InterstitialsController
591
635
  'integrated',
592
636
  );
593
637
  },
594
- seekTo: (time) => seekTo(time, 'integrated'),
595
638
  },
596
639
  skip: () => {
597
- const item = effectivePlayingItem();
640
+ const item = c.effectivePlayingItem;
598
641
  const event = item?.event;
599
642
  if (event && !event.restrictions.skip) {
600
643
  const index = c.findItemIndex(item);
@@ -612,6 +655,14 @@ export default class InterstitialsController
612
655
  }
613
656
 
614
657
  // Schedule getters
658
+ private get effectivePlayingItem(): InterstitialScheduleItem | null {
659
+ return this.waitingItem || this.playingItem || this.endedItem;
660
+ }
661
+
662
+ private get effectivePlayingAsset(): InterstitialAssetItem | null {
663
+ return this.playingAsset || this.endedAsset;
664
+ }
665
+
615
666
  private get playingLastItem(): boolean {
616
667
  const playingItem = this.playingItem;
617
668
  const items = this.schedule?.items;
@@ -623,7 +674,7 @@ export default class InterstitialsController
623
674
  }
624
675
 
625
676
  private get playbackStarted(): boolean {
626
- return this.playingItem !== null;
677
+ return this.effectivePlayingItem !== null;
627
678
  }
628
679
 
629
680
  // Media getters and event callbacks
@@ -828,6 +879,7 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
828
879
  if (this.playingLastItem && this.isInterstitial(playingItem)) {
829
880
  const restartAsset = playingItem.event.assetList[0];
830
881
  if (restartAsset) {
882
+ this.endedItem = this.playingItem;
831
883
  this.playingItem = null;
832
884
  this.setScheduleToAssetAtTime(currentTime, restartAsset);
833
885
  }
@@ -899,7 +951,7 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
899
951
  }
900
952
  // Start stepping through schedule when playback begins for the first time and we have a pre-roll
901
953
  const timelinePos = this.timelinePos;
902
- const waitingItem = this.waitingItem;
954
+ const effectivePlayingItem = this.effectivePlayingItem;
903
955
  if (timelinePos === -1) {
904
956
  const startPosition = this.hls.startPosition;
905
957
  this.timelinePos = startPosition;
@@ -912,8 +964,8 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
912
964
  const index = schedule.findItemIndexAtTime(start);
913
965
  this.setSchedulePosition(index);
914
966
  }
915
- } else if (waitingItem && !this.playingItem) {
916
- const index = schedule.findItemIndex(waitingItem);
967
+ } else if (effectivePlayingItem && !this.playingItem) {
968
+ const index = schedule.findItemIndex(effectivePlayingItem);
917
969
  this.setSchedulePosition(index);
918
970
  }
919
971
  }
@@ -986,11 +1038,12 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
986
1038
  (assetListIndex !== undefined &&
987
1039
  assetId !== interstitial.assetList?.[assetListIndex].identifier))
988
1040
  ) {
989
- this.playingAsset = null;
990
1041
  const assetListIndex = interstitial.findAssetIndex(playingAsset);
991
1042
  this.log(
992
1043
  `INTERSTITIAL_ASSET_ENDED ${assetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`,
993
1044
  );
1045
+ this.endedAsset = playingAsset;
1046
+ this.playingAsset = null;
994
1047
  this.hls.trigger(Events.INTERSTITIAL_ASSET_ENDED, {
995
1048
  asset: playingAsset,
996
1049
  assetListIndex,
@@ -1003,9 +1056,9 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
1003
1056
  if (player.media && !this.detachedData) {
1004
1057
  player.detachMedia();
1005
1058
  }
1006
- this.clearAssetPlayer(assetId, scheduledItem);
1007
1059
  }
1008
1060
  if (!this.eventItemsMatch(currentItem, scheduledItem)) {
1061
+ this.endedItem = currentItem;
1009
1062
  this.playingItem = null;
1010
1063
  this.log(
1011
1064
  `INTERSTITIAL_ENDED ${interstitial} ${segmentToString(currentItem)}`,
@@ -1085,7 +1138,7 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
1085
1138
  const waitingItem = this.waitingItem;
1086
1139
  this.setBufferingItem(scheduledItem);
1087
1140
  let player = this.preloadAssets(interstitial, assetListIndex);
1088
- if (!this.eventItemsMatch(scheduledItem, currentItem || waitingItem)) {
1141
+ if (!this.eventItemsMatch(scheduledItem, waitingItem || currentItem)) {
1089
1142
  this.waitingItem = scheduledItem;
1090
1143
  this.log(
1091
1144
  `INTERSTITIAL_STARTED ${segmentToString(scheduledItem)} ${interstitial.appendInPlace ? 'append in place' : ''}`,
@@ -1113,7 +1166,7 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
1113
1166
  return;
1114
1167
  }
1115
1168
  // Update schedule and asset list position now that it can start
1116
- this.waitingItem = null;
1169
+ this.waitingItem = this.endedItem = null;
1117
1170
  this.playingItem = scheduledItem;
1118
1171
 
1119
1172
  // If asset-list is empty or missing asset index, advance to next item
@@ -1173,6 +1226,7 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
1173
1226
  } else if (playedLastItem && this.isInterstitial(currentItem)) {
1174
1227
  // Maintain playingItem state at end of schedule (setSchedulePosition(-1) called to end program)
1175
1228
  // this allows onSeeking handler to update schedule position
1229
+ this.endedItem = null;
1176
1230
  this.playingItem = currentItem;
1177
1231
  if (!currentItem.event.appendInPlace) {
1178
1232
  // Media must be re-attached to resume primary schedule if not sharing source
@@ -1199,8 +1253,8 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
1199
1253
  fromItem: InterstitialScheduleItem | null,
1200
1254
  ) {
1201
1255
  this.playingItem = scheduledItem;
1202
- this.playingAsset = null;
1203
- this.waitingItem = null;
1256
+ this.playingAsset = this.endedAsset = null;
1257
+ this.waitingItem = this.endedItem = null;
1204
1258
 
1205
1259
  this.bufferedToItem(scheduledItem);
1206
1260
 
@@ -1345,7 +1399,7 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
1345
1399
  this.mediaSelection = currentSelection;
1346
1400
  this.schedule.parseInterstitialDateRanges(currentSelection);
1347
1401
 
1348
- if (!this.playingItem && this.schedule.items) {
1402
+ if (!this.effectivePlayingItem && this.schedule.items) {
1349
1403
  this.checkStart();
1350
1404
  }
1351
1405
  }
@@ -1420,7 +1474,7 @@ MediaSource ${stringify(attachMediaSourceData)} from ${logFromSource}`,
1420
1474
  event: Events.BUFFER_FLUSHED,
1421
1475
  data: BufferFlushedData,
1422
1476
  ) {
1423
- const { playingItem } = this;
1477
+ const playingItem = this.playingItem;
1424
1478
  if (
1425
1479
  playingItem &&
1426
1480
  !this.itemsMatch(playingItem, this.bufferingItem) &&
@@ -1519,7 +1573,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1519
1573
  const updatedPlayingItem = this.updateItem(playingItem, this.timelinePos);
1520
1574
  if (this.itemsMatch(playingItem, updatedPlayingItem)) {
1521
1575
  this.playingItem = updatedPlayingItem;
1576
+ this.waitingItem = this.endedItem = null;
1522
1577
  }
1578
+ } else {
1579
+ // Clear waitingItem if it has been removed from the schedule
1580
+ this.waitingItem = this.updateItem(this.waitingItem);
1581
+ this.endedItem = this.updateItem(this.endedItem);
1523
1582
  }
1524
1583
  // Do not replace Interstitial bufferingItem without a match - used for transfering media element or source
1525
1584
  const bufferingItem = this.bufferingItem;
@@ -1536,8 +1595,6 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1536
1595
  this.clearInterstitial(bufferingItem.event, null);
1537
1596
  }
1538
1597
  }
1539
- // Clear waitingItem if it has been removed from the schedule
1540
- this.waitingItem = this.updateItem(this.waitingItem);
1541
1598
 
1542
1599
  removedInterstitials.forEach((interstitial) => {
1543
1600
  interstitial.assetList.forEach((asset) => {
@@ -1727,7 +1784,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1727
1784
  if (!this.playbackDisabled) {
1728
1785
  if (isInterstitial) {
1729
1786
  // primary fragment loading will exit early in base-stream-controller while `bufferingItem` is set to an Interstitial block
1730
- this.playerQueue.forEach((player) => player.resumeBuffering());
1787
+ item.event.assetList.forEach((asset) => {
1788
+ const player = this.getAssetPlayer(asset.identifier);
1789
+ if (player) {
1790
+ player.resumeBuffering();
1791
+ }
1792
+ });
1731
1793
  } else {
1732
1794
  this.hls.resumeBuffering();
1733
1795
  this.playerQueue.forEach((player) => player.pauseBuffering());
@@ -1815,7 +1877,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
1815
1877
  );
1816
1878
  const timelineStart = interstitial.timelineStart;
1817
1879
  if (interstitial.appendInPlace) {
1818
- this.flushFrontBuffer(timelineStart);
1880
+ this.flushFrontBuffer(timelineStart + 0.25);
1819
1881
  }
1820
1882
  const uri = interstitial.assetUrl;
1821
1883
  if (uri) {
@@ -2181,6 +2243,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2181
2243
  const assetListLength = interstitial.assetList.length;
2182
2244
 
2183
2245
  const playingAsset = this.playingAsset;
2246
+ this.endedAsset = null;
2184
2247
  this.playingAsset = assetItem;
2185
2248
  if (!playingAsset || playingAsset.identifier !== assetId) {
2186
2249
  if (playingAsset) {
@@ -2194,7 +2257,6 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2194
2257
  this.log(
2195
2258
  `INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${player}`,
2196
2259
  );
2197
- // player.resumeBuffering();
2198
2260
  this.hls.trigger(Events.INTERSTITIAL_ASSET_STARTED, {
2199
2261
  asset: assetItem,
2200
2262
  assetListIndex,
@@ -2317,7 +2379,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
2317
2379
  private primaryFallback(interstitial: InterstitialEvent) {
2318
2380
  // Fallback to Primary by on current or future events by updating schedule to skip errored interstitials/assets
2319
2381
  const flushStart = interstitial.timelineStart;
2320
- const playingItem = this.playingItem || this.waitingItem;
2382
+ const playingItem = this.effectivePlayingItem;
2321
2383
  // Update schedule now that interstitial/assets are flagged with `error` for fallback
2322
2384
  this.updateSchedule();
2323
2385
  if (playingItem) {
@@ -160,7 +160,7 @@ export default class StreamController
160
160
  // hls.nextLoadLevel remains until it is set to a new value or until a new frag is successfully loaded
161
161
  hls.nextLoadLevel = startLevel;
162
162
  this.level = hls.loadLevel;
163
- this._hasEnoughToStart = false;
163
+ this._hasEnoughToStart = !!skipSeekToStartPosition;
164
164
  }
165
165
  // if startPosition undefined but lastCurrentTime set, set startPosition to last currentTime
166
166
  if (
@@ -975,7 +975,7 @@ export default class StreamController
975
975
  if (!media) {
976
976
  return;
977
977
  }
978
- if (!this._hasEnoughToStart && media.buffered.length) {
978
+ if (!this._hasEnoughToStart && BufferHelper.getBuffered(media).length) {
979
979
  this._hasEnoughToStart = true;
980
980
  this.seekToStartPos();
981
981
  }
@@ -1015,13 +1015,15 @@ export default class StreamController
1015
1015
  this.state = State.IDLE;
1016
1016
  }
1017
1017
  break;
1018
+ case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
1018
1019
  case ErrorDetails.BUFFER_APPEND_ERROR:
1019
- case ErrorDetails.BUFFER_FULL_ERROR:
1020
- if (!data.parent || data.parent !== 'main') {
1020
+ if (data.parent !== 'main') {
1021
1021
  return;
1022
1022
  }
1023
- if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) {
1024
- this.resetLoadingState();
1023
+ this.resetLoadingState();
1024
+ break;
1025
+ case ErrorDetails.BUFFER_FULL_ERROR:
1026
+ if (data.parent !== 'main') {
1025
1027
  return;
1026
1028
  }
1027
1029
  if (this.reduceLengthAndFlushBuffer(data)) {
@@ -1416,7 +1418,7 @@ export default class StreamController
1416
1418
  );
1417
1419
  }
1418
1420
  audio.levelCodec = audioCodec;
1419
- audio.id = 'main';
1421
+ audio.id = PlaylistLevelType.MAIN;
1420
1422
  this.log(
1421
1423
  `Init audio buffer, container:${
1422
1424
  audio.container
@@ -1428,7 +1430,7 @@ export default class StreamController
1428
1430
  }
1429
1431
  if (video) {
1430
1432
  video.levelCodec = currentLevel.videoCodec;
1431
- video.id = 'main';
1433
+ video.id = PlaylistLevelType.MAIN;
1432
1434
  const parsedVideoCodec = video.codec;
1433
1435
  if (parsedVideoCodec?.length === 4) {
1434
1436
  // Make up for passthrough-remuxer not being able to parse full codec
@@ -1490,10 +1492,12 @@ export default class StreamController
1490
1492
  }
1491
1493
 
1492
1494
  public getMainFwdBufferInfo(): BufferInfo | null {
1493
- return this.getFwdBufferInfo(
1494
- this.mediaBuffer ? this.mediaBuffer : this.media,
1495
- PlaylistLevelType.MAIN,
1496
- );
1495
+ // Observe video SourceBuffer (this.mediaBuffer) only when alt-audio is used, otherwise observe combined media buffer
1496
+ const bufferOutput =
1497
+ this.mediaBuffer && this.altAudio === AlternateAudio.SWITCHED
1498
+ ? this.mediaBuffer
1499
+ : this.media;
1500
+ return this.getFwdBufferInfo(bufferOutput, PlaylistLevelType.MAIN);
1497
1501
  }
1498
1502
 
1499
1503
  public get maxBufferLength(): number {
@@ -9,7 +9,7 @@ declare const __USE_CONTENT_STEERING__: boolean;
9
9
  declare const __USE_VARIABLE_SUBSTITUTION__: boolean;
10
10
  declare const __USE_M2TS_ADVANCED_CODECS__: boolean;
11
11
  declare const __USE_MEDIA_CAPABILITIES__: boolean;
12
- declare const __USE_INTERSTITALS__: boolean;
12
+ declare const __USE_INTERSTITIALS__: boolean;
13
13
 
14
14
  // __IN_WORKER__ is provided from a closure call around the final UMD bundle.
15
15
  declare const __IN_WORKER__: boolean;
package/src/hls.ts CHANGED
@@ -1324,7 +1324,10 @@ export type {
1324
1324
  ErrorActionFlags,
1325
1325
  IErrorAction,
1326
1326
  } from './controller/error-controller';
1327
- export type { HlsAssetPlayer } from './controller/interstitial-player';
1327
+ export type {
1328
+ HlsAssetPlayer,
1329
+ InterstitialPlayer,
1330
+ } from './controller/interstitial-player';
1328
1331
  export type { PlayheadTimes } from './controller/interstitials-controller';
1329
1332
  export type {
1330
1333
  InterstitialScheduleDurations,
@@ -38,8 +38,8 @@ export type InterstitialAssetItem = {
38
38
  parentIdentifier: InterstitialId;
39
39
  identifier: InterstitialAssetId;
40
40
  duration: number | null;
41
- startOffset: number;
42
- timelineStart: number;
41
+ startOffset: number; // asset start offset from start of interstitial event
42
+ timelineStart: number; // asset start on media element timeline
43
43
  uri: string;
44
44
  error?: Error;
45
45
  };
@@ -1,4 +1,9 @@
1
1
  import { getMediaSource } from './mediasource-helper';
2
+ import { isHEVC } from './mp4-tools';
3
+
4
+ export const userAgentHevcSupportIsInaccurate = () => {
5
+ return /\(Windows.+Firefox\//i.test(navigator.userAgent);
6
+ };
2
7
 
3
8
  // from http://mp4ra.org/codecs.html
4
9
  // values indicate codec selection preference (lower is higher priority)
@@ -121,8 +126,12 @@ export function videoCodecPreferenceValue(
121
126
  }
122
127
 
123
128
  export function codecsSetSelectionPreferenceValue(codecSet: string): number {
129
+ const limitedHevcSupport = userAgentHevcSupportIsInaccurate();
124
130
  return codecSet.split(',').reduce((num, fourCC) => {
125
- const preferenceValue = sampleEntryCodesISO.video[fourCC];
131
+ const lowerPriority = limitedHevcSupport && isHEVC(fourCC);
132
+ const preferenceValue = lowerPriority
133
+ ? 9
134
+ : sampleEntryCodesISO.video[fourCC];
126
135
  if (preferenceValue) {
127
136
  return (preferenceValue * 2 + num) / (num ? 3 : 2);
128
137
  }
@@ -272,3 +281,7 @@ export function getM2TSSupportedAudioTypes(
272
281
  : false,
273
282
  };
274
283
  }
284
+
285
+ export function getCodecsForMimeType(mimeType: string): string {
286
+ return mimeType.replace(/^.+codecs=["']?([^"']+).*$/, '$1');
287
+ }