hls.js 1.6.0-rc.1.0.canary.11071 → 1.6.0-rc.1.0.canary.11074

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.
@@ -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.playlistParsingError = new Error(
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
- const serverControlAttrs = new AttrList(value1);
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.trigger(Events.ERROR, {
715
- type: ErrorTypes.NETWORK_ERROR,
716
- details: ErrorDetails.LEVEL_PARSING_ERROR,
717
- fatal: false,
718
- url,
719
- error,
720
- reason: error.message,
721
- response,
722
- context,
723
- level: levelIndex,
724
- parent,
725
- networkDetails,
726
- stats,
727
- });
728
- return;
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
- ): void {
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,