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.js +431 -288
- package/dist/hls.js.d.ts +49 -16
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +364 -178
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +356 -179
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +422 -288
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/controller/audio-stream-controller.ts +13 -5
- package/src/controller/base-stream-controller.ts +28 -26
- package/src/controller/fragment-finders.ts +12 -14
- package/src/controller/fragment-tracker.ts +14 -14
- package/src/controller/id3-track-controller.ts +38 -28
- package/src/controller/stream-controller.ts +9 -4
- package/src/controller/subtitle-stream-controller.ts +3 -3
- package/src/demux/transmuxer-interface.ts +4 -4
- package/src/hls.ts +3 -1
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment.ts +6 -2
- package/src/loader/level-details.ts +7 -6
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/types/events.ts +2 -5
- package/src/types/fragment-tracker.ts +2 -2
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/level-helper.ts +68 -40
- package/src/utils/variable-substitution.ts +0 -19
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.
|
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
|
-
|
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
|
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.
|
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
|
-
|
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.
|
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 =
|
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
|
4932
|
-
|
4933
|
-
|
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 =
|
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;
|
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
|
5551
|
-
|
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,
|
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(
|
5608
|
-
|
5609
|
-
|
5610
|
-
|
5611
|
-
|
5612
|
-
|
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 &&
|
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
|
-
|
5834
|
+
const details = level == null ? void 0 : level.details;
|
5835
|
+
if (!details) {
|
5692
5836
|
return null;
|
5693
5837
|
}
|
5694
|
-
|
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 =
|
5842
|
+
fragment = details.fragmentHint;
|
5700
5843
|
if (fragment && fragment.sn === sn) {
|
5701
5844
|
return fragment;
|
5702
5845
|
}
|
5703
|
-
if (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
|
-
|
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
|
-
|
10056
|
-
|
10057
|
-
|
10058
|
-
|
10059
|
-
|
10060
|
-
|
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
|
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.
|
29159
|
+
return "1.5.12-0.canary.10341";
|
29026
29160
|
}
|
29027
29161
|
|
29028
29162
|
/**
|