shaka-player 4.9.32 → 4.9.34

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.
Files changed (56) hide show
  1. package/dist/deps.js +6 -5
  2. package/dist/locales.js +36 -36
  3. package/dist/shaka-player.compiled.d.ts +11 -3
  4. package/dist/shaka-player.compiled.debug.d.ts +11 -3
  5. package/dist/shaka-player.compiled.debug.externs.js +8 -5
  6. package/dist/shaka-player.compiled.debug.js +453 -440
  7. package/dist/shaka-player.compiled.debug.map +4 -4
  8. package/dist/shaka-player.compiled.externs.js +8 -5
  9. package/dist/shaka-player.compiled.js +1281 -1271
  10. package/dist/shaka-player.compiled.map +4 -4
  11. package/dist/shaka-player.ui.d.ts +11 -3
  12. package/dist/shaka-player.ui.debug.d.ts +11 -3
  13. package/dist/shaka-player.ui.debug.externs.js +8 -5
  14. package/dist/shaka-player.ui.debug.js +483 -470
  15. package/dist/shaka-player.ui.debug.map +4 -4
  16. package/dist/shaka-player.ui.externs.js +8 -5
  17. package/dist/shaka-player.ui.js +1478 -1468
  18. package/dist/shaka-player.ui.map +4 -4
  19. package/externs/managedmediasource.js +6 -0
  20. package/externs/shaka/codecs.js +21 -0
  21. package/externs/shaka/offline.js +4 -0
  22. package/lib/cast/cast_utils.js +1 -0
  23. package/lib/hls/hls_parser.js +26 -5
  24. package/lib/media/drm_engine.js +78 -18
  25. package/lib/media/media_source_capabilities.js +3 -2
  26. package/lib/media/media_source_engine.js +117 -36
  27. package/lib/media/segment_utils.js +57 -47
  28. package/lib/offline/download_info.js +5 -0
  29. package/lib/offline/download_manager.js +54 -0
  30. package/lib/offline/manifest_converter.js +1 -0
  31. package/lib/offline/storage.js +11 -2
  32. package/lib/player.js +89 -53
  33. package/lib/polyfill/media_capabilities.js +1 -1
  34. package/lib/text/simple_text_displayer.js +9 -1
  35. package/lib/text/ui_text_displayer.js +2 -14
  36. package/lib/text/vtt_text_parser.js +9 -4
  37. package/lib/transmuxer/aac_transmuxer.js +11 -3
  38. package/lib/transmuxer/ac3_transmuxer.js +11 -3
  39. package/lib/transmuxer/ec3_transmuxer.js +11 -3
  40. package/lib/transmuxer/h264.js +143 -70
  41. package/lib/transmuxer/mp3_transmuxer.js +11 -3
  42. package/lib/transmuxer/ts_transmuxer.js +68 -28
  43. package/lib/util/abortable_operation.js +3 -0
  44. package/lib/util/drm_utils.js +20 -0
  45. package/lib/util/functional.js +27 -0
  46. package/lib/util/mime_utils.js +2 -0
  47. package/lib/util/number_utils.js +37 -0
  48. package/lib/util/object_utils.js +83 -0
  49. package/lib/util/platform.js +48 -40
  50. package/lib/util/stream_utils.js +27 -106
  51. package/lib/util/ts_parser.js +111 -85
  52. package/package.json +2 -2
  53. package/ui/controls.js +4 -0
  54. package/ui/fast_forward_button.js +7 -0
  55. package/ui/play_button.js +1 -1
  56. package/ui/rewind_button.js +7 -0
@@ -765,46 +765,6 @@ shaka.util.Platform = class {
765
765
  shaka.log.alwaysWarn('Tizen: Error detecting screen size, default ' +
766
766
  'screen size 1920x1080.');
767
767
  }
