hls.js 1.6.0-rc.1.0.canary.11073 → 1.6.0-rc.1.0.canary.11076
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.d.mts +1 -0
- package/dist/hls.d.ts +1 -0
- package/dist/hls.js +101 -21
- package/dist/hls.js.d.ts +1 -0
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +101 -21
- 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 +108 -21
- 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 +108 -21
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/package.json +2 -2
- package/src/config.ts +2 -0
- package/src/controller/base-playlist-controller.ts +24 -0
- package/src/loader/m3u8-parser.ts +62 -7
- package/src/loader/playlist-loader.ts +19 -15
- package/src/utils/level-helper.ts +37 -1
@@ -60,7 +60,6 @@ const LEVEL_PLAYLIST_REGEX_FAST = new RegExp(
|
|
60
60
|
|
61
61
|
const LEVEL_PLAYLIST_REGEX_SLOW = new RegExp(
|
62
62
|
[
|
63
|
-
/#(EXTM3U)/.source,
|
64
63
|
/#EXT-X-(PROGRAM-DATE-TIME|BYTERANGE|DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/
|
65
64
|
.source,
|
66
65
|
/#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/
|
@@ -333,13 +332,19 @@ export default class M3U8Parser {
|
|
333
332
|
let firstPdtIndex = -1;
|
334
333
|
let createNextFrag = false;
|
335
334
|
let nextByteRange: string | null = null;
|
335
|
+
let serverControlAttrs: AttrList | undefined;
|
336
336
|
|
337
337
|
LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0;
|
338
338
|
level.m3u8 = string;
|
339
339
|
level.hasVariableRefs = __USE_VARIABLE_SUBSTITUTION__
|
340
340
|
? hasVariableReferences(string)
|
341
341
|
: false;
|
342
|
-
|
342
|
+
if (LEVEL_PLAYLIST_REGEX_FAST.exec(string)?.[0] !== '#EXTM3U') {
|
343
|
+
level.playlistParsingError = new Error(
|
344
|
+
'Missing format identifier #EXTM3U',
|
345
|
+
);
|
346
|
+
return level;
|
347
|
+
}
|
343
348
|
while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) {
|
344
349
|
if (createNextFrag) {
|
345
350
|
createNextFrag = false;
|
@@ -436,16 +441,22 @@ export default class M3U8Parser {
|
|
436
441
|
}
|
437
442
|
break;
|
438
443
|
case 'PLAYLIST-TYPE':
|
444
|
+
if (level.type) {
|
445
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
446
|
+
}
|
439
447
|
level.type = value1.toUpperCase();
|
440
448
|
break;
|
441
449
|
case 'MEDIA-SEQUENCE':
|
450
|
+
if (level.startSN !== 0) {
|
451
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
452
|
+
} else if (fragments.length > 0) {
|
453
|
+
assignMustAppearBeforeSegmentsError(level, tag, result);
|
454
|
+
}
|
442
455
|
currentSN = level.startSN = parseInt(value1);
|
443
456
|
break;
|
444
457
|
case 'SKIP': {
|
445
458
|
if (level.skippedSegments) {
|
446
|
-
level
|
447
|
-
`#EXT-X-SKIP MUST NOT appear more than once in a Playlist`,
|
448
|
-
);
|
459
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
449
460
|
}
|
450
461
|
const skipAttrs = new AttrList(value1, level);
|
451
462
|
const skippedSegments =
|
@@ -469,15 +480,23 @@ export default class M3U8Parser {
|
|
469
480
|
break;
|
470
481
|
}
|
471
482
|
case 'TARGETDURATION':
|
483
|
+
if (level.targetduration !== 0) {
|
484
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
485
|
+
}
|
472
486
|
level.targetduration = Math.max(parseInt(value1), 1);
|
473
487
|
break;
|
474
488
|
case 'VERSION':
|
489
|
+
if (level.version !== null) {
|
490
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
491
|
+
}
|
475
492
|
level.version = parseInt(value1);
|
476
493
|
break;
|
477
494
|
case 'INDEPENDENT-SEGMENTS':
|
478
|
-
case 'EXTM3U':
|
479
495
|
break;
|
480
496
|
case 'ENDLIST':
|
497
|
+
if (!level.live) {
|
498
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
499
|
+
}
|
481
500
|
level.live = false;
|
482
501
|
break;
|
483
502
|
case '#':
|
@@ -536,6 +555,11 @@ export default class M3U8Parser {
|
|
536
555
|
}
|
537
556
|
|
538
557
|
case 'DISCONTINUITY-SEQUENCE':
|
558
|
+
if (level.startCC !== 0) {
|
559
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
560
|
+
} else if (fragments.length > 0) {
|
561
|
+
assignMustAppearBeforeSegmentsError(level, tag, result);
|
562
|
+
}
|
539
563
|
level.startCC = discontinuityCounter = parseInt(value1);
|
540
564
|
break;
|
541
565
|
case 'KEY': {
|
@@ -594,7 +618,10 @@ export default class M3U8Parser {
|
|
594
618
|
break;
|
595
619
|
}
|
596
620
|
case 'SERVER-CONTROL': {
|
597
|
-
|
621
|
+
if (serverControlAttrs) {
|
622
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
623
|
+
}
|
624
|
+
serverControlAttrs = new AttrList(value1);
|
598
625
|
level.canBlockReload = serverControlAttrs.bool('CAN-BLOCK-RELOAD');
|
599
626
|
level.canSkipUntil = serverControlAttrs.optionalFloat(
|
600
627
|
'CAN-SKIP-UNTIL',
|
@@ -611,6 +638,9 @@ export default class M3U8Parser {
|
|
611
638
|
break;
|
612
639
|
}
|
613
640
|
case 'PART-INF': {
|
641
|
+
if (level.partTarget) {
|
642
|
+
assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
|
643
|
+
}
|
614
644
|
const partInfAttrs = new AttrList(value1);
|
615
645
|
level.partTarget = partInfAttrs.decimalFloatingPoint('PART-TARGET');
|
616
646
|
break;
|
@@ -670,6 +700,11 @@ export default class M3U8Parser {
|
|
670
700
|
setFragLevelKeys(frag, levelkeys, level);
|
671
701
|
}
|
672
702
|
}
|
703
|
+
if (!level.targetduration) {
|
704
|
+
level.playlistParsingError = new Error(
|
705
|
+
`#EXT-X-TARGETDURATION is required`,
|
706
|
+
);
|
707
|
+
}
|
673
708
|
const fragmentLength = fragments.length;
|
674
709
|
const firstFragment = fragments[0];
|
675
710
|
const lastFragment = fragments[fragmentLength - 1];
|
@@ -923,3 +958,23 @@ function setFragLevelKeys(
|
|
923
958
|
encryptedFragments.push(frag);
|
924
959
|
}
|
925
960
|
}
|
961
|
+
|
962
|
+
function assignMultipleMediaPlaylistTagOccuranceError(
|
963
|
+
level: LevelDetails,
|
964
|
+
tag: string,
|
965
|
+
result: string[],
|
966
|
+
) {
|
967
|
+
level.playlistParsingError = new Error(
|
968
|
+
`#EXT-X-${tag} must not appear more than once (${result[0]})`,
|
969
|
+
);
|
970
|
+
}
|
971
|
+
|
972
|
+
function assignMustAppearBeforeSegmentsError(
|
973
|
+
level: LevelDetails,
|
974
|
+
tag: string,
|
975
|
+
result: string[],
|
976
|
+
) {
|
977
|
+
level.playlistParsingError = new Error(
|
978
|
+
`#EXT-X-${tag} must appear before the first Media Segment (${result[0]})`,
|
979
|
+
);
|
980
|
+
}
|
@@ -711,21 +711,25 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
711
711
|
}
|
712
712
|
const error = levelDetails.playlistParsingError;
|
713
713
|
if (error) {
|
714
|
-
hls.
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
714
|
+
this.hls.logger.warn(error);
|
715
|
+
if (!hls.config.ignorePlaylistParsingErrors) {
|
716
|
+
hls.trigger(Events.ERROR, {
|
717
|
+
type: ErrorTypes.NETWORK_ERROR,
|
718
|
+
details: ErrorDetails.LEVEL_PARSING_ERROR,
|
719
|
+
fatal: false,
|
720
|
+
url,
|
721
|
+
error,
|
722
|
+
reason: error.message,
|
723
|
+
response,
|
724
|
+
context,
|
725
|
+
level: levelIndex,
|
726
|
+
parent,
|
727
|
+
networkDetails,
|
728
|
+
stats,
|
729
|
+
});
|
730
|
+
return;
|
731
|
+
}
|
732
|
+
levelDetails.playlistParsingError = null;
|
729
733
|
}
|
730
734
|
|
731
735
|
if (levelDetails.live && loader) {
|
@@ -383,7 +383,7 @@ export function mapFragmentIntersection(
|
|
383
383
|
oldDetails: LevelDetails,
|
384
384
|
newDetails: LevelDetails,
|
385
385
|
intersectionFn: FragmentIntersection,
|
386
|
-
)
|
386
|
+
) {
|
387
387
|
const skippedSegments = newDetails.skippedSegments;
|
388
388
|
const start =
|
389
389
|
Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN;
|
@@ -410,10 +410,46 @@ export function mapFragmentIntersection(
|
|
410
410
|
}
|
411
411
|
if (oldFrag && newFrag) {
|
412
412
|
intersectionFn(oldFrag, newFrag, i, newFrags);
|
413
|
+
if (oldFrag.url && oldFrag.url !== newFrag.url) {
|
414
|
+
newDetails.playlistParsingError = getSequenceError(
|
415
|
+
`media sequence mismatch ${newFrag.sn}:`,
|
416
|
+
oldDetails,
|
417
|
+
newDetails,
|
418
|
+
oldFrag,
|
419
|
+
newFrag,
|
420
|
+
);
|
421
|
+
return;
|
422
|
+
} else if (oldFrag.cc !== newFrag.cc) {
|
423
|
+
newDetails.playlistParsingError = getSequenceError(
|
424
|
+
`discontinuity sequence mismatch (${oldFrag.cc}!=${newFrag.cc})`,
|
425
|
+
oldDetails,
|
426
|
+
newDetails,
|
427
|
+
oldFrag,
|
428
|
+
newFrag,
|
429
|
+
);
|
430
|
+
return;
|
431
|
+
}
|
413
432
|
}
|
414
433
|
}
|
415
434
|
}
|
416
435
|
|
436
|
+
function getSequenceError(
|
437
|
+
message: string,
|
438
|
+
oldDetails: LevelDetails,
|
439
|
+
newDetails: LevelDetails,
|
440
|
+
oldFrag: MediaFragment,
|
441
|
+
newFrag: MediaFragment,
|
442
|
+
): Error {
|
443
|
+
return new Error(
|
444
|
+
`${message} ${newFrag.url}
|
445
|
+
Playlist starting @${oldDetails.startSN}
|
446
|
+
${oldDetails.m3u8}
|
447
|
+
|
448
|
+
Playlist starting @${newDetails.startSN}
|
449
|
+
${newDetails.m3u8}`,
|
450
|
+
);
|
451
|
+
}
|
452
|
+
|
417
453
|
export function adjustSliding(
|
418
454
|
oldDetails: LevelDetails,
|
419
455
|
newDetails: LevelDetails,
|