hls.js 1.5.12-0.canary.10340 → 1.5.12-0.canary.10343
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 +2 -2
- 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.light.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.10343"}`);
|
505
424
|
} catch (e) {
|
506
425
|
/* log fn threw an exception. All logger methods are no-ops. */
|
507
426
|
return createLogger();
|
@@ -518,20 +437,169 @@ function enableLogs(debugConfig, context, id) {
|
|
518
437
|
}
|
519
438
|
const logger = exportedLogger;
|
520
439
|
|
440
|
+
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
441
|
+
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
442
|
+
|
443
|
+
// adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js
|
444
|
+
class AttrList {
|
445
|
+
constructor(attrs, parsed) {
|
446
|
+
if (typeof attrs === 'string') {
|
447
|
+
attrs = AttrList.parseAttrList(attrs, parsed);
|
448
|
+
}
|
449
|
+
_extends(this, attrs);
|
450
|
+
}
|
451
|
+
get clientAttrs() {
|
452
|
+
return Object.keys(this).filter(attr => attr.substring(0, 2) === 'X-');
|
453
|
+
}
|
454
|
+
decimalInteger(attrName) {
|
455
|
+
const intValue = parseInt(this[attrName], 10);
|
456
|
+
if (intValue > Number.MAX_SAFE_INTEGER) {
|
457
|
+
return Infinity;
|
458
|
+
}
|
459
|
+
return intValue;
|
460
|
+
}
|
461
|
+
hexadecimalInteger(attrName) {
|
462
|
+
if (this[attrName]) {
|
463
|
+
let stringValue = (this[attrName] || '0x').slice(2);
|
464
|
+
stringValue = (stringValue.length & 1 ? '0' : '') + stringValue;
|
465
|
+
const value = new Uint8Array(stringValue.length / 2);
|
466
|
+
for (let i = 0; i < stringValue.length / 2; i++) {
|
467
|
+
value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
|
468
|
+
}
|
469
|
+
return value;
|
470
|
+
} else {
|
471
|
+
return null;
|
472
|
+
}
|
473
|
+
}
|
474
|
+
hexadecimalIntegerAsNumber(attrName) {
|
475
|
+
const intValue = parseInt(this[attrName], 16);
|
476
|
+
if (intValue > Number.MAX_SAFE_INTEGER) {
|
477
|
+
return Infinity;
|
478
|
+
}
|
479
|
+
return intValue;
|
480
|
+
}
|
481
|
+
decimalFloatingPoint(attrName) {
|
482
|
+
return parseFloat(this[attrName]);
|
483
|
+
}
|
484
|
+
optionalFloat(attrName, defaultValue) {
|
485
|
+
const value = this[attrName];
|
486
|
+
return value ? parseFloat(value) : defaultValue;
|
487
|
+
}
|
488
|
+
enumeratedString(attrName) {
|
489
|
+
return this[attrName];
|
490
|
+
}
|
491
|
+
enumeratedStringList(attrName, dict) {
|
492
|
+
const attrValue = this[attrName];
|
493
|
+
return (attrValue ? attrValue.split(/[ ,]+/) : []).reduce((result, identifier) => {
|
494
|
+
result[identifier.toLowerCase()] = true;
|
495
|
+
return result;
|
496
|
+
}, dict);
|
497
|
+
}
|
498
|
+
bool(attrName) {
|
499
|
+
return this[attrName] === 'YES';
|
500
|
+
}
|
501
|
+
decimalResolution(attrName) {
|
502
|
+
const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
|
503
|
+
if (res === null) {
|
504
|
+
return undefined;
|
505
|
+
}
|
506
|
+
return {
|
507
|
+
width: parseInt(res[1], 10),
|
508
|
+
height: parseInt(res[2], 10)
|
509
|
+
};
|
510
|
+
}
|
511
|
+
static parseAttrList(input, parsed) {
|
512
|
+
let match;
|
513
|
+
const attrs = {};
|
514
|
+
const quote = '"';
|
515
|
+
ATTR_LIST_REGEX.lastIndex = 0;
|
516
|
+
while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
|
517
|
+
const name = match[1].trim();
|
518
|
+
let value = match[2];
|
519
|
+
const quotedString = value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1;
|
520
|
+
let hexadecimalSequence = false;
|
521
|
+
if (quotedString) {
|
522
|
+
value = value.slice(1, -1);
|
523
|
+
} else {
|
524
|
+
switch (name) {
|
525
|
+
case 'IV':
|
526
|
+
case 'SCTE35-CMD':
|
527
|
+
case 'SCTE35-IN':
|
528
|
+
case 'SCTE35-OUT':
|
529
|
+
hexadecimalSequence = true;
|
530
|
+
}
|
531
|
+
}
|
532
|
+
if (parsed && (quotedString || hexadecimalSequence)) ; else if (!hexadecimalSequence && !quotedString) {
|
533
|
+
switch (name) {
|
534
|
+
case 'CLOSED-CAPTIONS':
|
535
|
+
if (value === 'NONE') {
|
536
|
+
break;
|
537
|
+
}
|
538
|
+
// falls through
|
539
|
+
case 'ALLOWED-CPC':
|
540
|
+
case 'CLASS':
|
541
|
+
case 'ASSOC-LANGUAGE':
|
542
|
+
case 'AUDIO':
|
543
|
+
case 'BYTERANGE':
|
544
|
+
case 'CHANNELS':
|
545
|
+
case 'CHARACTERISTICS':
|
546
|
+
case 'CODECS':
|
547
|
+
case 'DATA-ID':
|
548
|
+
case 'END-DATE':
|
549
|
+
case 'GROUP-ID':
|
550
|
+
case 'ID':
|
551
|
+
case 'IMPORT':
|
552
|
+
case 'INSTREAM-ID':
|
553
|
+
case 'KEYFORMAT':
|
554
|
+
case 'KEYFORMATVERSIONS':
|
555
|
+
case 'LANGUAGE':
|
556
|
+
case 'NAME':
|
557
|
+
case 'PATHWAY-ID':
|
558
|
+
case 'QUERYPARAM':
|
559
|
+
case 'RECENTLY-REMOVED-DATERANGES':
|
560
|
+
case 'SERVER-URI':
|
561
|
+
case 'STABLE-RENDITION-ID':
|
562
|
+
case 'STABLE-VARIANT-ID':
|
563
|
+
case 'START-DATE':
|
564
|
+
case 'SUBTITLES':
|
565
|
+
case 'SUPPLEMENTAL-CODECS':
|
566
|
+
case 'URI':
|
567
|
+
case 'VALUE':
|
568
|
+
case 'VIDEO':
|
569
|
+
case 'X-ASSET-LIST':
|
570
|
+
case 'X-ASSET-URI':
|
571
|
+
// Since we are not checking tag:attribute combination, just warn rather than ignoring attribute
|
572
|
+
logger.warn(`${input}: attribute ${name} is missing quotes`);
|
573
|
+
// continue;
|
574
|
+
}
|
575
|
+
}
|
576
|
+
attrs[name] = value;
|
577
|
+
}
|
578
|
+
return attrs;
|
579
|
+
}
|
580
|
+
}
|
581
|
+
|
521
582
|
// Avoid exporting const enum so that these values can be inlined
|
522
583
|
|
584
|
+
const CLASS_INTERSTITIAL = 'com.apple.hls.interstitial';
|
523
585
|
function isDateRangeCueAttribute(attrName) {
|
524
|
-
return attrName !== "ID" && attrName !== "CLASS" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
|
586
|
+
return attrName !== "ID" && attrName !== "CLASS" && attrName !== "CUE" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
|
525
587
|
}
|
526
588
|
function isSCTE35Attribute(attrName) {
|
527
|
-
return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN";
|
589
|
+
return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN" || attrName === "SCTE35-CMD";
|
528
590
|
}
|
529
591
|
class DateRange {
|
530
|
-
constructor(dateRangeAttr, dateRangeWithSameId) {
|
592
|
+
constructor(dateRangeAttr, dateRangeWithSameId, tagCount = 0) {
|
593
|
+
var _dateRangeWithSameId$;
|
531
594
|
this.attr = void 0;
|
595
|
+
this.tagAnchor = void 0;
|
596
|
+
this.tagOrder = void 0;
|
532
597
|
this._startDate = void 0;
|
533
598
|
this._endDate = void 0;
|
599
|
+
this._cue = void 0;
|
534
600
|
this._badValueForSameId = void 0;
|
601
|
+
this.tagAnchor = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagAnchor) || null;
|
602
|
+
this.tagOrder = (_dateRangeWithSameId$ = dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagOrder) != null ? _dateRangeWithSameId$ : tagCount;
|
535
603
|
if (dateRangeWithSameId) {
|
536
604
|
const previousAttr = dateRangeWithSameId.attr;
|
537
605
|
for (const key in previousAttr) {
|
@@ -545,9 +613,9 @@ class DateRange {
|
|
545
613
|
dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr);
|
546
614
|
}
|
547
615
|
this.attr = dateRangeAttr;
|
548
|
-
this._startDate = new Date(dateRangeAttr["START-DATE"]);
|
616
|
+
this._startDate = dateRangeWithSameId ? dateRangeWithSameId.startDate : new Date(dateRangeAttr["START-DATE"]);
|
549
617
|
if ("END-DATE" in this.attr) {
|
550
|
-
const endDate = new Date(this.attr["END-DATE"]);
|
618
|
+
const endDate = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.endDate) || new Date(this.attr["END-DATE"]);
|
551
619
|
if (isFiniteNumber(endDate.getTime())) {
|
552
620
|
this._endDate = endDate;
|
553
621
|
}
|
@@ -559,6 +627,28 @@ class DateRange {
|
|
559
627
|
get class() {
|
560
628
|
return this.attr.CLASS;
|
561
629
|
}
|
630
|
+
get cue() {
|
631
|
+
const _cue = this._cue;
|
632
|
+
if (_cue === undefined) {
|
633
|
+
return this._cue = this.attr.enumeratedStringList(this.attr.CUE ? 'CUE' : 'X-CUE', {
|
634
|
+
pre: false,
|
635
|
+
post: false,
|
636
|
+
once: false
|
637
|
+
});
|
638
|
+
}
|
639
|
+
return _cue;
|
640
|
+
}
|
641
|
+
get startTime() {
|
642
|
+
const {
|
643
|
+
tagAnchor
|
644
|
+
} = this;
|
645
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
646
|
+
if (tagAnchor === null || tagAnchor.programDateTime === null) {
|
647
|
+
logger.warn(`Expected tagAnchor Fragment with PDT set for DateRange "${this.id}": ${tagAnchor}`);
|
648
|
+
return NaN;
|
649
|
+
}
|
650
|
+
return tagAnchor.start + (this.startDate.getTime() - tagAnchor.programDateTime) / 1000;
|
651
|
+
}
|
562
652
|
get startDate() {
|
563
653
|
return this._startDate;
|
564
654
|
}
|
@@ -592,8 +682,11 @@ class DateRange {
|
|
592
682
|
get endOnNext() {
|
593
683
|
return this.attr.bool("END-ON-NEXT");
|
594
684
|
}
|
685
|
+
get isInterstitial() {
|
686
|
+
return this.class === CLASS_INTERSTITIAL;
|
687
|
+
}
|
595
688
|
get isValid() {
|
596
|
-
return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class);
|
689
|
+
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
690
|
}
|
598
691
|
}
|
599
692
|
|
@@ -679,7 +772,6 @@ class BaseSegment {
|
|
679
772
|
this._url = value;
|
680
773
|
}
|
681
774
|
}
|
682
|
-
|
683
775
|
/**
|
684
776
|
* Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
|
685
777
|
*/
|
@@ -892,6 +984,7 @@ class LevelDetails {
|
|
892
984
|
this.fragmentHint = void 0;
|
893
985
|
this.partList = null;
|
894
986
|
this.dateRanges = void 0;
|
987
|
+
this.dateRangeTagCount = 0;
|
895
988
|
this.live = true;
|
896
989
|
this.ageHeader = 0;
|
897
990
|
this.advancedDateTime = void 0;
|
@@ -2485,7 +2578,7 @@ class M3U8Parser {
|
|
2485
2578
|
if (result[1]) {
|
2486
2579
|
var _level$unknownCodecs;
|
2487
2580
|
// '#EXT-X-STREAM-INF' is found, parse level tag in group 1
|
2488
|
-
const attrs = new AttrList(result[1]);
|
2581
|
+
const attrs = new AttrList(result[1], parsed);
|
2489
2582
|
const uri = result[2];
|
2490
2583
|
const level = {
|
2491
2584
|
attrs,
|
@@ -2510,7 +2603,7 @@ class M3U8Parser {
|
|
2510
2603
|
case 'SESSION-DATA':
|
2511
2604
|
{
|
2512
2605
|
// #EXT-X-SESSION-DATA
|
2513
|
-
const sessionAttrs = new AttrList(attributes);
|
2606
|
+
const sessionAttrs = new AttrList(attributes, parsed);
|
2514
2607
|
const dataId = sessionAttrs['DATA-ID'];
|
2515
2608
|
if (dataId) {
|
2516
2609
|
if (parsed.sessionData === null) {
|
@@ -2523,7 +2616,7 @@ class M3U8Parser {
|
|
2523
2616
|
case 'SESSION-KEY':
|
2524
2617
|
{
|
2525
2618
|
// #EXT-X-SESSION-KEY
|
2526
|
-
const sessionKey = parseKey(attributes, baseurl);
|
2619
|
+
const sessionKey = parseKey(attributes, baseurl, parsed);
|
2527
2620
|
if (sessionKey.encrypted && sessionKey.isSupported()) {
|
2528
2621
|
if (parsed.sessionKeys === null) {
|
2529
2622
|
parsed.sessionKeys = [];
|
@@ -2541,7 +2634,7 @@ class M3U8Parser {
|
|
2541
2634
|
case 'CONTENT-STEERING':
|
2542
2635
|
{
|
2543
2636
|
// #EXT-X-CONTENT-STEERING
|
2544
|
-
const contentSteeringAttributes = new AttrList(attributes);
|
2637
|
+
const contentSteeringAttributes = new AttrList(attributes, parsed);
|
2545
2638
|
parsed.contentSteering = {
|
2546
2639
|
uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl),
|
2547
2640
|
pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.'
|
@@ -2583,7 +2676,7 @@ class M3U8Parser {
|
|
2583
2676
|
let id = 0;
|
2584
2677
|
MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
|
2585
2678
|
while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
|
2586
|
-
const attrs = new AttrList(result[1]);
|
2679
|
+
const attrs = new AttrList(result[1], parsed);
|
2587
2680
|
const type = attrs.TYPE;
|
2588
2681
|
if (type) {
|
2589
2682
|
const groups = groupsByType[type];
|
@@ -2635,6 +2728,7 @@ class M3U8Parser {
|
|
2635
2728
|
static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) {
|
2636
2729
|
const level = new LevelDetails(baseurl);
|
2637
2730
|
const fragments = level.fragments;
|
2731
|
+
const programDateTimes = [];
|
2638
2732
|
// The most recent init segment seen (applies to all subsequent segments)
|
2639
2733
|
let currentInitSegment = null;
|
2640
2734
|
let currentSN = 0;
|
@@ -2693,7 +2787,7 @@ class M3U8Parser {
|
|
2693
2787
|
// avoid sliced strings https://github.com/video-dev/hls.js/issues/939
|
2694
2788
|
const uri = (' ' + result[3]).slice(1);
|
2695
2789
|
frag.relurl = uri;
|
2696
|
-
assignProgramDateTime(frag, prevFrag);
|
2790
|
+
assignProgramDateTime(frag, prevFrag, programDateTimes);
|
2697
2791
|
prevFrag = frag;
|
2698
2792
|
totalduration += frag.duration;
|
2699
2793
|
currentSN++;
|
@@ -2741,19 +2835,22 @@ class M3U8Parser {
|
|
2741
2835
|
break;
|
2742
2836
|
case 'SKIP':
|
2743
2837
|
{
|
2744
|
-
|
2838
|
+
if (level.skippedSegments) {
|
2839
|
+
level.playlistParsingError = new Error(`#EXT-X-SKIP MUST NOT appear more than once in a Playlist`);
|
2840
|
+
}
|
2841
|
+
const skipAttrs = new AttrList(value1, level);
|
2745
2842
|
const skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS');
|
2746
2843
|
if (isFiniteNumber(skippedSegments)) {
|
2747
|
-
level.skippedSegments
|
2844
|
+
level.skippedSegments += skippedSegments;
|
2748
2845
|
// This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails`
|
2749
2846
|
for (let _i = skippedSegments; _i--;) {
|
2750
|
-
fragments.
|
2847
|
+
fragments.push(null);
|
2751
2848
|
}
|
2752
2849
|
currentSN += skippedSegments;
|
2753
2850
|
}
|
2754
2851
|
const recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES');
|
2755
2852
|
if (recentlyRemovedDateranges) {
|
2756
|
-
level.recentlyRemovedDateranges = recentlyRemovedDateranges.split('\t');
|
2853
|
+
level.recentlyRemovedDateranges = (level.recentlyRemovedDateranges || []).concat(recentlyRemovedDateranges.split('\t'));
|
2757
2854
|
}
|
2758
2855
|
break;
|
2759
2856
|
}
|
@@ -2787,8 +2884,9 @@ class M3U8Parser {
|
|
2787
2884
|
break;
|
2788
2885
|
case 'DATERANGE':
|
2789
2886
|
{
|
2790
|
-
const dateRangeAttr = new AttrList(value1);
|
2791
|
-
const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID]);
|
2887
|
+
const dateRangeAttr = new AttrList(value1, level);
|
2888
|
+
const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID], level.dateRangeTagCount);
|
2889
|
+
level.dateRangeTagCount++;
|
2792
2890
|
if (dateRange.isValid || level.skippedSegments) {
|
2793
2891
|
level.dateRanges[dateRange.id] = dateRange;
|
2794
2892
|
} else {
|
@@ -2807,7 +2905,7 @@ class M3U8Parser {
|
|
2807
2905
|
break;
|
2808
2906
|
case 'KEY':
|
2809
2907
|
{
|
2810
|
-
const levelKey = parseKey(value1, baseurl);
|
2908
|
+
const levelKey = parseKey(value1, baseurl, level);
|
2811
2909
|
if (levelKey.isSupported()) {
|
2812
2910
|
if (levelKey.method === 'NONE') {
|
2813
2911
|
levelkeys = undefined;
|
@@ -2830,7 +2928,7 @@ class M3U8Parser {
|
|
2830
2928
|
break;
|
2831
2929
|
case 'MAP':
|
2832
2930
|
{
|
2833
|
-
const mapAttrs = new AttrList(value1);
|
2931
|
+
const mapAttrs = new AttrList(value1, level);
|
2834
2932
|
if (frag.duration) {
|
2835
2933
|
// Initial segment tag is after segment duration tag.
|
2836
2934
|
// #EXTINF: 6.0
|
@@ -2856,6 +2954,7 @@ class M3U8Parser {
|
|
2856
2954
|
currentInitSegment = frag;
|
2857
2955
|
createNextFrag = true;
|
2858
2956
|
}
|
2957
|
+
currentInitSegment.cc = discontinuityCounter;
|
2859
2958
|
break;
|
2860
2959
|
}
|
2861
2960
|
case 'SERVER-CONTROL':
|
@@ -2882,7 +2981,7 @@ class M3U8Parser {
|
|
2882
2981
|
}
|
2883
2982
|
const previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : undefined;
|
2884
2983
|
const index = currentPart++;
|
2885
|
-
const partAttrs = new AttrList(value1);
|
2984
|
+
const partAttrs = new AttrList(value1, level);
|
2886
2985
|
const part = new Part(partAttrs, frag, baseurl, index, previousFragmentPart);
|
2887
2986
|
partList.push(part);
|
2888
2987
|
frag.duration += part.duration;
|
@@ -2890,13 +2989,13 @@ class M3U8Parser {
|
|
2890
2989
|
}
|
2891
2990
|
case 'PRELOAD-HINT':
|
2892
2991
|
{
|
2893
|
-
const preloadHintAttrs = new AttrList(value1);
|
2992
|
+
const preloadHintAttrs = new AttrList(value1, level);
|
2894
2993
|
level.preloadHint = preloadHintAttrs;
|
2895
2994
|
break;
|
2896
2995
|
}
|
2897
2996
|
case 'RENDITION-REPORT':
|
2898
2997
|
{
|
2899
|
-
const renditionReportAttrs = new AttrList(value1);
|
2998
|
+
const renditionReportAttrs = new AttrList(value1, level);
|
2900
2999
|
level.renditionReports = level.renditionReports || [];
|
2901
3000
|
level.renditionReports.push(renditionReportAttrs);
|
2902
3001
|
break;
|
@@ -2914,7 +3013,7 @@ class M3U8Parser {
|
|
2914
3013
|
level.fragmentHint = prevFrag;
|
2915
3014
|
}
|
2916
3015
|
} else if (level.partList) {
|
2917
|
-
assignProgramDateTime(frag, prevFrag);
|
3016
|
+
assignProgramDateTime(frag, prevFrag, programDateTimes);
|
2918
3017
|
frag.cc = discontinuityCounter;
|
2919
3018
|
level.fragmentHint = frag;
|
2920
3019
|
if (levelkeys) {
|
@@ -2935,6 +3034,21 @@ class M3U8Parser {
|
|
2935
3034
|
if (firstFragment) {
|
2936
3035
|
level.startCC = firstFragment.cc;
|
2937
3036
|
}
|
3037
|
+
/**
|
3038
|
+
* Backfill any missing PDT values
|
3039
|
+
* "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
|
3040
|
+
* one or more Media Segment URIs, the client SHOULD extrapolate
|
3041
|
+
* backward from that tag (using EXTINF durations and/or media
|
3042
|
+
* timestamps) to associate dates with those segments."
|
3043
|
+
* We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
|
3044
|
+
* computed.
|
3045
|
+
*/
|
3046
|
+
if (firstPdtIndex > 0) {
|
3047
|
+
backfillProgramDateTimes(fragments, firstPdtIndex);
|
3048
|
+
if (firstFragment) {
|
3049
|
+
programDateTimes.unshift(firstFragment);
|
3050
|
+
}
|
3051
|
+
}
|
2938
3052
|
} else {
|
2939
3053
|
level.endSN = 0;
|
2940
3054
|
level.startCC = 0;
|
@@ -2943,27 +3057,62 @@ class M3U8Parser {
|
|
2943
3057
|
totalduration += level.fragmentHint.duration;
|
2944
3058
|
}
|
2945
3059
|
level.totalduration = totalduration;
|
2946
|
-
level.
|
2947
|
-
|
2948
|
-
/**
|
2949
|
-
* Backfill any missing PDT values
|
2950
|
-
* "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
|
2951
|
-
* one or more Media Segment URIs, the client SHOULD extrapolate
|
2952
|
-
* backward from that tag (using EXTINF durations and/or media
|
2953
|
-
* timestamps) to associate dates with those segments."
|
2954
|
-
* We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
|
2955
|
-
* computed.
|
2956
|
-
*/
|
2957
|
-
if (firstPdtIndex > 0) {
|
2958
|
-
backfillProgramDateTimes(fragments, firstPdtIndex);
|
3060
|
+
if (programDateTimes.length && level.dateRangeTagCount && firstFragment) {
|
3061
|
+
mapDateRanges(programDateTimes, level);
|
2959
3062
|
}
|
3063
|
+
level.endCC = discontinuityCounter;
|
2960
3064
|
return level;
|
2961
3065
|
}
|
2962
3066
|
}
|
3067
|
+
function mapDateRanges(programDateTimes, details) {
|
3068
|
+
// Make sure DateRanges are mapped to a ProgramDateTime tag that applies a date to a segment that overlaps with its start date
|
3069
|
+
const programDateTimeCount = programDateTimes.length;
|
3070
|
+
const lastProgramDateTime = programDateTimes[programDateTimeCount - 1];
|
3071
|
+
const playlistEnd = details.live ? Infinity : details.totalduration;
|
3072
|
+
const dateRangeIds = Object.keys(details.dateRanges);
|
3073
|
+
for (let i = dateRangeIds.length; i--;) {
|
3074
|
+
const dateRange = details.dateRanges[dateRangeIds[i]];
|
3075
|
+
const startDateTime = dateRange.startDate.getTime();
|
3076
|
+
dateRange.tagAnchor = lastProgramDateTime;
|
3077
|
+
for (let j = programDateTimeCount; j--;) {
|
3078
|
+
const fragIndex = findFragmentWithStartDate(details, startDateTime, programDateTimes, j, playlistEnd);
|
3079
|
+
if (fragIndex !== -1) {
|
3080
|
+
dateRange.tagAnchor = details.fragments[fragIndex];
|
3081
|
+
break;
|
3082
|
+
}
|
3083
|
+
}
|
3084
|
+
}
|
3085
|
+
}
|
3086
|
+
function findFragmentWithStartDate(details, startDateTime, programDateTimes, index, endTime) {
|
3087
|
+
const pdtFragment = programDateTimes[index];
|
3088
|
+
if (pdtFragment) {
|
3089
|
+
var _programDateTimes;
|
3090
|
+
// find matching range between PDT tags
|
3091
|
+
const durationBetweenPdt = (((_programDateTimes = programDateTimes[index + 1]) == null ? void 0 : _programDateTimes.start) || endTime) - pdtFragment.start;
|
3092
|
+
const pdtStart = pdtFragment.programDateTime;
|
3093
|
+
if ((startDateTime >= pdtStart || index === 0) && startDateTime <= pdtStart + durationBetweenPdt * 1000) {
|
3094
|
+
// map to fragment with date-time range
|
3095
|
+
const startIndex = programDateTimes[index].sn - details.startSN;
|
3096
|
+
const fragments = details.fragments;
|
3097
|
+
if (fragments.length > programDateTimes.length) {
|
3098
|
+
const endSegment = programDateTimes[index + 1] || fragments[fragments.length - 1];
|
3099
|
+
const endIndex = endSegment.sn - details.startSN;
|
3100
|
+
for (let i = endIndex; i > startIndex; i--) {
|
3101
|
+
const fragStartDateTime = fragments[i].programDateTime;
|
3102
|
+
if (startDateTime >= fragStartDateTime && startDateTime < fragStartDateTime + fragments[i].duration * 1000) {
|
3103
|
+
return i;
|
3104
|
+
}
|
3105
|
+
}
|
3106
|
+
}
|
3107
|
+
return startIndex;
|
3108
|
+
}
|
3109
|
+
}
|
3110
|
+
return -1;
|
3111
|
+
}
|
2963
3112
|
function parseKey(keyTagAttributes, baseurl, parsed) {
|
2964
3113
|
var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT;
|
2965
3114
|
// https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
2966
|
-
const keyAttrs = new AttrList(keyTagAttributes);
|
3115
|
+
const keyAttrs = new AttrList(keyTagAttributes, parsed);
|
2967
3116
|
const decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : '';
|
2968
3117
|
const decrypturi = keyAttrs.URI;
|
2969
3118
|
const decryptiv = keyAttrs.hexadecimalInteger('IV');
|
@@ -3018,16 +3167,18 @@ function backfillProgramDateTimes(fragments, firstPdtIndex) {
|
|
3018
3167
|
fragPrev = frag;
|
3019
3168
|
}
|
3020
3169
|
}
|
3021
|
-
function assignProgramDateTime(frag, prevFrag) {
|
3170
|
+
function assignProgramDateTime(frag, prevFrag, programDateTimes) {
|
3022
3171
|
if (frag.rawProgramDateTime) {
|
3023
3172
|
frag.programDateTime = Date.parse(frag.rawProgramDateTime);
|
3173
|
+
if (!isFiniteNumber(frag.programDateTime)) {
|
3174
|
+
frag.programDateTime = null;
|
3175
|
+
frag.rawProgramDateTime = null;
|
3176
|
+
return;
|
3177
|
+
}
|
3178
|
+
programDateTimes.push(frag);
|
3024
3179
|
} else if (prevFrag != null && prevFrag.programDateTime) {
|
3025
3180
|
frag.programDateTime = prevFrag.endProgramDateTime;
|
3026
3181
|
}
|
3027
|
-
if (!isFiniteNumber(frag.programDateTime)) {
|
3028
|
-
frag.programDateTime = null;
|
3029
|
-
frag.rawProgramDateTime = null;
|
3030
|
-
}
|
3031
3182
|
}
|
3032
3183
|
function setInitSegment(frag, mapAttrs, id, levelkeys) {
|
3033
3184
|
frag.relurl = mapAttrs.URI;
|
@@ -4113,9 +4264,6 @@ const MAX_CUE_ENDTIME = (() => {
|
|
4113
4264
|
}
|
4114
4265
|
return Number.POSITIVE_INFINITY;
|
4115
4266
|
})();
|
4116
|
-
function dateRangeDateToTimelineSeconds(date, offset) {
|
4117
|
-
return date.getTime() / 1000 - offset;
|
4118
|
-
}
|
4119
4267
|
function hexToArrayBuffer(str) {
|
4120
4268
|
return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer;
|
4121
4269
|
}
|
@@ -4146,6 +4294,7 @@ class ID3TrackController {
|
|
4146
4294
|
hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
|
4147
4295
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
4148
4296
|
hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
4297
|
+
hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
|
4149
4298
|
}
|
4150
4299
|
_unregisterListeners() {
|
4151
4300
|
const {
|
@@ -4157,6 +4306,7 @@ class ID3TrackController {
|
|
4157
4306
|
hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
|
4158
4307
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
4159
4308
|
hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
4309
|
+
hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
|
4160
4310
|
}
|
4161
4311
|
|
4162
4312
|
// Add ID3 metatadata text track.
|
@@ -4297,6 +4447,14 @@ class ID3TrackController {
|
|
4297
4447
|
onLevelUpdated(event, {
|
4298
4448
|
details
|
4299
4449
|
}) {
|
4450
|
+
this.updateDateRangeCues(details, true);
|
4451
|
+
}
|
4452
|
+
onLevelPtsUpdated(event, data) {
|
4453
|
+
if (Math.abs(data.drift) > 0.01) {
|
4454
|
+
this.updateDateRangeCues(data.details);
|
4455
|
+
}
|
4456
|
+
}
|
4457
|
+
updateDateRangeCues(details, removeOldCues) {
|
4300
4458
|
if (!this.media || !details.hasProgramDateTime || !this.hls.config.enableDateRangeMetadataCues) {
|
4301
4459
|
return;
|
4302
4460
|
}
|
@@ -4309,7 +4467,7 @@ class ID3TrackController {
|
|
4309
4467
|
} = details;
|
4310
4468
|
const ids = Object.keys(dateRanges);
|
4311
4469
|
// Remove cues from track not found in details.dateRanges
|
4312
|
-
if (id3Track) {
|
4470
|
+
if (id3Track && removeOldCues) {
|
4313
4471
|
const idsToRemove = Object.keys(dateRangeCuesAppended).filter(id => !ids.includes(id));
|
4314
4472
|
for (let i = idsToRemove.length; i--;) {
|
4315
4473
|
const id = idsToRemove[i];
|
@@ -4327,21 +4485,23 @@ class ID3TrackController {
|
|
4327
4485
|
if (!this.id3Track) {
|
4328
4486
|
this.id3Track = this.createTrack(this.media);
|
4329
4487
|
}
|
4330
|
-
const dateTimeOffset = lastFragment.programDateTime / 1000 - lastFragment.start;
|
4331
4488
|
const Cue = getCueClass();
|
4332
4489
|
for (let i = 0; i < ids.length; i++) {
|
4333
4490
|
const id = ids[i];
|
4334
4491
|
const dateRange = dateRanges[id];
|
4335
|
-
const startTime =
|
4492
|
+
const startTime = dateRange.startTime;
|
4336
4493
|
|
4337
4494
|
// Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
|
4338
4495
|
const appendedDateRangeCues = dateRangeCuesAppended[id];
|
4339
4496
|
const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {};
|
4340
4497
|
let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false;
|
4341
4498
|
let endTime = MAX_CUE_ENDTIME;
|
4342
|
-
const
|
4343
|
-
|
4344
|
-
|
4499
|
+
const {
|
4500
|
+
duration,
|
4501
|
+
endDate
|
4502
|
+
} = dateRange;
|
4503
|
+
if (endDate && duration !== null) {
|
4504
|
+
endTime = startTime + duration;
|
4345
4505
|
durationKnown = true;
|
4346
4506
|
} else if (dateRange.endOnNext && !durationKnown) {
|
4347
4507
|
const nextDateRangeWithSameClass = ids.reduce((candidateDateRange, id) => {
|
@@ -4354,7 +4514,7 @@ class ID3TrackController {
|
|
4354
4514
|
return candidateDateRange;
|
4355
4515
|
}, null);
|
4356
4516
|
if (nextDateRangeWithSameClass) {
|
4357
|
-
endTime =
|
4517
|
+
endTime = nextDateRangeWithSameClass.startTime;
|
4358
4518
|
durationKnown = true;
|
4359
4519
|
}
|
4360
4520
|
}
|
@@ -4371,6 +4531,9 @@ class ID3TrackController {
|
|
4371
4531
|
if (cue) {
|
4372
4532
|
if (durationKnown && !appendedDateRangeCues.durationKnown) {
|
4373
4533
|
cue.endTime = endTime;
|
4534
|
+
} else if (Math.abs(cue.startTime - startTime) > 0.01) {
|
4535
|
+
cue.startTime = startTime;
|
4536
|
+
cue.endTime = endTime;
|
4374
4537
|
}
|
4375
4538
|
} else if (Cue) {
|
4376
4539
|
let data = dateRange.attr[key];
|
@@ -4862,7 +5025,7 @@ function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS) {
|
|
4862
5025
|
frag.endPTS = endPTS;
|
4863
5026
|
frag.minEndPTS = minEndPTS;
|
4864
5027
|
frag.endDTS = endDTS;
|
4865
|
-
const sn = frag.sn;
|
5028
|
+
const sn = frag.sn;
|
4866
5029
|
// exit if sn out of range
|
4867
5030
|
if (!details || sn < details.startSN || sn > details.endSN) {
|
4868
5031
|
return 0;
|
@@ -4940,8 +5103,8 @@ function mergeDetails(oldDetails, newDetails) {
|
|
4940
5103
|
currentInitSegment = oldFrag.initSegment;
|
4941
5104
|
}
|
4942
5105
|
});
|
5106
|
+
const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
|
4943
5107
|
if (currentInitSegment) {
|
4944
|
-
const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
|
4945
5108
|
fragmentsToCheck.forEach(frag => {
|
4946
5109
|
var _currentInitSegment;
|
4947
5110
|
if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
|
@@ -4958,8 +5121,19 @@ function mergeDetails(oldDetails, newDetails) {
|
|
4958
5121
|
}
|
4959
5122
|
newDetails.startSN = newDetails.fragments[0].sn;
|
4960
5123
|
newDetails.startCC = newDetails.fragments[0].cc;
|
4961
|
-
} else
|
4962
|
-
|
5124
|
+
} else {
|
5125
|
+
if (newDetails.canSkipDateRanges) {
|
5126
|
+
newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails);
|
5127
|
+
}
|
5128
|
+
const programDateTimes = oldDetails.fragments.filter(frag => frag.rawProgramDateTime);
|
5129
|
+
if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
|
5130
|
+
for (let i = 1; i < fragmentsToCheck.length; i++) {
|
5131
|
+
if (fragmentsToCheck[i].programDateTime === null) {
|
5132
|
+
assignProgramDateTime(fragmentsToCheck[i], fragmentsToCheck[i - 1], programDateTimes);
|
5133
|
+
}
|
5134
|
+
}
|
5135
|
+
}
|
5136
|
+
mapDateRanges(programDateTimes, newDetails);
|
4963
5137
|
}
|
4964
5138
|
}
|
4965
5139
|
const newFragments = newDetails.fragments;
|
@@ -5008,21 +5182,33 @@ function mergeDetails(oldDetails, newDetails) {
|
|
5008
5182
|
newDetails.advancedDateTime = oldDetails.advancedDateTime;
|
5009
5183
|
}
|
5010
5184
|
}
|
5011
|
-
function mergeDateRanges(oldDateRanges,
|
5185
|
+
function mergeDateRanges(oldDateRanges, newDetails) {
|
5186
|
+
const {
|
5187
|
+
dateRanges: deltaDateRanges,
|
5188
|
+
recentlyRemovedDateranges
|
5189
|
+
} = newDetails;
|
5012
5190
|
const dateRanges = _extends({}, oldDateRanges);
|
5013
5191
|
if (recentlyRemovedDateranges) {
|
5014
5192
|
recentlyRemovedDateranges.forEach(id => {
|
5015
5193
|
delete dateRanges[id];
|
5016
5194
|
});
|
5017
5195
|
}
|
5018
|
-
Object.keys(
|
5019
|
-
|
5020
|
-
|
5021
|
-
|
5022
|
-
|
5023
|
-
|
5024
|
-
|
5025
|
-
|
5196
|
+
const mergeIds = Object.keys(dateRanges);
|
5197
|
+
const mergeCount = mergeIds.length;
|
5198
|
+
if (mergeCount) {
|
5199
|
+
Object.keys(deltaDateRanges).forEach(id => {
|
5200
|
+
const mergedDateRange = dateRanges[id];
|
5201
|
+
const dateRange = new DateRange(deltaDateRanges[id].attr, mergedDateRange);
|
5202
|
+
if (dateRange.isValid) {
|
5203
|
+
dateRanges[id] = dateRange;
|
5204
|
+
if (!mergedDateRange) {
|
5205
|
+
dateRange.tagOrder += mergeCount;
|
5206
|
+
}
|
5207
|
+
} else {
|
5208
|
+
logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(deltaDateRanges[id].attr)}"`);
|
5209
|
+
}
|
5210
|
+
});
|
5211
|
+
}
|
5026
5212
|
return dateRanges;
|
5027
5213
|
}
|
5028
5214
|
function mapPartIntersection(oldParts, newParts, intersectionFn) {
|
@@ -5049,7 +5235,7 @@ function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
|
|
5049
5235
|
for (let i = start; i <= end; i++) {
|
5050
5236
|
const oldFrag = oldFrags[delta + i];
|
5051
5237
|
let newFrag = newFrags[i];
|
5052
|
-
if (skippedSegments && !newFrag &&
|
5238
|
+
if (skippedSegments && !newFrag && oldFrag) {
|
5053
5239
|
// Fill in skipped segments in delta playlist
|
5054
5240
|
newFrag = newDetails.fragments[i] = oldFrag;
|
5055
5241
|
}
|
@@ -5099,19 +5285,19 @@ function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) {
|
|
5099
5285
|
return Math.round(reloadInterval);
|
5100
5286
|
}
|
5101
5287
|
function getFragmentWithSN(level, sn, fragCurrent) {
|
5102
|
-
|
5288
|
+
const details = level == null ? void 0 : level.details;
|
5289
|
+
if (!details) {
|
5103
5290
|
return null;
|
5104
5291
|
}
|
5105
|
-
|
5106
|
-
let fragment = levelDetails.fragments[sn - levelDetails.startSN];
|
5292
|
+
let fragment = details.fragments[sn - details.startSN];
|
5107
5293
|
if (fragment) {
|
5108
5294
|
return fragment;
|
5109
5295
|
}
|
5110
|
-
fragment =
|
5296
|
+
fragment = details.fragmentHint;
|
5111
5297
|
if (fragment && fragment.sn === sn) {
|
5112
5298
|
return fragment;
|
5113
5299
|
}
|
5114
|
-
if (sn <
|
5300
|
+
if (sn < details.startSN && fragCurrent && fragCurrent.sn === sn) {
|
5115
5301
|
return fragCurrent;
|
5116
5302
|
}
|
5117
5303
|
return null;
|
@@ -10588,13 +10774,10 @@ class FragmentTracker {
|
|
10588
10774
|
*/
|
10589
10775
|
detectPartialFragments(data) {
|
10590
10776
|
const timeRanges = this.timeRanges;
|
10591
|
-
|
10592
|
-
frag,
|
10593
|
-
part
|
10594
|
-
} = data;
|
10595
|
-
if (!timeRanges || frag.sn === 'initSegment') {
|
10777
|
+
if (!timeRanges || data.frag.sn === 'initSegment') {
|
10596
10778
|
return;
|
10597
10779
|
}
|
10780
|
+
const frag = data.frag;
|
10598
10781
|
const fragKey = getFragmentKey(frag);
|
10599
10782
|
const fragmentEntity = this.fragments[fragKey];
|
10600
10783
|
if (!fragmentEntity || fragmentEntity.buffered && frag.gap) {
|
@@ -10608,7 +10791,7 @@ class FragmentTracker {
|
|
10608
10791
|
}
|
10609
10792
|
const timeRange = timeRanges[elementaryStream];
|
10610
10793
|
const partial = isFragHint || streamInfo.partial === true;
|
10611
|
-
fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, part, partial, timeRange);
|
10794
|
+
fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, data.part, partial, timeRange);
|
10612
10795
|
});
|
10613
10796
|
fragmentEntity.loaded = null;
|
10614
10797
|
if (Object.keys(fragmentEntity.range).length) {
|
@@ -10761,18 +10944,14 @@ class FragmentTracker {
|
|
10761
10944
|
return false;
|
10762
10945
|
}
|
10763
10946
|
onFragLoaded(event, data) {
|
10764
|
-
const {
|
10765
|
-
frag,
|
10766
|
-
part
|
10767
|
-
} = data;
|
10768
10947
|
// don't track initsegment (for which sn is not a number)
|
10769
10948
|
// don't track frags used for bitrateTest, they're irrelevant.
|
10770
|
-
if (frag.sn === 'initSegment' || frag.bitrateTest) {
|
10949
|
+
if (data.frag.sn === 'initSegment' || data.frag.bitrateTest) {
|
10771
10950
|
return;
|
10772
10951
|
}
|
10773
|
-
|
10952
|
+
const frag = data.frag;
|
10774
10953
|
// Fragment entity `loaded` FragLoadedData is null when loading parts
|
10775
|
-
const loaded = part ? null : data;
|
10954
|
+
const loaded = data.part ? null : data;
|
10776
10955
|
const fragKey = getFragmentKey(frag);
|
10777
10956
|
this.fragments[fragKey] = {
|
10778
10957
|
body: frag,
|
@@ -12857,14 +13036,12 @@ class BaseStreamController extends TaskLoop {
|
|
12857
13036
|
part.stats.parsing.end = now;
|
12858
13037
|
}
|
12859
13038
|
// See if part loading should be disabled/enabled based on buffer and playback position.
|
12860
|
-
|
12861
|
-
|
12862
|
-
|
12863
|
-
|
12864
|
-
|
12865
|
-
|
12866
|
-
this.loadingParts = shouldLoadParts;
|
12867
|
-
}
|
13039
|
+
const levelDetails = this.getLevelDetails();
|
13040
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
13041
|
+
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
13042
|
+
if (shouldLoadParts !== this.loadingParts) {
|
13043
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
13044
|
+
this.loadingParts = shouldLoadParts;
|
12868
13045
|
}
|
12869
13046
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
12870
13047
|
}
|
@@ -19738,8 +19915,8 @@ class StreamController extends BaseStreamController {
|
|
19738
19915
|
}
|
19739
19916
|
_handleFragmentLoadProgress(data) {
|
19740
19917
|
var _frag$initSegment;
|
19918
|
+
const frag = data.frag;
|
19741
19919
|
const {
|
19742
|
-
frag,
|
19743
19920
|
part,
|
19744
19921
|
payload
|
19745
19922
|
} = data;
|
@@ -20100,7 +20277,7 @@ class StreamController extends BaseStreamController {
|
|
20100
20277
|
}
|
20101
20278
|
|
20102
20279
|
// Avoid buffering if backtracking this fragment
|
20103
|
-
if (video && details
|
20280
|
+
if (video && details) {
|
20104
20281
|
const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
|
20105
20282
|
const isFirstFragment = frag.sn === details.startSN;
|
20106
20283
|
const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
|
@@ -20386,7 +20563,7 @@ class Hls {
|
|
20386
20563
|
* Get the video-dev/hls.js package version.
|
20387
20564
|
*/
|
20388
20565
|
static get version() {
|
20389
|
-
return "1.5.12-0.canary.
|
20566
|
+
return "1.5.12-0.canary.10343";
|
20390
20567
|
}
|
20391
20568
|
|
20392
20569
|
/**
|