768
- } else if (Platform.isXboxOne()) {
769
- maxResolution.width = 1920;
770
- maxResolution.height = 1080;
771
- try {
772
- let winRT = undefined;
773
-
774
- // Try to access to WinRT for WebView, if it's not defined,
775
- // try to access to WinRT for WebView2, if it's not defined either,
776
- // let it throw.
777
- if (typeof Windows !== 'undefined') {
778
- winRT = Windows;
779
- } else {
780
- winRT = chrome.webview.hostObjects.sync.Windows;
781
- }
782
-
783
- const protectionCapabilities =
784
- new winRT.Media.Protection.ProtectionCapabilities();
785
- const protectionResult =
786
- winRT.Media.Protection.ProtectionCapabilityResult;
787
- // isTypeSupported may return "maybe", which means the operation is not
788
- // completed. This means we need to retry
789
- // https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
790
- let result = null;
791
- const type =
792
- 'video/mp4;codecs="hvc1,mp4a";features="decode-res-x=3840,' +
793
- 'decode-res-y=2160,decode-bitrate=20000,decode-fps=30,' +
794
- 'decode-bpc=10,display-res-x=3840,display-res-y=2160,' +
795
- 'display-bpc=8"';
796
- const keySystem = 'com.microsoft.playready.recommendation';
797
- do {
798
- result = protectionCapabilities.isTypeSupported(type, keySystem);
799
- } while (result === protectionResult.maybe);
800
- if (result === protectionResult.probably) {
801
- maxResolution.width = 3840;
802
- maxResolution.height = 2160;
803
- }
804
- } catch (e) {
805
- shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
806
- 'screen size 1920x1080.');
807
- }
808
768
  } else if (Platform.isWebOS()) {
809
769
  try {
810
770
  const deviceInfo =
@@ -851,6 +811,54 @@ shaka.util.Platform = class {
851
811
  maxResolution.width = 1920;
852
812
  maxResolution.height = 1080;
853
813
  }
814
+ } else {
815
+ // For Xbox and UWP apps.
816
+ let winRT = undefined;
817
+ try {
818
+ // Try to access to WinRT for WebView, if it's not defined,
819
+ // try to access to WinRT for WebView2, if it's not defined either,
820
+ // let it throw.
821
+ if (typeof Windows !== 'undefined') {
822
+ winRT = Windows;
823
+ } else {
824
+ winRT = chrome.webview.hostObjects.sync.Windows;
825
+ }
826
+ } catch (e) {}
827
+ if (winRT) {
828
+ maxResolution.width = 1920;
829
+ maxResolution.height = 1080;
830
+ try {
831
+ const protectionCapabilities =
832
+ new winRT.Media.Protection.ProtectionCapabilities();
833
+ const protectionResult =
834
+ winRT.Media.Protection.ProtectionCapabilityResult;
835
+ // isTypeSupported may return "maybe", which means the operation
836
+ // is not completed. This means we need to retry
837
+ // https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
838
+ let result = null;
839
+ const type =
840
+ 'video/mp4;codecs="hvc1,mp4a";features="decode-res-x=3840,' +
841
+ 'decode-res-y=2160,decode-bitrate=20000,decode-fps=30,' +
842
+ 'decode-bpc=10,display-res-x=3840,display-res-y=2160,' +
843
+ 'display-bpc=8"';
844
+ const keySystem = 'com.microsoft.playready.recommendation';
845
+ do {
846
+ result = protectionCapabilities.isTypeSupported(type, keySystem);
847
+ } while (result === protectionResult.maybe);
848
+ if (result === protectionResult.probably) {
849
+ maxResolution.width = 3840;
850
+ maxResolution.height = 2160;
851
+ }
852
+ } catch (e) {
853
+ shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
854
+ 'screen size 1920x1080.');
855
+ }
856
+ } else if (Platform.isXboxOne()) {
857
+ maxResolution.width = 1920;
858
+ maxResolution.height = 1080;
859
+ shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
860
+ 'screen size 1920x1080.');
861
+ }
854
862
  }
855
863
  return maxResolution;
856
864
  }
