hls.js 1.5.12-0.canary.10340 → 1.5.12-0.canary.10341

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/dist/hls.mjs CHANGED
@@ -359,87 +359,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
359
359
  return ErrorDetails;
360
360
  }({});
361
361
 
362
- const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
363
- const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
364
-
365
- // adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js
366
- class AttrList {
367
- constructor(attrs) {
368
- if (typeof attrs === 'string') {
369
- attrs = AttrList.parseAttrList(attrs);
370
- }
371
- _extends(this, attrs);
372
- }
373
- get clientAttrs() {
374
- return Object.keys(this).filter(attr => attr.substring(0, 2) === 'X-');
375
- }
376
- decimalInteger(attrName) {
377
- const intValue = parseInt(this[attrName], 10);
378
- if (intValue > Number.MAX_SAFE_INTEGER) {
379
- return Infinity;
380
- }
381
- return intValue;
382
- }
383
- hexadecimalInteger(attrName) {
384
- if (this[attrName]) {
385
- let stringValue = (this[attrName] || '0x').slice(2);
386
- stringValue = (stringValue.length & 1 ? '0' : '') + stringValue;
387
- const value = new Uint8Array(stringValue.length / 2);
388
- for (let i = 0; i < stringValue.length / 2; i++) {
389
- value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
390
- }
391
- return value;
392
- } else {
393
- return null;
394
- }
395
- }
396
- hexadecimalIntegerAsNumber(attrName) {
397
- const intValue = parseInt(this[attrName], 16);
398
- if (intValue > Number.MAX_SAFE_INTEGER) {
399
- return Infinity;
400
- }
401
- return intValue;
402
- }
403
- decimalFloatingPoint(attrName) {
404
- return parseFloat(this[attrName]);
405
- }
406
- optionalFloat(attrName, defaultValue) {
407
- const value = this[attrName];
408
- return value ? parseFloat(value) : defaultValue;
409
- }
410
- enumeratedString(attrName) {
411
- return this[attrName];
412
- }
413
- bool(attrName) {
414
- return this[attrName] === 'YES';
415
- }
416
- decimalResolution(attrName) {
417
- const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
418
- if (res === null) {
419
- return undefined;
420
- }
421
- return {
422
- width: parseInt(res[1], 10),
423
- height: parseInt(res[2], 10)
424
- };
425
- }
426
- static parseAttrList(input) {
427
- let match;
428
- const attrs = {};
429
- const quote = '"';
430
- ATTR_LIST_REGEX.lastIndex = 0;
431
- while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
432
- let value = match[2];
433
- if (value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1) {
434
- value = value.slice(1, -1);
435
- }
436
- const name = match[1].trim();
437
- attrs[name] = value;
438
- }
439
- return attrs;
440
- }
441
- }
442
-
443
362
  class Logger {
444
363
  constructor(label, logger) {
445
364
  this.trace = void 0;
@@ -501,7 +420,7 @@ function enableLogs(debugConfig, context, id) {
501
420
  // Some browsers don't allow to use bind on console object anyway
502
421
  // fallback to default if needed
503
422
  try {
504
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.12-0.canary.10340"}`);
423
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.12-0.canary.10341"}`);
505
424
  } catch (e) {
506
425
  /* log fn threw an exception. All logger methods are no-ops. */
507
426
  return createLogger();
@@ -518,20 +437,234 @@ function enableLogs(debugConfig, context, id) {
518
437
  }
519
438
  const logger = exportedLogger;
520
439
 
440
+ const VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g;
441
+ function hasVariableReferences(str) {
442
+ return VARIABLE_REPLACEMENT_REGEX.test(str);
443
+ }
444
+ function substituteVariables(parsed, value) {
445
+ if (parsed.variableList !== null || parsed.hasVariableRefs) {
446
+ const variableList = parsed.variableList;
447
+ return value.replace(VARIABLE_REPLACEMENT_REGEX, variableReference => {
448
+ const variableName = variableReference.substring(2, variableReference.length - 1);
449
+ const variableValue = variableList == null ? void 0 : variableList[variableName];
450
+ if (variableValue === undefined) {
451
+ parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`));
452
+ return variableReference;
453
+ }
454
+ return variableValue;
455
+ });
456
+ }
457
+ return value;
458
+ }
459
+ function addVariableDefinition(parsed, attr, parentUrl) {
460
+ let variableList = parsed.variableList;
461
+ if (!variableList) {
462
+ parsed.variableList = variableList = {};
463
+ }
464
+ let NAME;
465
+ let VALUE;
466
+ if ('QUERYPARAM' in attr) {
467
+ NAME = attr.QUERYPARAM;
468
+ try {
469
+ const searchParams = new self.URL(parentUrl).searchParams;
470
+ if (searchParams.has(NAME)) {
471
+ VALUE = searchParams.get(NAME);
472
+ } else {
473
+ throw new Error(`"${NAME}" does not match any query parameter in URI: "${parentUrl}"`);
474
+ }
475
+ } catch (error) {
476
+ parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`));
477
+ }
478
+ } else {
479
+ NAME = attr.NAME;
480
+ VALUE = attr.VALUE;
481
+ }
482
+ if (NAME in variableList) {
483
+ parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`));
484
+ } else {
485
+ variableList[NAME] = VALUE || '';
486
+ }
487
+ }
488
+ function importVariableDefinition(parsed, attr, sourceVariableList) {
489
+ const IMPORT = attr.IMPORT;
490
+ if (sourceVariableList && IMPORT in sourceVariableList) {
491
+ let variableList = parsed.variableList;
492
+ if (!variableList) {
493
+ parsed.variableList = variableList = {};
494
+ }
495
+ variableList[IMPORT] = sourceVariableList[IMPORT];
496
+ } else {
497
+ parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`));
498
+ }
499
+ }
500
+
501
+ const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
502
+ const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
503
+
504
+ // adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js
505
+ class AttrList {
506
+ constructor(attrs, parsed) {
507
+ if (typeof attrs === 'string') {
508
+ attrs = AttrList.parseAttrList(attrs, parsed);
509
+ }
510
+ _extends(this, attrs);
511
+ }
512
+ get clientAttrs() {
513
+ return Object.keys(this).filter(attr => attr.substring(0, 2) === 'X-');
514
+ }
515
+ decimalInteger(attrName) {
516
+ const intValue = parseInt(this[attrName], 10);
517
+ if (intValue > Number.MAX_SAFE_INTEGER) {
518
+ return Infinity;
519
+ }
520
+ return intValue;
521
+ }
522
+ hexadecimalInteger(attrName) {
523
+ if (this[attrName]) {
524
+ let stringValue = (this[attrName] || '0x').slice(2);
525
+ stringValue = (stringValue.length & 1 ? '0' : '') + stringValue;
526
+ const value = new Uint8Array(stringValue.length / 2);
527
+ for (let i = 0; i < stringValue.length / 2; i++) {
528
+ value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
529
+ }
530
+ return value;
531
+ } else {
532
+ return null;
533
+ }
534
+ }
535
+ hexadecimalIntegerAsNumber(attrName) {
536
+ const intValue = parseInt(this[attrName], 16);
537
+ if (intValue > Number.MAX_SAFE_INTEGER) {
538
+ return Infinity;
539
+ }
540
+ return intValue;
541
+ }
542
+ decimalFloatingPoint(attrName) {
543
+ return parseFloat(this[attrName]);
544
+ }
545
+ optionalFloat(attrName, defaultValue) {
546
+ const value = this[attrName];
547
+ return value ? parseFloat(value) : defaultValue;
548
+ }
549
+ enumeratedString(attrName) {
550
+ return this[attrName];
551
+ }
552
+ enumeratedStringList(attrName, dict) {
553
+ const attrValue = this[attrName];
554
+ return (attrValue ? attrValue.split(/[ ,]+/) : []).reduce((result, identifier) => {
555
+ result[identifier.toLowerCase()] = true;
556
+ return result;
557
+ }, dict);
558
+ }
559
+ bool(attrName) {
560
+ return this[attrName] === 'YES';
561
+ }
562
+ decimalResolution(attrName) {
563
+ const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
564
+ if (res === null) {
565
+ return undefined;
566
+ }
567
+ return {
568
+ width: parseInt(res[1], 10),
569
+ height: parseInt(res[2], 10)
570
+ };
571
+ }
572
+ static parseAttrList(input, parsed) {
573
+ let match;
574
+ const attrs = {};
575
+ const quote = '"';
576
+ ATTR_LIST_REGEX.lastIndex = 0;
577
+ while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
578
+ const name = match[1].trim();
579
+ let value = match[2];
580
+ const quotedString = value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1;
581
+ let hexadecimalSequence = false;
582
+ if (quotedString) {
583
+ value = value.slice(1, -1);
584
+ } else {
585
+ switch (name) {
586
+ case 'IV':
587
+ case 'SCTE35-CMD':
588
+ case 'SCTE35-IN':
589
+ case 'SCTE35-OUT':
590
+ hexadecimalSequence = true;
591
+ }
592
+ }
593
+ if (parsed && (quotedString || hexadecimalSequence)) {
594
+ {
595
+ value = substituteVariables(parsed, value);
596
+ }
597
+ } else if (!hexadecimalSequence && !quotedString) {
598
+ switch (name) {
599
+ case 'CLOSED-CAPTIONS':
600
+ if (value === 'NONE') {
601
+ break;
602
+ }
603
+ // falls through
604
+ case 'ALLOWED-CPC':
605
+ case 'CLASS':
606
+ case 'ASSOC-LANGUAGE':
607
+ case 'AUDIO':
608
+ case 'BYTERANGE':
609
+ case 'CHANNELS':
610
+ case 'CHARACTERISTICS':
611
+ case 'CODECS':
612
+ case 'DATA-ID':
613
+ case 'END-DATE':
614
+ case 'GROUP-ID':
615
+ case 'ID':
616
+ case 'IMPORT':
617
+ case 'INSTREAM-ID':
618
+ case 'KEYFORMAT':
619
+ case 'KEYFORMATVERSIONS':
620
+ case 'LANGUAGE':
621
+ case 'NAME':
622
+ case 'PATHWAY-ID':
623
+ case 'QUERYPARAM':
624
+ case 'RECENTLY-REMOVED-DATERANGES':
625
+ case 'SERVER-URI':
626
+ case 'STABLE-RENDITION-ID':
627
+ case 'STABLE-VARIANT-ID':
628
+ case 'START-DATE':
629
+ case 'SUBTITLES':
630
+ case 'SUPPLEMENTAL-CODECS':
631
+ case 'URI':
632
+ case 'VALUE':
633
+ case 'VIDEO':
634
+ case 'X-ASSET-LIST':
635
+ case 'X-ASSET-URI':
636
+ // Since we are not checking tag:attribute combination, just warn rather than ignoring attribute
637
+ logger.warn(`${input}: attribute ${name} is missing quotes`);
638
+ // continue;
639
+ }
640
+ }
641
+ attrs[name] = value;
642
+ }
643
+ return attrs;
644
+ }
645
+ }
646
+
521
647
  // Avoid exporting const enum so that these values can be inlined
522
648
 
649
+ const CLASS_INTERSTITIAL = 'com.apple.hls.interstitial';
523
650
  function isDateRangeCueAttribute(attrName) {
524
- return attrName !== "ID" && attrName !== "CLASS" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
651
+ return attrName !== "ID" && attrName !== "CLASS" && attrName !== "CUE" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
525
652
  }
526
653
  function isSCTE35Attribute(attrName) {
527
- return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN";
654
+ return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN" || attrName === "SCTE35-CMD";
528
655
  }
529
656
  class DateRange {
530
- constructor(dateRangeAttr, dateRangeWithSameId) {
657
+ constructor(dateRangeAttr, dateRangeWithSameId, tagCount = 0) {
658
+ var _dateRangeWithSameId$;
531
659
  this.attr = void 0;
660
+ this.tagAnchor = void 0;
661
+ this.tagOrder = void 0;
532
662
  this._startDate = void 0;
533
663
  this._endDate = void 0;
664
+ this._cue = void 0;
534
665
  this._badValueForSameId = void 0;
666
+ this.tagAnchor = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagAnchor) || null;
667
+ this.tagOrder = (_dateRangeWithSameId$ = dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagOrder) != null ? _dateRangeWithSameId$ : tagCount;
535
668
  if (dateRangeWithSameId) {
536
669
  const previousAttr = dateRangeWithSameId.attr;
537
670
  for (const key in previousAttr) {
@@ -545,9 +678,9 @@ class DateRange {
545
678
  dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr);
546
679
  }
547
680
  this.attr = dateRangeAttr;
548
- this._startDate = new Date(dateRangeAttr["START-DATE"]);
681
+ this._startDate = dateRangeWithSameId ? dateRangeWithSameId.startDate : new Date(dateRangeAttr["START-DATE"]);
549
682
  if ("END-DATE" in this.attr) {
550
- const endDate = new Date(this.attr["END-DATE"]);
683
+ const endDate = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.endDate) || new Date(this.attr["END-DATE"]);
551
684
  if (isFiniteNumber(endDate.getTime())) {
552
685
  this._endDate = endDate;
553
686
  }
@@ -559,6 +692,28 @@ class DateRange {
559
692
  get class() {
560
693
  return this.attr.CLASS;
561
694
  }
695
+ get cue() {
696
+ const _cue = this._cue;
697
+ if (_cue === undefined) {
698
+ return this._cue = this.attr.enumeratedStringList(this.attr.CUE ? 'CUE' : 'X-CUE', {
699
+ pre: false,
700
+ post: false,
701
+ once: false
702
+ });
703
+ }
704
+ return _cue;
705
+ }
706
+ get startTime() {
707
+ const {
708
+ tagAnchor
709
+ } = this;
710
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
711
+ if (tagAnchor === null || tagAnchor.programDateTime === null) {
712
+ logger.warn(`Expected tagAnchor Fragment with PDT set for DateRange "${this.id}": ${tagAnchor}`);
713
+ return NaN;
714
+ }
715
+ return tagAnchor.start + (this.startDate.getTime() - tagAnchor.programDateTime) / 1000;
716
+ }
562
717
  get startDate() {
563
718
  return this._startDate;
564
719
  }
@@ -592,8 +747,11 @@ class DateRange {
592
747
  get endOnNext() {
593
748
  return this.attr.bool("END-ON-NEXT");
594
749
  }
750
+ get isInterstitial() {
751
+ return this.class === CLASS_INTERSTITIAL;
752
+ }
595
753
  get isValid() {
596
- return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class);
754
+ return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class) && (!this.attr.CUE || !this.cue.pre && !this.cue.post || this.cue.pre !== this.cue.post) && (!this.isInterstitial || 'X-ASSET-URI' in this.attr || 'X-ASSET-LIST' in this.attr);
597
755
  }
598
756
  }
599
757
 
@@ -679,7 +837,6 @@ class BaseSegment {
679
837
  this._url = value;
680
838
  }
681
839
  }
682
-
683
840
  /**
684
841
  * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
685
842
  */
@@ -892,6 +1049,7 @@ class LevelDetails {
892
1049
  this.fragmentHint = void 0;
893
1050
  this.partList = null;
894
1051
  this.dateRanges = void 0;
1052
+ this.dateRangeTagCount = 0;
895
1053
  this.live = true;
896
1054
  this.ageHeader = 0;
897
1055
  this.advancedDateTime = void 0;
@@ -2574,78 +2732,6 @@ function createInitializationVector(segmentNumber) {
2574
2732
  return uint8View;
2575
2733
  }
2576
2734
 
2577
- const VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g;
2578
- function hasVariableReferences(str) {
2579
- return VARIABLE_REPLACEMENT_REGEX.test(str);
2580
- }
2581
- function substituteVariablesInAttributes(parsed, attr, attributeNames) {
2582
- if (parsed.variableList !== null || parsed.hasVariableRefs) {
2583
- for (let i = attributeNames.length; i--;) {
2584
- const name = attributeNames[i];
2585
- const value = attr[name];
2586
- if (value) {
2587
- attr[name] = substituteVariables(parsed, value);
2588
- }
2589
- }
2590
- }
2591
- }
2592
- function substituteVariables(parsed, value) {
2593
- if (parsed.variableList !== null || parsed.hasVariableRefs) {
2594
- const variableList = parsed.variableList;
2595
- return value.replace(VARIABLE_REPLACEMENT_REGEX, variableReference => {
2596
- const variableName = variableReference.substring(2, variableReference.length - 1);
2597
- const variableValue = variableList == null ? void 0 : variableList[variableName];
2598
- if (variableValue === undefined) {
2599
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`));
2600
- return variableReference;
2601
- }
2602
- return variableValue;
2603
- });
2604
- }
2605
- return value;
2606
- }
2607
- function addVariableDefinition(parsed, attr, parentUrl) {
2608
- let variableList = parsed.variableList;
2609
- if (!variableList) {
2610
- parsed.variableList = variableList = {};
2611
- }
2612
- let NAME;
2613
- let VALUE;
2614
- if ('QUERYPARAM' in attr) {
2615
- NAME = attr.QUERYPARAM;
2616
- try {
2617
- const searchParams = new self.URL(parentUrl).searchParams;
2618
- if (searchParams.has(NAME)) {
2619
- VALUE = searchParams.get(NAME);
2620
- } else {
2621
- throw new Error(`"${NAME}" does not match any query parameter in URI: "${parentUrl}"`);
2622
- }
2623
- } catch (error) {
2624
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`));
2625
- }
2626
- } else {
2627
- NAME = attr.NAME;
2628
- VALUE = attr.VALUE;
2629
- }
2630
- if (NAME in variableList) {
2631
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`));
2632
- } else {
2633
- variableList[NAME] = VALUE || '';
2634
- }
2635
- }
2636
- function importVariableDefinition(parsed, attr, sourceVariableList) {
2637
- const IMPORT = attr.IMPORT;
2638
- if (sourceVariableList && IMPORT in sourceVariableList) {
2639
- let variableList = parsed.variableList;
2640
- if (!variableList) {
2641
- parsed.variableList = variableList = {};
2642
- }
2643
- variableList[IMPORT] = sourceVariableList[IMPORT];
2644
- } else {
2645
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`));
2646
- }
2647
- }
2648
-
2649
2735
  /**
2650
2736
  * MediaSource helper
2651
2737
  */
@@ -2881,10 +2967,7 @@ class M3U8Parser {
2881
2967
  if (result[1]) {
2882
2968
  var _level$unknownCodecs;
2883
2969
  // '#EXT-X-STREAM-INF' is found, parse level tag in group 1
2884
- const attrs = new AttrList(result[1]);
2885
- {
2886
- substituteVariablesInAttributes(parsed, attrs, ['CODECS', 'SUPPLEMENTAL-CODECS', 'ALLOWED-CPC', 'PATHWAY-ID', 'STABLE-VARIANT-ID', 'AUDIO', 'VIDEO', 'SUBTITLES', 'CLOSED-CAPTIONS', 'NAME']);
2887
- }
2970
+ const attrs = new AttrList(result[1], parsed);
2888
2971
  const uri = substituteVariables(parsed, result[2]) ;
2889
2972
  const level = {
2890
2973
  attrs,
@@ -2909,10 +2992,7 @@ class M3U8Parser {
2909
2992
  case 'SESSION-DATA':
2910
2993
  {
2911
2994
  // #EXT-X-SESSION-DATA
2912
- const sessionAttrs = new AttrList(attributes);
2913
- {
2914
- substituteVariablesInAttributes(parsed, sessionAttrs, ['DATA-ID', 'LANGUAGE', 'VALUE', 'URI']);
2915
- }
2995
+ const sessionAttrs = new AttrList(attributes, parsed);
2916
2996
  const dataId = sessionAttrs['DATA-ID'];
2917
2997
  if (dataId) {
2918
2998
  if (parsed.sessionData === null) {
@@ -2940,8 +3020,7 @@ class M3U8Parser {
2940
3020
  {
2941
3021
  // #EXT-X-DEFINE
2942
3022
  {
2943
- const variableAttributes = new AttrList(attributes);
2944
- substituteVariablesInAttributes(parsed, variableAttributes, ['NAME', 'VALUE', 'QUERYPARAM']);
3023
+ const variableAttributes = new AttrList(attributes, parsed);
2945
3024
  addVariableDefinition(parsed, variableAttributes, baseurl);
2946
3025
  }
2947
3026
  break;
@@ -2949,10 +3028,7 @@ class M3U8Parser {
2949
3028
  case 'CONTENT-STEERING':
2950
3029
  {
2951
3030
  // #EXT-X-CONTENT-STEERING
2952
- const contentSteeringAttributes = new AttrList(attributes);
2953
- {
2954
- substituteVariablesInAttributes(parsed, contentSteeringAttributes, ['SERVER-URI', 'PATHWAY-ID']);
2955
- }
3031
+ const contentSteeringAttributes = new AttrList(attributes, parsed);
2956
3032
  parsed.contentSteering = {
2957
3033
  uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl),
2958
3034
  pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.'
@@ -2994,15 +3070,12 @@ class M3U8Parser {
2994
3070
  let id = 0;
2995
3071
  MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
2996
3072
  while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
2997
- const attrs = new AttrList(result[1]);
3073
+ const attrs = new AttrList(result[1], parsed);
2998
3074
  const type = attrs.TYPE;
2999
3075
  if (type) {
3000
3076
  const groups = groupsByType[type];
3001
3077
  const medias = results[type] || [];
3002
3078
  results[type] = medias;
3003
- {
3004
- substituteVariablesInAttributes(parsed, attrs, ['URI', 'GROUP-ID', 'LANGUAGE', 'ASSOC-LANGUAGE', 'STABLE-RENDITION-ID', 'NAME', 'INSTREAM-ID', 'CHARACTERISTICS', 'CHANNELS']);
3005
- }
3006
3079
  const lang = attrs.LANGUAGE;
3007
3080
  const assocLang = attrs['ASSOC-LANGUAGE'];
3008
3081
  const channels = attrs.CHANNELS;
@@ -3049,6 +3122,7 @@ class M3U8Parser {
3049
3122
  static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) {
3050
3123
  const level = new LevelDetails(baseurl);
3051
3124
  const fragments = level.fragments;
3125
+ const programDateTimes = [];
3052
3126
  // The most recent init segment seen (applies to all subsequent segments)
3053
3127
  let currentInitSegment = null;
3054
3128
  let currentSN = 0;
@@ -3107,7 +3181,7 @@ class M3U8Parser {
3107
3181
  // avoid sliced strings https://github.com/video-dev/hls.js/issues/939
3108
3182
  const uri = (' ' + result[3]).slice(1);
3109
3183
  frag.relurl = substituteVariables(level, uri) ;
3110
- assignProgramDateTime(frag, prevFrag);
3184
+ assignProgramDateTime(frag, prevFrag, programDateTimes);
3111
3185
  prevFrag = frag;
3112
3186
  totalduration += frag.duration;
3113
3187
  currentSN++;
@@ -3155,22 +3229,22 @@ class M3U8Parser {
3155
3229
  break;
3156
3230
  case 'SKIP':
3157
3231
  {
3158
- const skipAttrs = new AttrList(value1);
3159
- {
3160
- substituteVariablesInAttributes(level, skipAttrs, ['RECENTLY-REMOVED-DATERANGES']);
3232
+ if (level.skippedSegments) {
3233
+ level.playlistParsingError = new Error(`#EXT-X-SKIP MUST NOT appear more than once in a Playlist`);
3161
3234
  }
3235
+ const skipAttrs = new AttrList(value1, level);
3162
3236
  const skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS');
3163
3237
  if (isFiniteNumber(skippedSegments)) {
3164
- level.skippedSegments = skippedSegments;
3238
+ level.skippedSegments += skippedSegments;
3165
3239
  // This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails`
3166
3240
  for (let _i = skippedSegments; _i--;) {
3167
- fragments.unshift(null);
3241
+ fragments.push(null);
3168
3242
  }
3169
3243
  currentSN += skippedSegments;
3170
3244
  }
3171
3245
  const recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES');
3172
3246
  if (recentlyRemovedDateranges) {
3173
- level.recentlyRemovedDateranges = recentlyRemovedDateranges.split('\t');
3247
+ level.recentlyRemovedDateranges = (level.recentlyRemovedDateranges || []).concat(recentlyRemovedDateranges.split('\t'));
3174
3248
  }
3175
3249
  break;
3176
3250
  }
@@ -3204,12 +3278,9 @@ class M3U8Parser {
3204
3278
  break;
3205
3279
  case 'DATERANGE':
3206
3280
  {
3207
- const dateRangeAttr = new AttrList(value1);
3208
- {
3209
- substituteVariablesInAttributes(level, dateRangeAttr, ['ID', 'CLASS', 'START-DATE', 'END-DATE', 'SCTE35-CMD', 'SCTE35-OUT', 'SCTE35-IN']);
3210
- substituteVariablesInAttributes(level, dateRangeAttr, dateRangeAttr.clientAttrs);
3211
- }
3212
- const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID]);
3281
+ const dateRangeAttr = new AttrList(value1, level);
3282
+ const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID], level.dateRangeTagCount);
3283
+ level.dateRangeTagCount++;
3213
3284
  if (dateRange.isValid || level.skippedSegments) {
3214
3285
  level.dateRanges[dateRange.id] = dateRange;
3215
3286
  } else {
@@ -3222,8 +3293,7 @@ class M3U8Parser {
3222
3293
  case 'DEFINE':
3223
3294
  {
3224
3295
  {
3225
- const variableAttributes = new AttrList(value1);
3226
- substituteVariablesInAttributes(level, variableAttributes, ['NAME', 'VALUE', 'IMPORT', 'QUERYPARAM']);
3296
+ const variableAttributes = new AttrList(value1, level);
3227
3297
  if ('IMPORT' in variableAttributes) {
3228
3298
  importVariableDefinition(level, variableAttributes, multivariantVariableList);
3229
3299
  } else {
@@ -3260,10 +3330,7 @@ class M3U8Parser {
3260
3330
  break;
3261
3331
  case 'MAP':
3262
3332
  {
3263
- const mapAttrs = new AttrList(value1);
3264
- {
3265
- substituteVariablesInAttributes(level, mapAttrs, ['BYTERANGE', 'URI']);
3266
- }
3333
+ const mapAttrs = new AttrList(value1, level);
3267
3334
  if (frag.duration) {
3268
3335
  // Initial segment tag is after segment duration tag.
3269
3336
  // #EXTINF: 6.0
@@ -3289,6 +3356,7 @@ class M3U8Parser {
3289
3356
  currentInitSegment = frag;
3290
3357
  createNextFrag = true;
3291
3358
  }
3359
+ currentInitSegment.cc = discontinuityCounter;
3292
3360
  break;
3293
3361
  }
3294
3362
  case 'SERVER-CONTROL':
@@ -3315,10 +3383,7 @@ class M3U8Parser {
3315
3383
  }
3316
3384
  const previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : undefined;
3317
3385
  const index = currentPart++;
3318
- const partAttrs = new AttrList(value1);
3319
- {
3320
- substituteVariablesInAttributes(level, partAttrs, ['BYTERANGE', 'URI']);
3321
- }
3386
+ const partAttrs = new AttrList(value1, level);
3322
3387
  const part = new Part(partAttrs, frag, baseurl, index, previousFragmentPart);
3323
3388
  partList.push(part);
3324
3389
  frag.duration += part.duration;
@@ -3326,19 +3391,13 @@ class M3U8Parser {
3326
3391
  }
3327
3392
  case 'PRELOAD-HINT':
3328
3393
  {
3329
- const preloadHintAttrs = new AttrList(value1);
3330
- {
3331
- substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']);
3332
- }
3394
+ const preloadHintAttrs = new AttrList(value1, level);
3333
3395
  level.preloadHint = preloadHintAttrs;
3334
3396
  break;
3335
3397
  }
3336
3398
  case 'RENDITION-REPORT':
3337
3399
  {
3338
- const renditionReportAttrs = new AttrList(value1);
3339
- {
3340
- substituteVariablesInAttributes(level, renditionReportAttrs, ['URI']);
3341
- }
3400
+ const renditionReportAttrs = new AttrList(value1, level);
3342
3401
  level.renditionReports = level.renditionReports || [];
3343
3402
  level.renditionReports.push(renditionReportAttrs);
3344
3403
  break;
@@ -3356,7 +3415,7 @@ class M3U8Parser {
3356
3415
  level.fragmentHint = prevFrag;
3357
3416
  }
3358
3417
  } else if (level.partList) {
3359
- assignProgramDateTime(frag, prevFrag);
3418
+ assignProgramDateTime(frag, prevFrag, programDateTimes);
3360
3419
  frag.cc = discontinuityCounter;
3361
3420
  level.fragmentHint = frag;
3362
3421
  if (levelkeys) {
@@ -3377,6 +3436,21 @@ class M3U8Parser {
3377
3436
  if (firstFragment) {
3378
3437
  level.startCC = firstFragment.cc;
3379
3438
  }
3439
+ /**
3440
+ * Backfill any missing PDT values
3441
+ * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
3442
+ * one or more Media Segment URIs, the client SHOULD extrapolate
3443
+ * backward from that tag (using EXTINF durations and/or media
3444
+ * timestamps) to associate dates with those segments."
3445
+ * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
3446
+ * computed.
3447
+ */
3448
+ if (firstPdtIndex > 0) {
3449
+ backfillProgramDateTimes(fragments, firstPdtIndex);
3450
+ if (firstFragment) {
3451
+ programDateTimes.unshift(firstFragment);
3452
+ }
3453
+ }
3380
3454
  } else {
3381
3455
  level.endSN = 0;
3382
3456
  level.startCC = 0;
@@ -3385,30 +3459,62 @@ class M3U8Parser {
3385
3459
  totalduration += level.fragmentHint.duration;
3386
3460
  }
3387
3461
  level.totalduration = totalduration;
3388
- level.endCC = discontinuityCounter;
3389
-
3390
- /**
3391
- * Backfill any missing PDT values
3392
- * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
3393
- * one or more Media Segment URIs, the client SHOULD extrapolate
3394
- * backward from that tag (using EXTINF durations and/or media
3395
- * timestamps) to associate dates with those segments."
3396
- * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
3397
- * computed.
3398
- */
3399
- if (firstPdtIndex > 0) {
3400
- backfillProgramDateTimes(fragments, firstPdtIndex);
3462
+ if (programDateTimes.length && level.dateRangeTagCount && firstFragment) {
3463
+ mapDateRanges(programDateTimes, level);
3401
3464
  }
3465
+ level.endCC = discontinuityCounter;
3402
3466
  return level;
3403
3467
  }
3404
3468
  }
3469
+ function mapDateRanges(programDateTimes, details) {
3470
+ // Make sure DateRanges are mapped to a ProgramDateTime tag that applies a date to a segment that overlaps with its start date
3471
+ const programDateTimeCount = programDateTimes.length;
3472
+ const lastProgramDateTime = programDateTimes[programDateTimeCount - 1];
3473
+ const playlistEnd = details.live ? Infinity : details.totalduration;
3474
+ const dateRangeIds = Object.keys(details.dateRanges);
3475
+ for (let i = dateRangeIds.length; i--;) {
3476
+ const dateRange = details.dateRanges[dateRangeIds[i]];
3477
+ const startDateTime = dateRange.startDate.getTime();
3478
+ dateRange.tagAnchor = lastProgramDateTime;
3479
+ for (let j = programDateTimeCount; j--;) {
3480
+ const fragIndex = findFragmentWithStartDate(details, startDateTime, programDateTimes, j, playlistEnd);
3481
+ if (fragIndex !== -1) {
3482
+ dateRange.tagAnchor = details.fragments[fragIndex];
3483
+ break;
3484
+ }
3485
+ }
3486
+ }
3487
+ }
3488
+ function findFragmentWithStartDate(details, startDateTime, programDateTimes, index, endTime) {
3489
+ const pdtFragment = programDateTimes[index];
3490
+ if (pdtFragment) {
3491
+ var _programDateTimes;
3492
+ // find matching range between PDT tags
3493
+ const durationBetweenPdt = (((_programDateTimes = programDateTimes[index + 1]) == null ? void 0 : _programDateTimes.start) || endTime) - pdtFragment.start;
3494
+ const pdtStart = pdtFragment.programDateTime;
3495
+ if ((startDateTime >= pdtStart || index === 0) && startDateTime <= pdtStart + durationBetweenPdt * 1000) {
3496
+ // map to fragment with date-time range
3497
+ const startIndex = programDateTimes[index].sn - details.startSN;
3498
+ const fragments = details.fragments;
3499
+ if (fragments.length > programDateTimes.length) {
3500
+ const endSegment = programDateTimes[index + 1] || fragments[fragments.length - 1];
3501
+ const endIndex = endSegment.sn - details.startSN;
3502
+ for (let i = endIndex; i > startIndex; i--) {
3503
+ const fragStartDateTime = fragments[i].programDateTime;
3504
+ if (startDateTime >= fragStartDateTime && startDateTime < fragStartDateTime + fragments[i].duration * 1000) {
3505
+ return i;
3506
+ }
3507
+ }
3508
+ }
3509
+ return startIndex;
3510
+ }
3511
+ }
3512
+ return -1;
3513
+ }
3405
3514
  function parseKey(keyTagAttributes, baseurl, parsed) {
3406
3515
  var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT;
3407
3516
  // https://tools.ietf.org/html/rfc8216#section-4.3.2.4
3408
- const keyAttrs = new AttrList(keyTagAttributes);
3409
- {
3410
- substituteVariablesInAttributes(parsed, keyAttrs, ['KEYFORMAT', 'KEYFORMATVERSIONS', 'URI', 'IV', 'URI']);
3411
- }
3517
+ const keyAttrs = new AttrList(keyTagAttributes, parsed);
3412
3518
  const decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : '';
3413
3519
  const decrypturi = keyAttrs.URI;
3414
3520
  const decryptiv = keyAttrs.hexadecimalInteger('IV');
@@ -3463,16 +3569,18 @@ function backfillProgramDateTimes(fragments, firstPdtIndex) {
3463
3569
  fragPrev = frag;
3464
3570
  }
3465
3571
  }
3466
- function assignProgramDateTime(frag, prevFrag) {
3572
+ function assignProgramDateTime(frag, prevFrag, programDateTimes) {
3467
3573
  if (frag.rawProgramDateTime) {
3468
3574
  frag.programDateTime = Date.parse(frag.rawProgramDateTime);
3575
+ if (!isFiniteNumber(frag.programDateTime)) {
3576
+ frag.programDateTime = null;
3577
+ frag.rawProgramDateTime = null;
3578
+ return;
3579
+ }
3580
+ programDateTimes.push(frag);
3469
3581
  } else if (prevFrag != null && prevFrag.programDateTime) {
3470
3582
  frag.programDateTime = prevFrag.endProgramDateTime;
3471
3583
  }
3472
- if (!isFiniteNumber(frag.programDateTime)) {
3473
- frag.programDateTime = null;
3474
- frag.rawProgramDateTime = null;
3475
- }
3476
3584
  }
3477
3585
  function setInitSegment(frag, mapAttrs, id, levelkeys) {
3478
3586
  frag.relurl = mapAttrs.URI;
@@ -4702,9 +4810,6 @@ const MAX_CUE_ENDTIME = (() => {
4702
4810
  }
4703
4811
  return Number.POSITIVE_INFINITY;
4704
4812
  })();
4705
- function dateRangeDateToTimelineSeconds(date, offset) {
4706
- return date.getTime() / 1000 - offset;
4707
- }
4708
4813
  function hexToArrayBuffer(str) {
4709
4814
  return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer;
4710
4815
  }
@@ -4735,6 +4840,7 @@ class ID3TrackController {
4735
4840
  hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
4736
4841
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
4737
4842
  hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
4843
+ hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
4738
4844
  }
4739
4845
  _unregisterListeners() {
4740
4846
  const {
@@ -4746,6 +4852,7 @@ class ID3TrackController {
4746
4852
  hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
4747
4853
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
4748
4854
  hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
4855
+ hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
4749
4856
  }
4750
4857
 
4751
4858
  // Add ID3 metatadata text track.
@@ -4886,6 +4993,14 @@ class ID3TrackController {
4886
4993
  onLevelUpdated(event, {
4887
4994
  details
4888
4995
  }) {
4996
+ this.updateDateRangeCues(details, true);
4997
+ }
4998
+ onLevelPtsUpdated(event, data) {
4999
+ if (Math.abs(data.drift) > 0.01) {
5000
+ this.updateDateRangeCues(data.details);
5001
+ }
5002
+ }
5003
+ updateDateRangeCues(details, removeOldCues) {
4889
5004
  if (!this.media || !details.hasProgramDateTime || !this.hls.config.enableDateRangeMetadataCues) {
4890
5005
  return;
4891
5006
  }
@@ -4898,7 +5013,7 @@ class ID3TrackController {
4898
5013
  } = details;
4899
5014
  const ids = Object.keys(dateRanges);
4900
5015
  // Remove cues from track not found in details.dateRanges
4901
- if (id3Track) {
5016
+ if (id3Track && removeOldCues) {
4902
5017
  const idsToRemove = Object.keys(dateRangeCuesAppended).filter(id => !ids.includes(id));
4903
5018
  for (let i = idsToRemove.length; i--;) {
4904
5019
  const id = idsToRemove[i];
@@ -4916,21 +5031,23 @@ class ID3TrackController {
4916
5031
  if (!this.id3Track) {
4917
5032
  this.id3Track = this.createTrack(this.media);
4918
5033
  }
4919
- const dateTimeOffset = lastFragment.programDateTime / 1000 - lastFragment.start;
4920
5034
  const Cue = getCueClass();
4921
5035
  for (let i = 0; i < ids.length; i++) {
4922
5036
  const id = ids[i];
4923
5037
  const dateRange = dateRanges[id];
4924
- const startTime = dateRangeDateToTimelineSeconds(dateRange.startDate, dateTimeOffset);
5038
+ const startTime = dateRange.startTime;
4925
5039
 
4926
5040
  // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
4927
5041
  const appendedDateRangeCues = dateRangeCuesAppended[id];
4928
5042
  const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {};
4929
5043
  let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false;
4930
5044
  let endTime = MAX_CUE_ENDTIME;
4931
- const endDate = dateRange.endDate;
4932
- if (endDate) {
4933
- endTime = dateRangeDateToTimelineSeconds(endDate, dateTimeOffset);
5045
+ const {
5046
+ duration,
5047
+ endDate
5048
+ } = dateRange;
5049
+ if (endDate && duration !== null) {
5050
+ endTime = startTime + duration;
4934
5051
  durationKnown = true;
4935
5052
  } else if (dateRange.endOnNext && !durationKnown) {
4936
5053
  const nextDateRangeWithSameClass = ids.reduce((candidateDateRange, id) => {
@@ -4943,7 +5060,7 @@ class ID3TrackController {
4943
5060
  return candidateDateRange;
4944
5061
  }, null);
4945
5062
  if (nextDateRangeWithSameClass) {
4946
- endTime = dateRangeDateToTimelineSeconds(nextDateRangeWithSameClass.startDate, dateTimeOffset);
5063
+ endTime = nextDateRangeWithSameClass.startTime;
4947
5064
  durationKnown = true;
4948
5065
  }
4949
5066
  }
@@ -4960,6 +5077,9 @@ class ID3TrackController {
4960
5077
  if (cue) {
4961
5078
  if (durationKnown && !appendedDateRangeCues.durationKnown) {
4962
5079
  cue.endTime = endTime;
5080
+ } else if (Math.abs(cue.startTime - startTime) > 0.01) {
5081
+ cue.startTime = startTime;
5082
+ cue.endTime = endTime;
4963
5083
  }
4964
5084
  } else if (Cue) {
4965
5085
  let data = dateRange.attr[key];
@@ -5451,7 +5571,7 @@ function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS) {
5451
5571
  frag.endPTS = endPTS;
5452
5572
  frag.minEndPTS = minEndPTS;
5453
5573
  frag.endDTS = endDTS;
5454
- const sn = frag.sn; // 'initSegment'
5574
+ const sn = frag.sn;
5455
5575
  // exit if sn out of range
5456
5576
  if (!details || sn < details.startSN || sn > details.endSN) {
5457
5577
  return 0;
@@ -5529,8 +5649,8 @@ function mergeDetails(oldDetails, newDetails) {
5529
5649
  currentInitSegment = oldFrag.initSegment;
5530
5650
  }
5531
5651
  });
5652
+ const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
5532
5653
  if (currentInitSegment) {
5533
- const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
5534
5654
  fragmentsToCheck.forEach(frag => {
5535
5655
  var _currentInitSegment;
5536
5656
  if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
@@ -5547,8 +5667,19 @@ function mergeDetails(oldDetails, newDetails) {
5547
5667
  }
5548
5668
  newDetails.startSN = newDetails.fragments[0].sn;
5549
5669
  newDetails.startCC = newDetails.fragments[0].cc;
5550
- } else if (newDetails.canSkipDateRanges) {
5551
- newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges);
5670
+ } else {
5671
+ if (newDetails.canSkipDateRanges) {
5672
+ newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails);
5673
+ }
5674
+ const programDateTimes = oldDetails.fragments.filter(frag => frag.rawProgramDateTime);
5675
+ if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
5676
+ for (let i = 1; i < fragmentsToCheck.length; i++) {
5677
+ if (fragmentsToCheck[i].programDateTime === null) {
5678
+ assignProgramDateTime(fragmentsToCheck[i], fragmentsToCheck[i - 1], programDateTimes);
5679
+ }
5680
+ }
5681
+ }
5682
+ mapDateRanges(programDateTimes, newDetails);
5552
5683
  }
5553
5684
  }
5554
5685
  const newFragments = newDetails.fragments;
@@ -5597,21 +5728,33 @@ function mergeDetails(oldDetails, newDetails) {
5597
5728
  newDetails.advancedDateTime = oldDetails.advancedDateTime;
5598
5729
  }
5599
5730
  }
5600
- function mergeDateRanges(oldDateRanges, deltaDateRanges, recentlyRemovedDateranges) {
5731
+ function mergeDateRanges(oldDateRanges, newDetails) {
5732
+ const {
5733
+ dateRanges: deltaDateRanges,
5734
+ recentlyRemovedDateranges
5735
+ } = newDetails;
5601
5736
  const dateRanges = _extends({}, oldDateRanges);
5602
5737
  if (recentlyRemovedDateranges) {
5603
5738
  recentlyRemovedDateranges.forEach(id => {
5604
5739
  delete dateRanges[id];
5605
5740
  });
5606
5741
  }
5607
- Object.keys(deltaDateRanges).forEach(id => {
5608
- const dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]);
5609
- if (dateRange.isValid) {
5610
- dateRanges[id] = dateRange;
5611
- } else {
5612
- logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(deltaDateRanges[id].attr)}"`);
5613
- }
5614
- });
5742
+ const mergeIds = Object.keys(dateRanges);
5743
+ const mergeCount = mergeIds.length;
5744
+ if (mergeCount) {
5745
+ Object.keys(deltaDateRanges).forEach(id => {
5746
+ const mergedDateRange = dateRanges[id];
5747
+ const dateRange = new DateRange(deltaDateRanges[id].attr, mergedDateRange);
5748
+ if (dateRange.isValid) {
5749
+ dateRanges[id] = dateRange;
5750
+ if (!mergedDateRange) {
5751
+ dateRange.tagOrder += mergeCount;
5752
+ }
5753
+ } else {
5754
+ logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(deltaDateRanges[id].attr)}"`);
5755
+ }
5756
+ });
5757
+ }
5615
5758
  return dateRanges;
5616
5759
  }
5617
5760
  function mapPartIntersection(oldParts, newParts, intersectionFn) {
@@ -5638,7 +5781,7 @@ function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
5638
5781
  for (let i = start; i <= end; i++) {
5639
5782
  const oldFrag = oldFrags[delta + i];
5640
5783
  let newFrag = newFrags[i];
5641
- if (skippedSegments && !newFrag && i < skippedSegments) {
5784
+ if (skippedSegments && !newFrag && oldFrag) {
5642
5785
  // Fill in skipped segments in delta playlist
5643
5786
  newFrag = newDetails.fragments[i] = oldFrag;
5644
5787
  }
@@ -5688,19 +5831,19 @@ function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) {
5688
5831
  return Math.round(reloadInterval);
5689
5832
  }
5690
5833
  function getFragmentWithSN(level, sn, fragCurrent) {
5691
- if (!(level != null && level.details)) {
5834
+ const details = level == null ? void 0 : level.details;
5835
+ if (!details) {
5692
5836
  return null;
5693
5837
  }
5694
- const levelDetails = level.details;
5695
- let fragment = levelDetails.fragments[sn - levelDetails.startSN];
5838
+ let fragment = details.fragments[sn - details.startSN];
5696
5839
  if (fragment) {
5697
5840
  return fragment;
5698
5841
  }
5699
- fragment = levelDetails.fragmentHint;
5842
+ fragment = details.fragmentHint;
5700
5843
  if (fragment && fragment.sn === sn) {
5701
5844
  return fragment;
5702
5845
  }
5703
- if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) {
5846
+ if (sn < details.startSN && fragCurrent && fragCurrent.sn === sn) {
5704
5847
  return fragCurrent;
5705
5848
  }
5706
5849
  return null;
@@ -8023,13 +8166,10 @@ class FragmentTracker {
8023
8166
  */
8024
8167
  detectPartialFragments(data) {
8025
8168
  const timeRanges = this.timeRanges;
8026
- const {
8027
- frag,
8028
- part
8029
- } = data;
8030
- if (!timeRanges || frag.sn === 'initSegment') {
8169
+ if (!timeRanges || data.frag.sn === 'initSegment') {
8031
8170
  return;
8032
8171
  }
8172
+ const frag = data.frag;
8033
8173
  const fragKey = getFragmentKey(frag);
8034
8174
  const fragmentEntity = this.fragments[fragKey];
8035
8175
  if (!fragmentEntity || fragmentEntity.buffered && frag.gap) {
@@ -8043,7 +8183,7 @@ class FragmentTracker {
8043
8183
  }
8044
8184
  const timeRange = timeRanges[elementaryStream];
8045
8185
  const partial = isFragHint || streamInfo.partial === true;
8046
- fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, part, partial, timeRange);
8186
+ fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, data.part, partial, timeRange);
8047
8187
  });
8048
8188
  fragmentEntity.loaded = null;
8049
8189
  if (Object.keys(fragmentEntity.range).length) {
@@ -8196,18 +8336,14 @@ class FragmentTracker {
8196
8336
  return false;
8197
8337
  }
8198
8338
  onFragLoaded(event, data) {
8199
- const {
8200
- frag,
8201
- part
8202
- } = data;
8203
8339
  // don't track initsegment (for which sn is not a number)
8204
8340
  // don't track frags used for bitrateTest, they're irrelevant.
8205
- if (frag.sn === 'initSegment' || frag.bitrateTest) {
8341
+ if (data.frag.sn === 'initSegment' || data.frag.bitrateTest) {
8206
8342
  return;
8207
8343
  }
8208
-
8344
+ const frag = data.frag;
8209
8345
  // Fragment entity `loaded` FragLoadedData is null when loading parts
8210
- const loaded = part ? null : data;
8346
+ const loaded = data.part ? null : data;
8211
8347
  const fragKey = getFragmentKey(frag);
8212
8348
  this.fragments[fragKey] = {
8213
8349
  body: frag,
@@ -10052,14 +10188,12 @@ class BaseStreamController extends TaskLoop {
10052
10188
  part.stats.parsing.end = now;
10053
10189
  }
10054
10190
  // See if part loading should be disabled/enabled based on buffer and playback position.
10055
- if (frag.sn !== 'initSegment') {
10056
- const levelDetails = this.getLevelDetails();
10057
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
10058
- const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
10059
- if (shouldLoadParts !== this.loadingParts) {
10060
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
10061
- this.loadingParts = shouldLoadParts;
10062
- }
10191
+ const levelDetails = this.getLevelDetails();
10192
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
10193
+ const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
10194
+ if (shouldLoadParts !== this.loadingParts) {
10195
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
10196
+ this.loadingParts = shouldLoadParts;
10063
10197
  }
10064
10198
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
10065
10199
  }
@@ -16957,7 +17091,7 @@ class AudioStreamController extends BaseStreamController {
16957
17091
  baseTime: initPTS,
16958
17092
  timescale
16959
17093
  };
16960
- this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`);
17094
+ this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}/${timescale}`);
16961
17095
  this.videoTrackCC = cc;
16962
17096
  // If we are waiting, tick immediately to unblock audio fragment transmuxing
16963
17097
  if (this.state === State.WAITING_INIT_PTS) {
@@ -17306,8 +17440,8 @@ class AudioStreamController extends BaseStreamController {
17306
17440
  }
17307
17441
  _handleFragmentLoadProgress(data) {
17308
17442
  var _frag$initSegment;
17443
+ const frag = data.frag;
17309
17444
  const {
17310
- frag,
17311
17445
  part,
17312
17446
  payload
17313
17447
  } = data;
@@ -28374,8 +28508,8 @@ class StreamController extends BaseStreamController {
28374
28508
  }
28375
28509
  _handleFragmentLoadProgress(data) {
28376
28510
  var _frag$initSegment;
28511
+ const frag = data.frag;
28377
28512
  const {
28378
- frag,
28379
28513
  part,
28380
28514
  payload
28381
28515
  } = data;
@@ -28736,7 +28870,7 @@ class StreamController extends BaseStreamController {
28736
28870
  }
28737
28871
 
28738
28872
  // Avoid buffering if backtracking this fragment
28739
- if (video && details && frag.sn !== 'initSegment') {
28873
+ if (video && details) {
28740
28874
  const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
28741
28875
  const isFirstFragment = frag.sn === details.startSN;
28742
28876
  const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
@@ -29022,7 +29156,7 @@ class Hls {
29022
29156
  * Get the video-dev/hls.js package version.
29023
29157
  */
29024
29158
  static get version() {
29025
- return "1.5.12-0.canary.10340";
29159
+ return "1.5.12-0.canary.10341";
29026
29160
  }
29027
29161
 
29028
29162
  /**