@@ -444,11 +444,23 @@ shaka.util.StreamUtils = class {
444
444
  * @private
445
445
  */
446
446
  static checkVariantSupported_(variant, keySystem) {
447
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
448
- const Capabilities = shaka.media.Capabilities;
449
- const ManifestParserUtils = shaka.util.ManifestParserUtils;
450
- const MimeUtils = shaka.util.MimeUtils;
451
- const StreamUtils = shaka.util.StreamUtils;
447
+ const variantSupported = variant.decodingInfos.some((decodingInfo) => {
448
+ if (!decodingInfo.supported) {
449
+ return false;
450
+ }
451
+ if (keySystem) {
452
+ const keySystemAccess = decodingInfo.keySystemAccess;
453
+ if (keySystemAccess) {
454
+ if (keySystemAccess.keySystem != keySystem) {
455
+ return false;
456
+ }
457
+ }
458
+ }
459
+ return true;
460
+ });
461
+ if (!variantSupported) {
462
+ return false;
463
+ }
452
464
 
453
465
  const isXboxOne = shaka.util.Platform.isXboxOne();
454
466
  const isFirefoxAndroid = shaka.util.Platform.isFirefox() &&
@@ -466,43 +478,6 @@ shaka.util.StreamUtils = class {
466
478
  return false;
467
479
  }
468
480
 
469
- if (video) {
470
- let videoCodecs = StreamUtils.getCorrectVideoCodecs(video.codecs);
471
- // For multiplexed streams. Here we must check the audio of the
472
- // stream to see if it is compatible.
473
- if (video.codecs.includes(',')) {
474
- const allCodecs = video.codecs.split(',');
475
-
476
- videoCodecs = ManifestParserUtils.guessCodecs(
477
- ContentType.VIDEO, allCodecs);
478
- videoCodecs = StreamUtils.getCorrectVideoCodecs(videoCodecs);
479
- let audioCodecs = ManifestParserUtils.guessCodecs(
480
- ContentType.AUDIO, allCodecs);
481
- audioCodecs = StreamUtils.getCorrectAudioCodecs(
482
- audioCodecs, video.mimeType);
483
-
484
- const audioFullType = MimeUtils.getFullOrConvertedType(
485
- video.mimeType, audioCodecs, ContentType.AUDIO);
486
-
487
- if (!Capabilities.isTypeSupported(audioFullType)) {
488
- return false;
489
- }
490
-
491
- // Update the codec string with the (possibly) converted codecs.
492
- videoCodecs = [videoCodecs, audioCodecs].join(',');
493
- }
494
-
495
- const fullType = MimeUtils.getFullOrConvertedType(
496
- video.mimeType, videoCodecs, ContentType.VIDEO);
497
-
498
- if (!Capabilities.isTypeSupported(fullType)) {
499
- return false;
500
- }
501
-
502
- // Update the codec string with the (possibly) converted codecs.
503
- video.codecs = videoCodecs;
504
- }
505
-
506
481
  const audio = variant.audio;
507
482
 
508
483
  // See: https://github.com/shaka-project/shaka-player/issues/6111
@@ -514,68 +489,7 @@ shaka.util.StreamUtils = class {
514
489
  return false;
515
490
  }
516
491
 
517
- if (audio) {
518
- const codecs = StreamUtils.getCorrectAudioCodecs(
519
- audio.codecs, audio.mimeType);
520
- const fullType = MimeUtils.getFullOrConvertedType(
521
- audio.mimeType, codecs, ContentType.AUDIO);
522
-
523
- if (!Capabilities.isTypeSupported(fullType)) {
524
- return false;
525
- }
526
-
527
- // Update the codec string with the (possibly) converted codecs.
528
- audio.codecs = codecs;
529
- }
530
-
531
- return variant.decodingInfos.some((decodingInfo) => {
532
- if (!decodingInfo.supported) {
533
- return false;
534
- }
535
- if (keySystem) {
536
- const keySystemAccess = decodingInfo.keySystemAccess;
537
- if (keySystemAccess) {
538
- if (keySystemAccess.keySystem != keySystem) {
539
- return false;
540
- }
541
- }
542
- }
543
- return true;
544
- });
545
- }
546
-
547
-
548
- /**
549
- * Constructs a string out of an object, similar to the JSON.stringify method.
550
- * Unlike that method, this guarantees that the order of the keys is
551
- * alphabetical, so it can be used as a way to reliably compare two objects.
552
- *
553
- * @param {!Object} obj
554
- * @return {string}
555
- * @private
556
- */
557
- static alphabeticalKeyOrderStringify_(obj) {
558
- const keys = [];
559
- for (const key in obj) {
560
- keys.push(key);
561
- }
562
- // Alphabetically sort the keys, so they will be in a reliable order.
563
- keys.sort();
564
-
565
- const terms = [];
566
- for (const key of keys) {
567
- const escapedKey = JSON.stringify(key);
568
- const value = obj[key];
569
- if (value instanceof Object) {
570
- const stringifiedValue =
571
- shaka.util.StreamUtils.alphabeticalKeyOrderStringify_(value);
572
- terms.push(escapedKey + ':' + stringifiedValue);
573
- } else {
574
- const escapedValue = JSON.stringify(value);
575
- terms.push(escapedKey + ':' + escapedValue);
576
- }
577
- }
578
- return '{' + terms.join(',') + '}';
492
+ return true;
579
493
  }
580
494
 
581
495
 
@@ -614,7 +528,7 @@ shaka.util.StreamUtils = class {
614
528
  const promises = [];
615
529
  for (const decodingConfig of decodingConfigs) {
616
530
  const cacheKey =
617
- StreamUtils.alphabeticalKeyOrderStringify_(decodingConfig);
531
+ shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(decodingConfig);
618
532
 
619
533
  const cache = StreamUtils.decodingConfigCache_;
620
534
  if (cache[cacheKey]) {
@@ -682,7 +596,14 @@ shaka.util.StreamUtils = class {
682
596
  mimeType, audioCodec);
683
597
  }
684
598
  promises.push(new Promise((resolve, reject) => {
685
- navigator.mediaCapabilities.decodingInfo(copy).then((res) => {
599
+ // On some (Android) WebView environments, decodingInfo will
600
+ // not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
601
+ // is not set. This is a workaround for that issue.
602
+ const TIMEOUT_FOR_DECODING_INFO_IN_SECONDS = 1;
603
+ shaka.util.Functional.promiseWithTimeout(
604
+ TIMEOUT_FOR_DECODING_INFO_IN_SECONDS,
605
+ navigator.mediaCapabilities.decodingInfo(copy),
606
+ ).then((res) => {
686
607
  resolve(res);
687
608
  }).catch(reject);
688
609
  }));
@@ -516,7 +516,11 @@ shaka.util.TsParser = class {
516
516
  shaka.Deprecate.deprecateFeature(5,
517
517
  'TsParser.parseAvcNalus',
518
518
  'Please use parseNalus function instead.');
519
- return this.parseNalus(pes);
519
+ const lastInfo = {
520
+ nalu: null,
521
+ state: null,
522
+ };
523
+ return this.parseNalus(pes, lastInfo);
520
524
  }
521
525
 
522
526
  /**
@@ -526,12 +530,11 @@ shaka.util.TsParser = class {
526
530
  * Credit to https://github.com/video-dev/hls.js/blob/master/src/demux/tsdemuxer.ts
527
531
  *
528
532
  * @param {shaka.extern.MPEG_PES} pes
529
- * @param {?shaka.extern.VideoNalu=} lastNalu
530
- * @param {?number=} lastState
533
+ * @param {{nalu: ?shaka.extern.VideoNalu, state: ?number}} lastInfo
531
534
  * @return {!Array.<shaka.extern.VideoNalu>}
532
535
  * @export
533
536
  */
534
- parseNalus(pes, lastNalu, lastState) {
537
+ parseNalus(pes, lastInfo) {
535
538
  const timescale = shaka.util.TsParser.Timescale;
536
539
  const time = pes.pts ? pes.pts / timescale : null;
537
540
  const data = pes.data;
@@ -547,7 +550,12 @@ shaka.util.TsParser = class {
547
550
  // precede each NALU. A start code is 2 or 3 0x00 bytes followed with a
548
551
  // 0x01 byte. e.g. 0x000001 or 0x00000001.
549
552
  // More info in: https://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream/24890903#24890903
550
- let numZeros = lastState || 0;
553
+ let numZeros = lastInfo.state || 0;
554
+
555
+ const initialNumZeros = numZeros;
556
+
557
+ /** @type {number} */
558
+ let i = 0;
551
559
 
552
560
  /** @type {!Array.<shaka.extern.VideoNalu>} */
553
561
  const nalus = [];
@@ -558,103 +566,119 @@ shaka.util.TsParser = class {
558
566
  // Extracted from the first byte.
559
567
  let lastNaluType = 0;
560
568
 
561
- /** @type {?shaka.extern.VideoNalu} */
562
- let infoOfLastNalu;
569
+ const getNaluType = (offset) => {
570
+ if (this.videoCodec_ == 'hvc') {
571
+ return (data[offset] >> 1) & 0x3f;
572
+ } else {
573
+ return data[offset] & 0x1f;
574
+ }
575
+ };
563
576
 
564
- for (let i = 0; i < len; ++i) {
565
- const value = data[i];
566
- if (!value) {
567
- numZeros++;
568
- } else if (numZeros >= 2 && value == 1) {
569
- if (lastNalu && !nalus.length && lastNaluStart == -1) {
570
- // If we are scanning the next PES, we need append the data to the
571
- // previous Nalu and don't scan for more nalus.
572
- const startCodeSize = numZeros > 3 ? 3 : numZeros;
573
- const lastByteToKeep = i - startCodeSize;
574
- // Optimization
575
- if (lastState && lastByteToKeep != 0) {
576
- const prevData = data.subarray(0, lastByteToKeep);
577
- lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
578
- lastNalu.data, prevData);
579
- lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
580
- lastNalu.fullData, prevData);
581
- }
582
- }
583
- // We just read a start code. Consume the NALU we passed, if any.
584
- if (lastNaluStart >= 0) {
585
- // Because the start position includes the header size.
586
- const firstByteToKeep = lastNaluStart + naluHeaderSize;
577
+ const getLastNalu = () => {
578
+ if (nalus.length) {
579
+ return nalus[nalus.length - 1];
580
+ }
581
+ return lastInfo.nalu;
582
+ };
587
583
 
588
- // Compute the last byte to keep. The start code is at most 3 zeros.
589
- // Any earlier zeros are not part of the start code.
590
- const startCodeSize = (numZeros > 3 ? 3 : numZeros) + 1;
591
- const lastByteToKeep = i - startCodeSize;
584
+ if (numZeros == -1) {
585
+ // special use case where we found 3 or 4-byte start codes exactly at the
586
+ // end of previous PES packet
587
+ lastNaluStart = 0;
588
+ // NALu type is value read from offset 0
589
+ lastNaluType = getNaluType(0);
590
+ numZeros = 0;
591
+ i = 1;
592
+ }
592
593
 
594
+ while (i < len) {
595
+ const value = data[i++];
596
+ // Optimization. numZeros 0 and 1 are the predominant case.
597
+ if (!numZeros) {
598
+ numZeros = value ? 0 : 1;
599
+ continue;
600
+ }
601
+ if (numZeros === 1) {
602
+ numZeros = value ? 0 : 2;
603
+ continue;
604
+ }
605
+ if (!value) {
606
+ numZeros = 3;
607
+ } else if (value == 1) {
608
+ const overflow = i - numZeros - 1;
609
+ if (lastNaluStart >= 0) {
593
610
  /** @type {shaka.extern.VideoNalu} */
594
611
  const nalu = {
595
- // subarray's end position is exclusive, so add one.
596
- data: data.subarray(firstByteToKeep, lastByteToKeep + 1),
597
- fullData: data.subarray(lastNaluStart, lastByteToKeep + 1),
612
+ data: data.subarray(lastNaluStart + naluHeaderSize, overflow),
613
+ fullData: data.subarray(lastNaluStart, overflow),
598
614
  type: lastNaluType,
599
615
  time: time,
616
+ state: null,
600
617
  };
601
618
  nalus.push(nalu);
602
- } else if (lastNalu && !nalus.length) {
603
- const overflow = i - numZeros;
604
- if (overflow > 0) {
605
- const prevData = data.subarray(0, overflow);
606
- lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
607
- lastNalu.data, prevData);
608
- lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
609
- lastNalu.fullData, prevData);
619
+ } else {
620
+ const lastNalu = getLastNalu();
621
+ if (lastNalu) {
622
+ if (initialNumZeros && i <= 4 - initialNumZeros) {
623
+ // Start delimiter overlapping between PES packets
624
+ // strip start delimiter bytes from the end of last NAL unit
625
+ // check if lastNalu had a state different from zero
626
+ if (lastNalu.state) {
627
+ // strip last bytes
628
+ lastNalu.data = lastNalu.data.subarray(
629
+ 0, lastNalu.data.byteLength - initialNumZeros);
630
+ lastNalu.fullData = lastNalu.fullData.subarray(
631
+ 0, lastNalu.fullData.byteLength - initialNumZeros);
632
+ }
633
+ }
634
+ // If NAL units are not starting right at the beginning of the PES
635
+ // packet, push preceding data into previous NAL unit.
636
+ if (overflow > 0) {
637
+ const prevData = data.subarray(0, overflow);
638
+ lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
639
+ lastNalu.data, prevData);
640
+ lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
641
+ lastNalu.fullData, prevData);
642
+ lastNalu.state = 0;
643
+ }
610
644
  }
611
645
  }
612
646
 
613
- // We just read a start code, so there should be another byte here, at
614
- // least, for the NALU type. Check just in case.
615
- if (i >= len - naluHeaderSize) {
616
- shaka.log.warning('Malformed TS, incomplete NALU, ignoring.');
617
- return nalus;
618
- }
619
-
620
- // Advance and read the type of the next NALU.
621
- i++;
622
- lastNaluStart = i;
623
- if (this.videoCodec_ == 'hvc') {
624
- lastNaluType = (data[i] >> 1) & 0x3f;
647
+ // Check if we can read unit type
648
+ if (i < len) {
649
+ lastNaluType = getNaluType(i);
650
+ lastNaluStart = i;
651
+ numZeros = 0;
625
652
  } else {
626
- lastNaluType = data[i] & 0x1f;
653
+ // Not enough byte to read unit type.
654
+ // Let's read it on next PES parsing.
655
+ numZeros = -1;
627
656
  }
628
- numZeros = 0;
629
657
  } else {
630
658
  numZeros = 0;
631
659
  }
632
- // If we have gone through all the data from the PES and we have an
633
- // unfinished Nalu, we will try to use the next PES to complete the
634
- // unfinished Nalu.
635
- if (i >= (len - 1) && lastNaluStart >= 0 && numZeros >= 0) {
636
- // The rest of the buffer was a NALU.
637
- // Because the start position includes the header size.
638
- const firstByteToKeep = lastNaluStart + naluHeaderSize;
639
- infoOfLastNalu = {
640
- data: data.subarray(firstByteToKeep, len),
641
- fullData: data.subarray(lastNaluStart, len),
642
- type: lastNaluType,
643
- time: time,
644
- };
645
- }
646
660
  }
647
661
 
648
- if (infoOfLastNalu) {
649
- nalus.push(infoOfLastNalu);
662
+ if (lastNaluStart >= 0 && numZeros >= 0) {
663
+ const nalu = {
664
+ data: data.subarray(lastNaluStart + naluHeaderSize, len),
665
+ fullData: data.subarray(lastNaluStart, len),
666
+ type: lastNaluType,
667
+ time: time,
668
+ state: numZeros,
669
+ };
670
+ nalus.push(nalu);
650
671
  }
651
- if (!nalus.length && lastNalu) {
652
- lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
653
- lastNalu.data, data);
654
- lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
655
- lastNalu.fullData, data);
672
+ if (!nalus.length && lastInfo.nalu) {
673
+ const lastNalu = getLastNalu();
674
+ if (lastNalu) {
675
+ lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
676
+ lastNalu.data, data);
677
+ lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
678
+ lastNalu.fullData, data);
679
+ }
656
680
  }
657
- lastState = Math.min(3, numZeros);
681
+ lastInfo.state = numZeros;
658
682
  return nalus;
659
683
  }
660
684
 
@@ -758,14 +782,16 @@ shaka.util.TsParser = class {
758
782
  }
759
783
  }
760
784
  if (naluProcessing) {
761
- let lastNalu;
762
- let lastState;
785
+ const lastInfo = {
786
+ nalu: null,
787
+ state: null,
788
+ };
763
789
  const pesWithLength = [];
764
790
  for (const pes of this.videoPes_) {
765
- pes.nalus = this.parseNalus(pes, lastNalu, lastState);
791
+ pes.nalus = this.parseNalus(pes, lastInfo);
766
792
  if (pes.nalus.length) {
767
793
  pesWithLength.push(pes);
768
- lastNalu = pes.nalus[pes.nalus.length - 1];
794
+ lastInfo.nalu = pes.nalus[pes.nalus.length - 1];
769
795
  }
770
796
  }
771
797
  this.videoPes_ = pesWithLength;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.9.32",
4
+ "version": "4.9.34",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -105,7 +105,7 @@
105
105
  "prepublishOnly": "python build/checkversion.py && python build/all.py --force"
106
106
  },
107
107
  "dependencies": {
108
- "eme-encryption-scheme-polyfill": "^2.1.5"
108
+ "eme-encryption-scheme-polyfill": "^2.1.6"
109
109
  },
110
110
  "clean-package": {
111
111
  "remove": [
package/ui/controls.js CHANGED
@@ -841,6 +841,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
841
841
  this.player_.cancelTrickPlay();
842
842
 
843
843
  if (this.presentationIsPaused()) {
844
+ // If we are at the end, go back to the beginning.
845
+ if (this.player_.isEnded()) {
846
+ this.video_.currentTime = this.player_.seekRange().start;
847
+ }
844
848
  this.video_.play();
845
849
  } else {
846
850
  this.video_.pause();
@@ -81,6 +81,13 @@ shaka.ui.FastForwardButton = class extends shaka.ui.Element {
81
81
  // selected. If no more rates are available, the first one is set.
82
82
  const newRate = (newRateIndex != this.fastForwardRates_.length) ?
83
83
  this.fastForwardRates_[newRateIndex] : this.fastForwardRates_[0];
84
+
85
+ if (this.video.paused) {
86
+ // Our fast forward is implemented with playbackRate and needs the video
87
+ // to be playing (to not be paused) to take immediate effect.
88
+ // If the video is paused, "unpause" it.
89
+ this.video.play();
90
+ }
84
91
  this.player.trickPlay(newRate);
85
92
 
86
93
  this.button_.setAttribute('shaka-status', newRate + 'x');
package/ui/play_button.js CHANGED
@@ -116,7 +116,7 @@ shaka.ui.PlayButton = class extends shaka.ui.Element {
116
116
  return false;
117
117
  }
118
118
 
119
- return this.video.ended;
119
+ return this.player ? this.player.isEnded() : true;
120
120
  }
121
121
 
122
122
  /**
@@ -82,6 +82,13 @@ shaka.ui.RewindButton = class extends shaka.ui.Element {
82
82
  // selected. If no more rates are available, the first one is set.
83
83
  const newRate = (newRateIndex != this.rewindRates_.length) ?
84
84
  this.rewindRates_[newRateIndex] : this.rewindRates_[0];
85
+
86
+ if (this.video.paused) {
87
+ // Our fast forward is implemented with playbackRate and needs the video
88
+ // to be playing (to not be paused) to take immediate effect.
89
+ // If the video is paused, "unpause" it.
90
+ this.video.play();
91
+ }
85
92
  this.player.trickPlay(newRate);
86
93
 
87
94
  this.button_.setAttribute('shaka-status', newRate + 'x');