shaka-player 3.3.5 → 4.0.2

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 (184) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/demo-version-index.yaml +8 -0
  3. package/.github/workflows/release-please.yaml +18 -2
  4. package/.github/workflows/sync-labels.yaml +33 -0
  5. package/.github/workflows/update-issues.yaml +29 -0
  6. package/.github/workflows/validate-pr-title.yaml +35 -0
  7. package/AUTHORS +1 -0
  8. package/CHANGELOG.md +215 -41
  9. package/CONTRIBUTORS +1 -0
  10. package/README.md +14 -11
  11. package/app-engine/demo-version-index/README.md +3 -3
  12. package/app-engine/demo-version-index/app.yaml +27 -1
  13. package/app-engine/demo-version-index/generate.py +164 -0
  14. package/app-engine/demo-version-index/main.py +4 -155
  15. package/app-engine/demo-version-index/requirements.txt +0 -1
  16. package/build/checkversion.py +1 -1
  17. package/build/compiler.py +1 -1
  18. package/build/misspellings.txt +1 -0
  19. package/build/shaka-lab.yaml +0 -2
  20. package/build/types/polyfill +0 -3
  21. package/demo/common/asset.js +17 -1
  22. package/demo/common/assets.js +29 -0
  23. package/demo/common/message_ids.js +6 -3
  24. package/demo/config.js +10 -11
  25. package/demo/custom.js +39 -0
  26. package/demo/demo.less +0 -1
  27. package/demo/demo_utils.js +0 -1
  28. package/demo/locales/en.json +6 -3
  29. package/demo/locales/source.json +22 -10
  30. package/demo/main.js +9 -6
  31. package/demo/search.js +2 -1
  32. package/dist/controls.css +3 -3
  33. package/dist/controls.css.map +1 -1
  34. package/dist/demo.compiled.debug.js +71 -64
  35. package/dist/demo.compiled.debug.map +3 -3
  36. package/dist/demo.compiled.js +71 -64
  37. package/dist/demo.compiled.map +3 -3
  38. package/dist/demo.css +1 -1
  39. package/dist/demo.css.map +1 -1
  40. package/dist/deps.js +17 -20
  41. package/dist/locales.js +33 -31
  42. package/dist/receiver.compiled.debug.js +32 -28
  43. package/dist/receiver.compiled.debug.map +3 -3
  44. package/dist/receiver.compiled.js +32 -28
  45. package/dist/receiver.compiled.map +3 -3
  46. package/dist/shaka-player.compiled.d.ts +311 -314
  47. package/dist/shaka-player.compiled.debug.d.ts +311 -314
  48. package/dist/shaka-player.compiled.debug.externs.js +126 -126
  49. package/dist/shaka-player.compiled.debug.js +430 -484
  50. package/dist/shaka-player.compiled.debug.map +4 -4
  51. package/dist/shaka-player.compiled.externs.js +126 -126
  52. package/dist/shaka-player.compiled.js +829 -845
  53. package/dist/shaka-player.compiled.map +4 -4
  54. package/dist/shaka-player.ui.d.ts +357 -358
  55. package/dist/shaka-player.ui.debug.d.ts +357 -358
  56. package/dist/shaka-player.ui.debug.externs.js +134 -126
  57. package/dist/shaka-player.ui.debug.js +489 -541
  58. package/dist/shaka-player.ui.debug.map +4 -4
  59. package/dist/shaka-player.ui.externs.js +134 -126
  60. package/dist/shaka-player.ui.js +988 -1000
  61. package/dist/shaka-player.ui.map +4 -4
  62. package/docs/tutorials/config.md +0 -2
  63. package/docs/tutorials/drm-config.md +5 -0
  64. package/docs/tutorials/fairplay.md +7 -22
  65. package/docs/tutorials/faq.md +0 -11
  66. package/docs/tutorials/index.json +2 -8
  67. package/docs/tutorials/plugins.md +1 -3
  68. package/docs/tutorials/upgrade.md +85 -11
  69. package/externs/polyfill.js +15 -0
  70. package/externs/shaka/manifest.js +5 -1
  71. package/externs/shaka/net.js +12 -0
  72. package/externs/shaka/offline.js +5 -1
  73. package/externs/shaka/player.js +39 -21
  74. package/externs/shaka/text.js +12 -12
  75. package/externs/sourcebuffer.js +15 -0
  76. package/lib/abr/simple_abr_manager.js +2 -1
  77. package/lib/ads/client_side_ad.js +13 -2
  78. package/lib/ads/client_side_ad_manager.js +22 -4
  79. package/lib/cast/cast_utils.js +0 -1
  80. package/lib/dash/content_protection.js +85 -5
  81. package/lib/dash/dash_parser.js +1 -0
  82. package/lib/hls/hls_parser.js +390 -747
  83. package/lib/hls/manifest_text_parser.js +2 -3
  84. package/lib/media/drm_engine.js +105 -37
  85. package/lib/media/gap_jumping_controller.js +10 -45
  86. package/lib/media/media_source_engine.js +150 -30
  87. package/lib/media/playhead.js +2 -5
  88. package/lib/media/presentation_timeline.js +1 -2
  89. package/lib/media/segment_index.js +13 -46
  90. package/lib/media/segment_reference.js +47 -1
  91. package/lib/media/streaming_engine.js +28 -11
  92. package/lib/media/time_ranges_utils.js +4 -9
  93. package/lib/net/networking_engine.js +3 -0
  94. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  95. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  96. package/lib/offline/manifest_converter.js +1 -0
  97. package/lib/offline/storage.js +8 -41
  98. package/lib/player.js +17 -201
  99. package/lib/polyfill/media_capabilities.js +12 -6
  100. package/lib/polyfill/mediasource.js +0 -28
  101. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  102. package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
  103. package/lib/text/cue.js +0 -6
  104. package/lib/text/lrc_text_parser.js +8 -0
  105. package/lib/text/mp4_ttml_parser.js +8 -0
  106. package/lib/text/mp4_vtt_parser.js +8 -0
  107. package/lib/text/sbv_text_parser.js +8 -0
  108. package/lib/text/simple_text_displayer.js +1 -7
  109. package/lib/text/srt_text_parser.js +8 -0
  110. package/lib/text/ssa_text_parser.js +8 -0
  111. package/lib/text/text_engine.js +19 -3
  112. package/lib/text/ttml_text_parser.js +8 -0
  113. package/lib/text/ui_text_displayer.js +1 -7
  114. package/lib/text/vtt_text_parser.js +27 -5
  115. package/lib/text/web_vtt_generator.js +1 -7
  116. package/lib/util/error.js +9 -13
  117. package/lib/util/fairplay_utils.js +109 -0
  118. package/lib/util/functional.js +0 -42
  119. package/lib/util/mime_utils.js +16 -5
  120. package/lib/util/platform.js +45 -0
  121. package/lib/util/player_configuration.js +17 -6
  122. package/lib/util/pssh.js +23 -4
  123. package/lib/util/stream_utils.js +92 -22
  124. package/lib/util/uint8array_utils.js +0 -16
  125. package/lib/util/xml_utils.js +1 -1
  126. package/package.json +5 -2
  127. package/roadmap.md +19 -19
  128. package/shaka-player.uncompiled.js +0 -3
  129. package/test/dash/dash_parser_content_protection_unit.js +44 -0
  130. package/test/dash/dash_parser_segment_template_unit.js +3 -4
  131. package/test/demo/demo_unit.js +2 -1
  132. package/test/hls/hls_live_unit.js +155 -230
  133. package/test/hls/hls_parser_unit.js +515 -432
  134. package/test/media/drm_engine_unit.js +86 -120
  135. package/test/media/media_source_engine_integration.js +25 -3
  136. package/test/media/media_source_engine_unit.js +3 -2
  137. package/test/media/playhead_unit.js +27 -191
  138. package/test/media/segment_index_unit.js +15 -25
  139. package/test/media/streaming_engine_integration.js +2 -39
  140. package/test/media/streaming_engine_unit.js +3 -2
  141. package/test/media/time_ranges_utils_unit.js +0 -5
  142. package/test/offline/manifest_convert_unit.js +34 -4
  143. package/test/offline/offline_manifest_parser_unit.js +1 -0
  144. package/test/offline/storage_integration.js +5 -4
  145. package/test/player_external.js +0 -6
  146. package/test/player_integration.js +16 -0
  147. package/test/player_unit.js +26 -0
  148. package/test/test/util/fake_media_source_engine.js +11 -4
  149. package/test/test/util/manifest_generator.js +3 -1
  150. package/test/test/util/manifest_parser_util.js +4 -1
  151. package/test/test/util/offline_utils.js +1 -0
  152. package/test/test/util/streaming_engine_util.js +1 -0
  153. package/test/test/util/test_scheme.js +24 -1
  154. package/test/test/util/util.js +4 -4
  155. package/test/text/cue_integration.js +2 -2
  156. package/test/text/lrc_text_parser_unit.js +6 -7
  157. package/test/text/mp4_ttml_parser_unit.js +6 -4
  158. package/test/text/mp4_vtt_parser_unit.js +6 -5
  159. package/test/text/sbv_text_parser_unit.js +5 -6
  160. package/test/text/srt_text_parser_unit.js +5 -6
  161. package/test/text/ssa_text_parser_unit.js +17 -29
  162. package/test/text/text_engine_unit.js +45 -5
  163. package/test/text/ttml_text_parser_unit.js +77 -77
  164. package/test/text/vtt_text_parser_unit.js +116 -59
  165. package/test/ui/ui_integration.js +0 -16
  166. package/test/ui/ui_unit.js +27 -0
  167. package/test/util/cmcd_manager_unit.js +3 -0
  168. package/test/util/stream_utils_unit.js +23 -7
  169. package/ui/controls.js +65 -40
  170. package/ui/fullscreen_button.js +26 -6
  171. package/ui/less/containers.less +4 -4
  172. package/ui/less/general.less +1 -1
  173. package/ui/less/range_elements.less +1 -0
  174. package/ui/locales/en.json +1 -0
  175. package/ui/locales/source.json +5 -0
  176. package/ui/resolution_selection.js +31 -33
  177. package/ui/ui.js +0 -17
  178. package/.github/pull_request_template.md +0 -10
  179. package/docs/tutorials/upgrade-v2-5.md +0 -441
  180. package/docs/tutorials/upgrade-v3.md +0 -38
  181. package/lib/polyfill/mathround.js +0 -49
  182. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  183. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  184. package/test/util/functional_unit.js +0 -82
@@ -6,7 +6,6 @@
6
6
 
7
7
  describe('HlsParser', () => {
8
8
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
9
- const ManifestParser = shaka.test.ManifestParser;
10
9
  const TextStreamKind = shaka.util.ManifestParserUtils.TextStreamKind;
11
10
  const Util = shaka.test.Util;
12
11
  const originalAlwaysWarn = shaka.log.alwaysWarn;
@@ -156,6 +155,7 @@ describe('HlsParser', () => {
156
155
  ].join('');
157
156
 
158
157
  const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
158
+ manifest.sequenceMode = true;
159
159
  manifest.anyTimeline();
160
160
  manifest.addPartialVariant((variant) => {
161
161
  variant.language = 'en';
@@ -230,6 +230,7 @@ describe('HlsParser', () => {
230
230
  ].join('');
231
231
 
232
232
  const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
233
+ manifest.sequenceMode = true;
233
234
  manifest.anyTimeline();
234
235
  manifest.addPartialVariant((variant) => {
235
236
  variant.language = 'en';
@@ -300,6 +301,7 @@ describe('HlsParser', () => {
300
301
  stream.size(960, 540);
301
302
  });
302
303
  });
304
+ manifest.sequenceMode = true;
303
305
  });
304
306
 
305
307
  fakeNetEngine
@@ -336,6 +338,7 @@ describe('HlsParser', () => {
336
338
  stream.mime('video/mp4', 'avc1.4d001e');
337
339
  });
338
340
  });
341
+ manifest.sequenceMode = true;
339
342
  });
340
343
 
341
344
  await testHlsParser(master, media, manifest);
@@ -365,6 +368,7 @@ describe('HlsParser', () => {
365
368
  stream.mime('video/mp4', 'avc1');
366
369
  });
367
370
  });
371
+ manifest.sequenceMode = true;
368
372
  });
369
373
 
370
374
  await testHlsParser(master, media, manifest);
@@ -393,6 +397,7 @@ describe('HlsParser', () => {
393
397
  stream.mime('video/mp4', 'avc1');
394
398
  });
395
399
  });
400
+ manifest.sequenceMode = true;
396
401
  });
397
402
 
398
403
  await testHlsParser(master, media, manifest);
@@ -424,6 +429,7 @@ describe('HlsParser', () => {
424
429
  stream.mime('video/mp4', 'avc1');
425
430
  });
426
431
  });
432
+ manifest.sequenceMode = true;
427
433
  });
428
434
 
429
435
  await testHlsParser(master, media, manifest);
@@ -452,6 +458,7 @@ describe('HlsParser', () => {
452
458
  stream.mime('audio/mp4', 'mp4a');
453
459
  });
454
460
  });
461
+ manifest.sequenceMode = true;
455
462
  });
456
463
 
457
464
  await testHlsParser(master, media, manifest);
@@ -486,6 +493,7 @@ describe('HlsParser', () => {
486
493
  stream.mime('audio/mp4', 'mp4a');
487
494
  });
488
495
  });
496
+ manifest.sequenceMode = true;
489
497
  });
490
498
 
491
499
  await testHlsParser(master, media, manifest);
@@ -520,6 +528,35 @@ describe('HlsParser', () => {
520
528
  stream.mime('audio/mp4', '');
521
529
  });
522
530
  });
531
+ manifest.sequenceMode = true;
532
+ });
533
+
534
+ await testHlsParser(master, media, manifest);
535
+ });
536
+
537
+ it('accepts containerless streams', async () => {
538
+ const master = [
539
+ '#EXTM3U\n',
540
+ '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=41457,CODECS="mp4a.40.2"\n',
541
+ 'audio\n',
542
+ ].join('');
543
+
544
+ const media = [
545
+ '#EXTM3U\n',
546
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
547
+ '#EXTINF:5,\n',
548
+ '#EXT-X-BYTERANGE:121090@616\n',
549
+ 'main.aac',
550
+ ].join('');
551
+
552
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
553
+ manifest.anyTimeline();
554
+ manifest.addPartialVariant((variant) => {
555
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
556
+ stream.mime('audio/aac', '');
557
+ });
558
+ });
559
+ manifest.sequenceMode = true;
523
560
  });
524
561
 
525
562
  await testHlsParser(master, media, manifest);
@@ -558,6 +595,7 @@ describe('HlsParser', () => {
558
595
  stream.mime('audio/mp4', 'mp4a');
559
596
  });
560
597
  });
598
+ manifest.sequenceMode = true;
561
599
  });
562
600
 
563
601
  await testHlsParser(master, media, manifest);
@@ -594,6 +632,7 @@ describe('HlsParser', () => {
594
632
  stream.mime('audio/mp4', 'mp4a');
595
633
  });
596
634
  });
635
+ manifest.sequenceMode = true;
597
636
  });
598
637
 
599
638
  await testHlsParser(master, media, manifest);
@@ -624,66 +663,12 @@ describe('HlsParser', () => {
624
663
  stream.mime('audio/mp4', 'mp4a');
625
664
  });
626
665
  });
666
+ manifest.sequenceMode = true;
627
667
  });
628
668
 
629
669
  await testHlsParser(master, media, manifest);
630
670
  });
631
671
 
632
- it('sets seek range correctly for non-zero start', async () => {
633
- const master = [
634
- '#EXTM3U\n',
635
- '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
636
- 'RESOLUTION=960x540,FRAME-RATE=60\n',
637
- 'video',
638
- ].join('');
639
-
640
- const media = [
641
- '#EXTM3U\n',
642
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
643
- '#EXT-X-MEDIA-SEQUENCE:131\n',
644
- '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
645
- '#EXTINF:5,\n',
646
- '#EXT-X-BYTERANGE:121090@616\n',
647
- 'main.mp4',
648
- ].join('');
649
-
650
- segmentData = new Uint8Array([
651
- 0x00, 0x00, 0x00, 0x24, // size (36)
652
- 0x6D, 0x6F, 0x6F, 0x66, // type (moof)
653
- 0x00, 0x00, 0x00, 0x1C, // traf size (28)
654
- 0x74, 0x72, 0x61, 0x66, // type (traf)
655
-
656
- 0x00, 0x00, 0x00, 0x14, // tfdt size (20)
657
- 0x74, 0x66, 0x64, 0x74, // type (tfdt)
658
- 0x01, 0x00, 0x00, 0x00, // version and flags
659
-
660
- 0x00, 0x00, 0x00, 0x00, // baseMediaDecodeTime first 4 bytes (0)
661
- 0x00, 0x0A, 0x00, 0x00, // baseMediaDecodeTime last 4 bytes (655360)
662
- ]);
663
-
664
- fakeNetEngine
665
- .setResponseText('test:/master', master)
666
- .setResponseText('test:/video', media)
667
- .setResponseValue('test:/init.mp4', initSegmentData)
668
- .setResponseValue('test:/main.mp4', segmentData);
669
-
670
- const manifest = await parser.start('test:/master', playerInterface);
671
- const presentationTimeline = manifest.presentationTimeline;
672
- const stream = manifest.variants[0].video;
673
- await stream.createSegmentIndex();
674
- goog.asserts.assert(stream.segmentIndex != null, 'Null segmentIndex!');
675
-
676
- const ref = Array.from(stream.segmentIndex)[0];
677
- expect(ref).not.toBe(null);
678
- if (ref) {
679
- expect(ref.startTime).toBe(0);
680
- // baseMediaDecodeTime (655360) / timescale (1000)
681
- expect(ref.timestampOffset).toBe(-655.36);
682
- }
683
- expect(presentationTimeline.getSeekRangeStart()).toBe(0);
684
- expect(presentationTimeline.getSeekRangeEnd()).toBe(5);
685
- });
686
-
687
672
  it('parses multiplexed variant', async () => {
688
673
  const master = [
689
674
  '#EXTM3U\n',
@@ -708,6 +693,7 @@ describe('HlsParser', () => {
708
693
  stream.mime('video/mp4', 'avc1,mp4a');
709
694
  });
710
695
  });
696
+ manifest.sequenceMode = true;
711
697
  });
712
698
 
713
699
  await testHlsParser(master, media, manifest);
@@ -737,6 +723,7 @@ describe('HlsParser', () => {
737
723
  stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
738
724
  });
739
725
  });
726
+ manifest.sequenceMode = true;
740
727
  });
741
728
 
742
729
  await testHlsParser(master, media, manifest);
@@ -771,6 +758,7 @@ describe('HlsParser', () => {
771
758
  stream.mime('audio/mp4', /** @type {?} */ (jasmine.any(String)));
772
759
  });
773
760
  });
761
+ manifest.sequenceMode = true;
774
762
  });
775
763
 
776
764
  await testHlsParser(master, media, manifest);
@@ -801,6 +789,7 @@ describe('HlsParser', () => {
801
789
  stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
802
790
  });
803
791
  });
792
+ manifest.sequenceMode = true;
804
793
  });
805
794
 
806
795
  await testHlsParser(master, media, manifest);
@@ -831,6 +820,7 @@ describe('HlsParser', () => {
831
820
  stream.mime('audio/mp4', /** @type {?} */ (jasmine.any(String)));
832
821
  });
833
822
  });
823
+ manifest.sequenceMode = true;
834
824
  });
835
825
 
836
826
  await testHlsParser(master, media, manifest);
@@ -882,6 +872,7 @@ describe('HlsParser', () => {
882
872
  stream.language = 'fr';
883
873
  });
884
874
  });
875
+ manifest.sequenceMode = true;
885
876
  });
886
877
 
887
878
  await testHlsParser(master, media, manifest);
@@ -924,6 +915,7 @@ describe('HlsParser', () => {
924
915
  stream.language = 'fr';
925
916
  });
926
917
  });
918
+ manifest.sequenceMode = true;
927
919
  });
928
920
 
929
921
  await testHlsParser(master, media, manifest);
@@ -971,63 +963,12 @@ describe('HlsParser', () => {
971
963
  ];
972
964
  });
973
965
  });
966
+ manifest.sequenceMode = true;
974
967
  });
975
968
 
976
969
  await testHlsParser(master, media, manifest);
977
970
  });
978
971
 
979
- it('fetch the start time for one audio/video stream and reuse for the others',
980
- async () => {
981
- const SEGMENT = shaka.net.NetworkingEngine.RequestType.SEGMENT;
982
- const master = [
983
- '#EXTM3U\n',
984
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
985
- 'CHANNELS="2",URI="audio"\n',
986
- '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
987
- 'URI="text"\n',
988
- '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
989
- 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
990
- 'video\n',
991
- ].join('');
992
-
993
- const media = [
994
- '#EXTM3U\n',
995
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
996
- '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
997
- '#EXTINF:5,\n',
998
- '#EXT-X-BYTERANGE:121090@616\n',
999
- 'main.mp4',
1000
- ].join('');
1001
-
1002
- const textMedia = [
1003
- '#EXTM3U\n',
1004
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
1005
- '#EXTINF:5,\n',
1006
- '#EXT-X-BYTERANGE:121090@616\n',
1007
- 'main.vtt',
1008
- ].join('');
1009
-
1010
- fakeNetEngine
1011
- .setResponseText('test:/master', master)
1012
- .setResponseText('test:/audio', media)
1013
- .setResponseText('test:/video', media)
1014
- .setResponseText('test:/text', textMedia)
1015
- .setResponseText('test:/main.vtt', vttText)
1016
- .setResponseValue('test:/init.mp4', initSegmentData)
1017
- .setResponseValue('test:/main.mp4', segmentData);
1018
-
1019
- await parser.start('test:/master', playerInterface);
1020
- // The start time of audio should be fetched first, and then video and
1021
- // text streams should reuse the start time from audio.
1022
- // Thus, there should be 2 segment requests, for fetching audio init
1023
- // and main segments, and not for video and text segments.
1024
- expect(fakeNetEngine.request.calls.allArgs().filter((args) => {
1025
- return args[0] == SEGMENT;
1026
- }).length).toBe(2);
1027
- fakeNetEngine.expectRequest('test:/init.mp4', SEGMENT);
1028
- fakeNetEngine.expectRequest('test:/main.mp4', SEGMENT);
1029
- });
1030
-
1031
972
  it('gets mime type from header request', async () => {
1032
973
  const master = [
1033
974
  '#EXTM3U\n',
@@ -1052,6 +993,7 @@ describe('HlsParser', () => {
1052
993
  stream.mime('video/mp4', 'avc1');
1053
994
  });
1054
995
  });
996
+ manifest.sequenceMode = true;
1055
997
  });
1056
998
 
1057
999
  // The extra parameters should be stripped by the parser.
@@ -1109,6 +1051,7 @@ describe('HlsParser', () => {
1109
1051
  stream.kind = TextStreamKind.SUBTITLE;
1110
1052
  stream.mime('text/vtt', '');
1111
1053
  });
1054
+ manifest.sequenceMode = true;
1112
1055
  });
1113
1056
 
1114
1057
  fakeNetEngine
@@ -1178,6 +1121,7 @@ describe('HlsParser', () => {
1178
1121
  stream.kind = TextStreamKind.SUBTITLE;
1179
1122
  stream.mime('text/vtt', '');
1180
1123
  });
1124
+ manifest.sequenceMode = true;
1181
1125
  });
1182
1126
 
1183
1127
  fakeNetEngine
@@ -1194,6 +1138,69 @@ describe('HlsParser', () => {
1194
1138
  expect(actual).toEqual(manifest);
1195
1139
  });
1196
1140
 
1141
+ it('gets mime type of SUBTITLES from header request', async () => {
1142
+ const master = [
1143
+ '#EXTM3U\n',
1144
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1145
+ 'URI="audio"\n',
1146
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1147
+ 'URI="text"\n',
1148
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1149
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1150
+ 'video\n',
1151
+ ].join('');
1152
+
1153
+ const media = [
1154
+ '#EXTM3U\n',
1155
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1156
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1157
+ '#EXTINF:5,\n',
1158
+ '#EXT-X-BYTERANGE:121090@616\n',
1159
+ 'main.mp4',
1160
+ ].join('');
1161
+
1162
+ const textMedia = [
1163
+ '#EXTM3U\n',
1164
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1165
+ '#EXTINF:5,\n',
1166
+ '#EXT-X-BYTERANGE:121090@616\n',
1167
+ 'main.subs',
1168
+ ].join('');
1169
+
1170
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1171
+ manifest.anyTimeline();
1172
+ manifest.addPartialVariant((variant) => {
1173
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
1174
+ stream.mime('video/mp4', 'avc1');
1175
+ });
1176
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
1177
+ stream.mime('audio/mp4', 'mp4a');
1178
+ });
1179
+ });
1180
+ manifest.addPartialTextStream((stream) => {
1181
+ stream.language = 'en';
1182
+ stream.kind = TextStreamKind.SUBTITLE;
1183
+ stream.mime('application/mp4', '');
1184
+ });
1185
+ manifest.sequenceMode = true;
1186
+ });
1187
+
1188
+ fakeNetEngine
1189
+ .setResponseText('test:/master', master)
1190
+ .setResponseText('test:/audio', media)
1191
+ .setResponseText('test:/video', media)
1192
+ .setResponseText('test:/text', textMedia)
1193
+ .setResponseText('test:/main.subs', vttText)
1194
+ .setHeaders('test:/main.subs', {
1195
+ 'content-type': 'application/mp4; foo=bar',
1196
+ })
1197
+ .setResponseValue('test:/init.mp4', initSegmentData)
1198
+ .setResponseValue('test:/main.mp4', segmentData);
1199
+
1200
+ const actual = await parser.start('test:/master', playerInterface);
1201
+ expect(actual).toEqual(manifest);
1202
+ });
1203
+
1197
1204
  it('parses manifest with FORCED SUBTITLES', async () => {
1198
1205
  const master = [
1199
1206
  '#EXTM3U\n',
@@ -1239,6 +1246,7 @@ describe('HlsParser', () => {
1239
1246
  stream.kind = TextStreamKind.SUBTITLE;
1240
1247
  stream.mime('text/vtt', '');
1241
1248
  });
1249
+ manifest.sequenceMode = true;
1242
1250
  });
1243
1251
 
1244
1252
  fakeNetEngine
@@ -1300,6 +1308,7 @@ describe('HlsParser', () => {
1300
1308
  stream.kind = TextStreamKind.SUBTITLE;
1301
1309
  stream.mime('text/vtt', '');
1302
1310
  });
1311
+ manifest.sequenceMode = true;
1303
1312
  });
1304
1313
 
1305
1314
  fakeNetEngine
@@ -1478,6 +1487,105 @@ describe('HlsParser', () => {
1478
1487
  }
1479
1488
  });
1480
1489
 
1490
+ it('parse EXT-X-GAP', async () => {
1491
+ const master = [
1492
+ '#EXTM3U\n',
1493
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1494
+ 'CHANNELS="2",URI="audio"\n',
1495
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1496
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
1497
+ 'video\n',
1498
+ ].join('');
1499
+
1500
+ const video = [
1501
+ '#EXTM3U\n',
1502
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1503
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1504
+ '#EXTINF:5,\n',
1505
+ 'main.mp4\n',
1506
+ '#EXTINF:5,\n',
1507
+ '#EXT-X-GAP\n',
1508
+ 'main.mp4\n',
1509
+ '#EXTINF:5,\n',
1510
+ 'main.mp4\n',
1511
+ ].join('');
1512
+
1513
+ const audio = [
1514
+ '#EXTM3U\n',
1515
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1516
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1517
+ '#EXTINF:5,\n',
1518
+ 'main.mp4\n',
1519
+ '#EXTINF:5,\n',
1520
+ '#EXT-X-GAP\n',
1521
+ 'main.mp4\n',
1522
+ '#EXTINF:5,\n',
1523
+ 'main.mp4\n',
1524
+ ].join('');
1525
+
1526
+ fakeNetEngine
1527
+ .setResponseText('test:/master', master)
1528
+ .setResponseText('test:/audio', audio)
1529
+ .setResponseText('test:/video', video)
1530
+ .setResponseValue('test:/init.mp4', initSegmentData)
1531
+ .setResponseValue('test:/main.mp4', segmentData);
1532
+
1533
+ const actual = await parser.start('test:/master', playerInterface);
1534
+
1535
+ expect(actual.variants.length).toBe(1);
1536
+
1537
+ const variant = actual.variants[0];
1538
+ expect(variant.video).toBeTruthy();
1539
+ expect(variant.audio).toBeTruthy();
1540
+
1541
+ const available = shaka.media.SegmentReference.Status.AVAILABLE;
1542
+ const missing = shaka.media.SegmentReference.Status.MISSING;
1543
+
1544
+ await variant.video.createSegmentIndex();
1545
+ goog.asserts.assert(variant.video.segmentIndex != null,
1546
+ 'Null segmentIndex!');
1547
+
1548
+ const firstVideoReference = variant.video.segmentIndex.get(0);
1549
+ const secondVideoReference = variant.video.segmentIndex.get(1);
1550
+ const thirdVideoReference = variant.video.segmentIndex.get(2);
1551
+
1552
+ expect(firstVideoReference).not.toBe(null);
1553
+ expect(secondVideoReference).not.toBe(null);
1554
+ expect(thirdVideoReference).not.toBe(null);
1555
+
1556
+ if (firstVideoReference) {
1557
+ expect(firstVideoReference.getStatus()).toBe(available);
1558
+ }
1559
+ if (secondVideoReference) {
1560
+ expect(secondVideoReference.getStatus()).toBe(missing);
1561
+ }
1562
+ if (thirdVideoReference) {
1563
+ expect(thirdVideoReference.getStatus()).toBe(available);
1564
+ }
1565
+
1566
+ await variant.audio.createSegmentIndex();
1567
+ goog.asserts.assert(variant.audio.segmentIndex != null,
1568
+ 'Null segmentIndex!');
1569
+
1570
+ const firstAudioReference = variant.audio.segmentIndex.get(0);
1571
+ const secondAudioReference = variant.audio.segmentIndex.get(1);
1572
+ const thirdAudioReference = variant.audio.segmentIndex.get(2);
1573
+
1574
+ expect(firstAudioReference).not.toBe(null);
1575
+ expect(secondAudioReference).not.toBe(null);
1576
+ expect(thirdAudioReference).not.toBe(null);
1577
+
1578
+ if (firstAudioReference) {
1579
+ expect(firstAudioReference.getStatus()).toBe(available);
1580
+ }
1581
+ if (secondAudioReference) {
1582
+ expect(secondAudioReference.getStatus()).toBe(missing);
1583
+ }
1584
+ if (thirdAudioReference) {
1585
+ expect(thirdAudioReference.getStatus()).toBe(available);
1586
+ }
1587
+ });
1588
+
1481
1589
  it('Disable audio does not create audio streams', async () => {
1482
1590
  const master = [
1483
1591
  '#EXTM3U\n',
@@ -1804,6 +1912,7 @@ describe('HlsParser', () => {
1804
1912
  stream.language = 'en';
1805
1913
  stream.mime('application/mp4', 'stpp.ttml.im1t');
1806
1914
  });
1915
+ manifest.sequenceMode = true;
1807
1916
  });
1808
1917
 
1809
1918
  fakeNetEngine
@@ -1853,6 +1962,7 @@ describe('HlsParser', () => {
1853
1962
  manifest.addPartialTextStream((stream) => {
1854
1963
  stream.mime('text/vtt', 'vtt');
1855
1964
  });
1965
+ manifest.sequenceMode = true;
1856
1966
  });
1857
1967
 
1858
1968
  fakeNetEngine
@@ -1895,6 +2005,7 @@ describe('HlsParser', () => {
1895
2005
  manifest.addPartialTextStream((stream) => {
1896
2006
  stream.kind = TextStreamKind.SUBTITLE;
1897
2007
  });
2008
+ manifest.sequenceMode = true;
1898
2009
  });
1899
2010
 
1900
2011
  fakeNetEngine
@@ -1909,6 +2020,173 @@ describe('HlsParser', () => {
1909
2020
  expect(actual).toEqual(manifest);
1910
2021
  });
1911
2022
 
2023
+ describe('produces syncTime', () => {
2024
+ /**
2025
+ * @param {number} startTime
2026
+ * @param {number} endTime
2027
+ * @param {number} syncTime
2028
+ * @return {!shaka.media.SegmentReference}
2029
+ */
2030
+ function makeReference(startTime, endTime, syncTime) {
2031
+ const initUris = () => ['test:/init.mp4'];
2032
+ const init = new shaka.media.InitSegmentReference(initUris, 0, 615);
2033
+ const uris = () => ['test:/main.mp4'];
2034
+ return new shaka.media.SegmentReference(
2035
+ startTime, endTime, uris, 0, null, init, 0, 0, Infinity,
2036
+ [], undefined, undefined, syncTime);
2037
+ }
2038
+
2039
+ /**
2040
+ * @param {string} media
2041
+ * @param {!Array.<number>} startTimes
2042
+ * @param {(function(!shaka.media.SegmentReference))=} modifyFn
2043
+ */
2044
+ async function test(media, startTimes, modifyFn) {
2045
+ const master = [
2046
+ '#EXTM3U\n',
2047
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
2048
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2049
+ 'video\n',
2050
+ ].join('');
2051
+
2052
+ const segments = [];
2053
+ for (let i = 0; i < startTimes.length - 1; i++) {
2054
+ const startTime = startTimes[i];
2055
+ const endTime = startTimes[i + 1];
2056
+ const reference = makeReference(startTime, endTime, startTime);
2057
+ if (modifyFn) {
2058
+ modifyFn(reference);
2059
+ }
2060
+ segments.push(reference);
2061
+ }
2062
+
2063
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2064
+ manifest.anyTimeline();
2065
+ manifest.addPartialVariant((variant) => {
2066
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2067
+ stream.segmentIndex = new shaka.media.SegmentIndex(segments);
2068
+ });
2069
+ });
2070
+ manifest.sequenceMode = true;
2071
+ });
2072
+
2073
+ fakeNetEngine
2074
+ .setResponseText('test:/master', master)
2075
+ .setResponseText('test:/video', media)
2076
+ .setResponseValue('test:/init.mp4', initSegmentData)
2077
+ .setResponseValue('test:/main.mp4', segmentData);
2078
+
2079
+ const actual = await parser.start('test:/master', playerInterface);
2080
+ expect(actual).toEqual(manifest);
2081
+ }
2082
+
2083
+ it('from EXT-X-PROGRAM-DATE-TIME', async () => {
2084
+ await test([
2085
+ '#EXTM3U\n',
2086
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2087
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2088
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:05.00Z\n',
2089
+ '#EXTINF:5,\n',
2090
+ 'main.mp4\n',
2091
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
2092
+ '#EXTINF:5,\n',
2093
+ 'main.mp4\n',
2094
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:15.00Z\n',
2095
+ '#EXTINF:5,\n',
2096
+ 'main.mp4\n',
2097
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:20.00Z\n',
2098
+ '#EXTINF:5,\n',
2099
+ 'main.mp4\n',
2100
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2101
+ '#EXTINF:5,\n',
2102
+ 'main.mp4',
2103
+ ].join(''), [0, 5, 10, 15, 20, 25]);
2104
+ });
2105
+
2106
+ it('when some EXT-X-PROGRAM-DATE-TIME values are missing', async () => {
2107
+ await test([
2108
+ '#EXTM3U\n',
2109
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2110
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2111
+ '#EXTINF:2,\n',
2112
+ 'main.mp4\n',
2113
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
2114
+ '#EXTINF:5,\n',
2115
+ 'main.mp4\n',
2116
+ '#EXTINF:5,\n',
2117
+ 'main.mp4\n',
2118
+ '#EXTINF:5,\n',
2119
+ 'main.mp4\n',
2120
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2121
+ '#EXTINF:2,\n',
2122
+ 'main.mp4\n',
2123
+ '#EXTINF:4,\n',
2124
+ 'main.mp4',
2125
+ ].join(''), [0, 2, 7, 12, 17, 19, 23]);
2126
+ });
2127
+
2128
+ it('except when ignoreManifestProgramDateTime is set', async () => {
2129
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
2130
+ config.hls.ignoreManifestProgramDateTime = true;
2131
+ parser.configure(config);
2132
+ await test([
2133
+ '#EXTM3U\n',
2134
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2135
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2136
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:05.00Z\n',
2137
+ '#EXTINF:5,\n',
2138
+ 'main.mp4\n',
2139
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
2140
+ '#EXTINF:5,\n',
2141
+ 'main.mp4\n',
2142
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:15.00Z\n',
2143
+ '#EXTINF:5,\n',
2144
+ 'main.mp4\n',
2145
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:20.00Z\n',
2146
+ '#EXTINF:5,\n',
2147
+ 'main.mp4\n',
2148
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2149
+ '#EXTINF:5,\n',
2150
+ 'main.mp4',
2151
+ ].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
2152
+ reference.syncTime = null;
2153
+ });
2154
+ });
2155
+
2156
+ it('when there are partial segments', async () => {
2157
+ playerInterface.isLowLatencyMode = () => true;
2158
+ await test([
2159
+ '#EXTM3U\n',
2160
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2161
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2162
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:05.00Z\n',
2163
+ '#EXTINF:5,\n',
2164
+ 'main.mp4\n',
2165
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
2166
+ '#EXTINF:5,\n',
2167
+ 'main.mp4\n',
2168
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:15.00Z\n',
2169
+ '#EXT-X-PART:DURATION=2.5,URI="main.mp4"\n',
2170
+ '#EXT-X-PART:DURATION=2.5,URI="main.mp4"\n',
2171
+ '#EXTINF:5,\n',
2172
+ 'main.mp4\n',
2173
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:20.00Z\n',
2174
+ '#EXTINF:5,\n',
2175
+ 'main.mp4\n',
2176
+ '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2177
+ '#EXTINF:5,\n',
2178
+ 'main.mp4',
2179
+ ].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
2180
+ if (reference.startTime == 10) {
2181
+ reference.partialReferences = [
2182
+ makeReference(10, 12.5, 10),
2183
+ makeReference(12.5, 15, 12.5),
2184
+ ];
2185
+ }
2186
+ });
2187
+ });
2188
+ });
2189
+
1912
2190
  it('drops failed text streams when configured to', async () => {
1913
2191
  const master = [
1914
2192
  '#EXTM3U\n',
@@ -1932,6 +2210,7 @@ describe('HlsParser', () => {
1932
2210
  manifest.addPartialVariant((variant) => {
1933
2211
  variant.addPartialStream(ContentType.VIDEO);
1934
2212
  });
2213
+ manifest.sequenceMode = true;
1935
2214
  });
1936
2215
 
1937
2216
  fakeNetEngine
@@ -1969,6 +2248,7 @@ describe('HlsParser', () => {
1969
2248
  manifest.addPartialVariant((variant) => {
1970
2249
  variant.addPartialStream(ContentType.VIDEO);
1971
2250
  });
2251
+ manifest.sequenceMode = true;
1972
2252
  });
1973
2253
 
1974
2254
  fakeNetEngine
@@ -2011,6 +2291,7 @@ describe('HlsParser', () => {
2011
2291
  });
2012
2292
  variant.addPartialStream(ContentType.AUDIO);
2013
2293
  });
2294
+ manifest.sequenceMode = true;
2014
2295
  });
2015
2296
 
2016
2297
  await testHlsParser(master, media, manifest);
@@ -2095,6 +2376,7 @@ describe('HlsParser', () => {
2095
2376
  stream.mime('audio/mp4', 'mp4a');
2096
2377
  });
2097
2378
  });
2379
+ manifest.sequenceMode = true;
2098
2380
  });
2099
2381
 
2100
2382
  await testHlsParser(master, media, manifest);
@@ -2190,6 +2472,7 @@ describe('HlsParser', () => {
2190
2472
  stream.language = 'en';
2191
2473
  });
2192
2474
  });
2475
+ manifest.sequenceMode = true;
2193
2476
  });
2194
2477
 
2195
2478
  fakeNetEngine
@@ -2248,6 +2531,7 @@ describe('HlsParser', () => {
2248
2531
  });
2249
2532
  });
2250
2533
  });
2534
+ manifest.sequenceMode = true;
2251
2535
  });
2252
2536
 
2253
2537
  await testHlsParser(master, media, manifest);
@@ -2287,6 +2571,44 @@ describe('HlsParser', () => {
2287
2571
  });
2288
2572
  });
2289
2573
  });
2574
+ manifest.sequenceMode = true;
2575
+ });
2576
+
2577
+ await testHlsParser(master, media, manifest);
2578
+ });
2579
+
2580
+ it('constructs DrmInfo for FairPlay', async () => {
2581
+ const master = [
2582
+ '#EXTM3U\n',
2583
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2584
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2585
+ 'video\n',
2586
+ ].join('');
2587
+
2588
+ const media = [
2589
+ '#EXTM3U\n',
2590
+ '#EXT-X-TARGETDURATION:6\n',
2591
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2592
+ '#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
2593
+ 'KEYFORMAT="com.apple.streamingkeydelivery",',
2594
+ 'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
2595
+ '#EXT-X-MAP:URI="init.mp4"\n',
2596
+ '#EXTINF:5,\n',
2597
+ '#EXT-X-BYTERANGE:121090@616\n',
2598
+ 'main.mp4',
2599
+ ].join('');
2600
+
2601
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2602
+ manifest.anyTimeline();
2603
+ manifest.addPartialVariant((variant) => {
2604
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2605
+ stream.encrypted = true;
2606
+ stream.addDrmInfo('com.apple.fps', (drmInfo) => {
2607
+ drmInfo.addInitData('sinf', new Uint8Array(0));
2608
+ });
2609
+ });
2610
+ });
2611
+ manifest.sequenceMode = true;
2290
2612
  });
2291
2613
 
2292
2614
  await testHlsParser(master, media, manifest);
@@ -2320,6 +2642,7 @@ describe('HlsParser', () => {
2320
2642
  stream.mime('audio/mp4', 'mp4a');
2321
2643
  });
2322
2644
  });
2645
+ manifest.sequenceMode = true;
2323
2646
  });
2324
2647
 
2325
2648
  fakeNetEngine.setHeaders(
@@ -2488,6 +2811,34 @@ describe('HlsParser', () => {
2488
2811
  });
2489
2812
  });
2490
2813
 
2814
+ it('if FairPlay encryption with MSE and mp2t content', async () => {
2815
+ const master = [
2816
+ '#EXTM3U\n',
2817
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2818
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2819
+ 'video\n',
2820
+ ].join('');
2821
+
2822
+ const media = [
2823
+ '#EXTM3U\n',
2824
+ '#EXT-X-TARGETDURATION:6\n',
2825
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2826
+ '#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
2827
+ 'KEYFORMAT="com.apple.streamingkeydelivery",',
2828
+ 'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
2829
+ '#EXTINF:5,\n',
2830
+ '#EXT-X-BYTERANGE:121090@616\n',
2831
+ 'main.ts',
2832
+ ].join('');
2833
+
2834
+ const error = new shaka.util.Error(
2835
+ shaka.util.Error.Severity.CRITICAL,
2836
+ shaka.util.Error.Category.MANIFEST,
2837
+ Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2838
+
2839
+ await verifyError(master, media, error);
2840
+ });
2841
+
2491
2842
  describe('if required tags are missing', () => {
2492
2843
  /**
2493
2844
  * @param {string} master
@@ -2526,261 +2877,6 @@ describe('HlsParser', () => {
2526
2877
  });
2527
2878
  }); // Errors out
2528
2879
 
2529
- describe('getStartTime_', () => {
2530
- /** @type {number} */
2531
- let segmentDataStartTime;
2532
- /** @type {!Uint8Array} */
2533
- let tsSegmentData;
2534
- /** @type {!Uint8Array} */
2535
- let nullTsPacketData;
2536
-
2537
- const master = [
2538
- '#EXTM3U\n',
2539
- '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2540
- 'RESOLUTION=960x540,FRAME-RATE=60\n',
2541
- 'video',
2542
- ].join('');
2543
-
2544
- const media = [
2545
- '#EXTM3U\n',
2546
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
2547
- '#EXT-X-MAP:URI="init.mp4"\n',
2548
- '#EXTINF:5,\n',
2549
- '#EXT-X-BYTERANGE:121090@616\n',
2550
- 'main.mp4',
2551
- ].join('');
2552
-
2553
- // TODO: Add separate tests to cover correct handling of BYTERANGE in
2554
- // constructing references. Here it is covered incidentally.
2555
- const expectedStartByte = 616;
2556
- const expectedEndByte = 121705;
2557
- // Nit: this value is an implementation detail of the fix for #1106
2558
- const partialEndByte = expectedStartByte + 2048 - 1;
2559
-
2560
- beforeEach(() => {
2561
- // TODO: use StreamGenerator?
2562
- segmentData = new Uint8Array([
2563
- 0x00, 0x00, 0x00, 0x24, // size (36)
2564
- 0x6D, 0x6F, 0x6F, 0x66, // type (moof)
2565
- 0x00, 0x00, 0x00, 0x1C, // traf size (28)
2566
- 0x74, 0x72, 0x61, 0x66, // type (traf)
2567
- 0x00, 0x00, 0x00, 0x14, // tfdt size (20)
2568
- 0x74, 0x66, 0x64, 0x74, // type (tfdt)
2569
- 0x01, 0x00, 0x00, 0x00, // version and flags
2570
-
2571
- 0x00, 0x00, 0x00, 0x00, // baseMediaDecodeTime first 4 bytes
2572
- 0x00, 0x00, 0x07, 0xd0, // baseMediaDecodeTime last 4 bytes (2000)
2573
- ]);
2574
- tsSegmentData = new Uint8Array([
2575
- 0x47, // TS sync byte (fixed value)
2576
- 0x41, 0x01, // not corrupt, payload follows, packet ID 257
2577
- 0x10, // not scrambled, no adaptation field, payload only, seq #0
2578
- 0x00, 0x00, 0x01, // PES start code (fixed value)
2579
- 0xe0, // stream ID (video stream 0)
2580
- 0x00, 0x00, // PES packet length (doesn't matter)
2581
- 0x80, // marker bits (fixed value), not scrambled, not priority
2582
- 0x80, // PTS only, no DTS, other flags 0 (don't matter)
2583
- 0x05, // remaining PES header length == 5 (one timestamp)
2584
- 0x21, 0x00, 0x0b, 0x7e, 0x41, // PTS = 180000, encoded into 5 bytes
2585
- ]);
2586
- // 180000 (TS PTS) divided by fixed TS timescale (90000) = 2s.
2587
- // 2000 (MP4 PTS) divided by parsed MP4 timescale (1000) = 2s.
2588
- segmentDataStartTime = 2;
2589
- nullTsPacketData = new Uint8Array([
2590
- 0x47, // TS sync byte (fixed value)
2591
- 0x1f, 0xff, // null packet (packet ID 8191)
2592
- ]);
2593
- });
2594
-
2595
- it('parses start time from mp4 segment', async () => {
2596
- fakeNetEngine
2597
- .setResponseText('test:/master', master)
2598
- .setResponseText('test:/video', media)
2599
- .setResponseValue('test:/init.mp4', initSegmentData)
2600
- .setResponseValue('test:/main.mp4', segmentData);
2601
-
2602
- const expectedRef = ManifestParser.makeReference(
2603
- /* uri= */ 'test:/main.mp4',
2604
- /* startTime= */ 0,
2605
- /* endTime= */ 5,
2606
- /* baseUri= */ '',
2607
- expectedStartByte,
2608
- expectedEndByte);
2609
- // In VOD content, we set the timestampOffset to align the
2610
- // content to presentation time 0.
2611
- expectedRef.timestampOffset = -segmentDataStartTime;
2612
-
2613
- const manifest = await parser.start('test:/master', playerInterface);
2614
- const video = manifest.variants[0].video;
2615
- await video.createSegmentIndex();
2616
- ManifestParser.verifySegmentIndex(video, [expectedRef]);
2617
-
2618
- // Make sure the segment data was fetched with the correct byte
2619
- // range.
2620
- fakeNetEngine.expectRangeRequest(
2621
- 'test:/main.mp4',
2622
- expectedStartByte,
2623
- partialEndByte);
2624
- });
2625
-
2626
- it('parses start time from ts segments', async () => {
2627
- const tsMediaPlaylist = media.replace(/\.mp4/g, '.ts');
2628
-
2629
- fakeNetEngine
2630
- .setResponseText('test:/master', master)
2631
- .setResponseText('test:/video', tsMediaPlaylist)
2632
- .setResponseValue('test:/main.ts', tsSegmentData);
2633
-
2634
- const expectedRef = ManifestParser.makeReference(
2635
- /* uri= */ 'test:/main.ts',
2636
- /* startTime= */ 0,
2637
- /* endTime= */ 5,
2638
- /* baseUri= */ '',
2639
- expectedStartByte,
2640
- expectedEndByte);
2641
- // In VOD content, we set the timestampOffset to align the
2642
- // content to presentation time 0.
2643
- expectedRef.timestampOffset = -segmentDataStartTime;
2644
-
2645
- const manifest = await parser.start('test:/master', playerInterface);
2646
- const video = manifest.variants[0].video;
2647
- await video.createSegmentIndex();
2648
- ManifestParser.verifySegmentIndex(video, [expectedRef]);
2649
-
2650
- // Make sure the segment data was fetched with the correct byte
2651
- // range.
2652
- fakeNetEngine.expectRangeRequest(
2653
- 'test:/main.ts',
2654
- expectedStartByte,
2655
- partialEndByte);
2656
- });
2657
-
2658
- it('parses start time from ts segments with null packets', async () => {
2659
- const tsMediaPlaylist = media.replace(/\.mp4/g, '.ts');
2660
-
2661
- // Each packet is 188 bytes, so allocate space for 3.
2662
- const tsSegmentWithNullPackets = new Uint8Array(188 * 3);
2663
- // The first two are "null" packets.
2664
- tsSegmentWithNullPackets.set(nullTsPacketData, /* offset= */ 0);
2665
- tsSegmentWithNullPackets.set(nullTsPacketData, /* offset= */ 188);
2666
- // The third has a timestamp.
2667
- tsSegmentWithNullPackets.set(tsSegmentData, /* offset= */ 188 * 2);
2668
-
2669
- fakeNetEngine
2670
- .setResponseText('test:/master', master)
2671
- .setResponseText('test:/video', tsMediaPlaylist)
2672
- .setResponseValue('test:/main.ts', tsSegmentWithNullPackets);
2673
-
2674
- const expectedRef = ManifestParser.makeReference(
2675
- /* uri= */ 'test:/main.ts',
2676
- /* startTime= */ 0,
2677
- /* endTime= */ 5,
2678
- /* baseUri= */ '',
2679
- expectedStartByte,
2680
- expectedEndByte);
2681
- // In VOD content, we set the timestampOffset to align the
2682
- // content to presentation time 0.
2683
- expectedRef.timestampOffset = -segmentDataStartTime;
2684
-
2685
- const manifest = await parser.start('test:/master', playerInterface);
2686
- const video = manifest.variants[0].video;
2687
- await video.createSegmentIndex();
2688
- ManifestParser.verifySegmentIndex(video, [expectedRef]);
2689
-
2690
- // Make sure the segment data was fetched with the correct byte
2691
- // range.
2692
- fakeNetEngine.expectRangeRequest(
2693
- 'test:/main.ts',
2694
- expectedStartByte,
2695
- partialEndByte);
2696
- });
2697
-
2698
- // We want to make sure that we can interrupt the parser while it is getting
2699
- // the start time. This is a regression test for Issue #1788 where
2700
- // interrupting the partial network request would be misinterpreted as the
2701
- // server not supporting range requests.
2702
- it('can be interrupted', async () => {
2703
- fakeNetEngine
2704
- .setResponseText('test:/master', master)
2705
- .setResponseText('test:/video', media)
2706
- .setResponseValue('test:/init.mp4', initSegmentData);
2707
-
2708
- // We are assuming that the time will be pulled out of the main mp4
2709
- // segment, so if we see a request that has a range header, we will stop
2710
- // the parser.
2711
- /** @type {!Map.<string, !BufferSource>} */
2712
- const responses = new Map();
2713
- responses.set('test:/main.mp4', segmentData);
2714
- responses.set('test:/init.mp4', initSegmentData);
2715
-
2716
- responses.forEach((data, uri) => {
2717
- fakeNetEngine.setResponse(uri, () => {
2718
- // Now that we are stopping the parser, we don't want to see any more
2719
- // requests. So if there is another request, fail the test.
2720
- responses.forEach((data, uri) => {
2721
- fakeNetEngine.setResponse(uri, fail);
2722
- });
2723
-
2724
- // Stop the parser, but don't wait on it or else we will hit deadlock.
2725
- parser.stop();
2726
-
2727
- return Promise.resolve(data);
2728
- });
2729
- });
2730
-
2731
- const expected = Util.jasmineError(new shaka.util.Error(
2732
- shaka.util.Error.Severity.CRITICAL,
2733
- shaka.util.Error.Category.PLAYER,
2734
- shaka.util.Error.Code.OPERATION_ABORTED));
2735
- await expectAsync(parser.start('test:/master', playerInterface))
2736
- .toBeRejectedWith(expected);
2737
- });
2738
-
2739
- it('sets duration with respect to presentation offset', async () => {
2740
- fakeNetEngine
2741
- .setResponseText('test:/master', master)
2742
- .setResponseText('test:/video', media)
2743
- .setResponseValue('test:/init.mp4', initSegmentData)
2744
- .setResponseValue('test:/main.mp4', segmentData);
2745
-
2746
- const manifest = await parser.start('test:/master', playerInterface);
2747
- const presentationTimeline = manifest.presentationTimeline;
2748
-
2749
- const video = manifest.variants[0].video;
2750
- await video.createSegmentIndex();
2751
- goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!');
2752
-
2753
- const refs = Array.from(video.segmentIndex);
2754
- expect(refs.length).toBe(1);
2755
-
2756
- expect(refs[0].timestampOffset).toBe(-segmentDataStartTime);
2757
- // The duration should be set to the sum of the segment durations (5),
2758
- // even though the endTime of the segment is larger.
2759
- expect(refs[0].endTime - refs[0].startTime).toBe(5);
2760
- expect(presentationTimeline.getDuration()).toBe(5);
2761
- });
2762
-
2763
- it('forces full segment request', async () => {
2764
- fakeNetEngine
2765
- .setResponseText('test:/master', master)
2766
- .setResponseText('test:/video', media)
2767
- .setResponseValue('test:/init.mp4', initSegmentData)
2768
- .setResponseValue('test:/main.mp4', segmentData);
2769
-
2770
- const config = shaka.util.PlayerConfiguration.createDefault().manifest;
2771
- config.hls.useFullSegmentsForStartTime = true;
2772
- parser.configure(config);
2773
- await parser.start('test:/master', playerInterface);
2774
-
2775
- // Make sure the segment data was fetched with the correct byte
2776
- // range.
2777
- fakeNetEngine.expectRangeRequest(
2778
- 'test:/main.mp4',
2779
- expectedStartByte,
2780
- expectedEndByte);
2781
- });
2782
- });
2783
-
2784
2880
  it('correctly detects VOD streams as non-live', async () => {
2785
2881
  const master = [
2786
2882
  '#EXTM3U\n',
@@ -3037,80 +3133,12 @@ describe('HlsParser', () => {
3037
3133
  stream.language = 'fr';
3038
3134
  });
3039
3135
  });
3136
+ manifest.sequenceMode = true;
3040
3137
  });
3041
3138
 
3042
3139
  await testHlsParser(master, media, manifest);
3043
3140
  });
3044
3141
 
3045
- it('skips raw audio formats', async () => {
3046
- const master = [
3047
- '#EXTM3U\n',
3048
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",URI="audio1"\n',
3049
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",URI="audio2"\n',
3050
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",URI="audio3"\n',
3051
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",URI="audio4"\n',
3052
- '#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1,mp4a",',
3053
- 'RESOLUTION=1280x720,AUDIO="audio"\n',
3054
- 'video\n',
3055
- ].join('');
3056
-
3057
- const videoMedia = [
3058
- '#EXTM3U\n',
3059
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
3060
- '#EXT-X-MAP:URI="v-init.mp4"\n',
3061
- '#EXTINF:5,\n',
3062
- 'v1.mp4',
3063
- ].join('');
3064
-
3065
- const audioMedia1 = [
3066
- '#EXTM3U\n',
3067
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
3068
- '#EXTINF:5,\n',
3069
- 'a1.mp3',
3070
- ].join('');
3071
-
3072
- const audioMedia2 = [
3073
- '#EXTM3U\n',
3074
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
3075
- '#EXTINF:5,\n',
3076
- 'a1.aac',
3077
- ].join('');
3078
-
3079
- const audioMedia3 = [
3080
- '#EXTM3U\n',
3081
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
3082
- '#EXTINF:5,\n',
3083
- 'a1.ac3',
3084
- ].join('');
3085
-
3086
- const audioMedia4 = [
3087
- '#EXTM3U\n',
3088
- '#EXT-X-PLAYLIST-TYPE:VOD\n',
3089
- '#EXTINF:5,\n',
3090
- 'a1.ec3',
3091
- ].join('');
3092
-
3093
- fakeNetEngine
3094
- .setResponseText('test:/master', master)
3095
- .setResponseText('test:/video', videoMedia)
3096
- .setResponseText('test:/audio1', audioMedia1)
3097
- .setResponseText('test:/audio2', audioMedia2)
3098
- .setResponseText('test:/audio3', audioMedia3)
3099
- .setResponseText('test:/audio4', audioMedia4)
3100
- .setResponseValue('test:/v-init.mp4', initSegmentData)
3101
- .setResponseValue('test:/v1.mp4', segmentData);
3102
-
3103
- const alwaysWarnSpy = jasmine.createSpy('shaka.log.alwaysWarn');
3104
- shaka.log.alwaysWarn = shaka.test.Util.spyFunc(alwaysWarnSpy);
3105
-
3106
- const manifest = await parser.start('test:/master', playerInterface);
3107
- expect(manifest.variants.length).toBe(1);
3108
- expect(manifest.variants[0].audio).toBe(null);
3109
-
3110
- // We should log a warning when this happens.
3111
- expect(alwaysWarnSpy).toHaveBeenCalled();
3112
- });
3113
-
3114
3142
  // Issue #1875
3115
3143
  it('ignores audio groups on audio-only content', async () => {
3116
3144
  // NOTE: To reproduce the original issue accurately, the two audio playlist
@@ -3146,6 +3174,7 @@ describe('HlsParser', () => {
3146
3174
  stream.mime('audio/mp4', 'mp4a');
3147
3175
  });
3148
3176
  });
3177
+ manifest.sequenceMode = true;
3149
3178
  });
3150
3179
 
3151
3180
  fakeNetEngine
@@ -3328,6 +3357,7 @@ describe('HlsParser', () => {
3328
3357
  stream.mime('audio/mp4', 'mp4a');
3329
3358
  });
3330
3359
  });
3360
+ manifest.sequenceMode = true;
3331
3361
  });
3332
3362
 
3333
3363
  await testHlsParser(master, media, manifest);
@@ -3365,6 +3395,7 @@ describe('HlsParser', () => {
3365
3395
  stream.mime('audio/mp4', 'mp4a');
3366
3396
  });
3367
3397
  });
3398
+ manifest.sequenceMode = true;
3368
3399
  });
3369
3400
 
3370
3401
  await testHlsParser(master, media, manifest);
@@ -3403,6 +3434,7 @@ describe('HlsParser', () => {
3403
3434
  stream.mime('audio/mp4', 'mp4a');
3404
3435
  });
3405
3436
  });
3437
+ manifest.sequenceMode = true;
3406
3438
  });
3407
3439
 
3408
3440
  await testHlsParser(master, media, manifest);
@@ -3442,6 +3474,7 @@ describe('HlsParser', () => {
3442
3474
  stream.mime('audio/mp4', 'mp4a');
3443
3475
  });
3444
3476
  });
3477
+ manifest.sequenceMode = true;
3445
3478
  });
3446
3479
 
3447
3480
  await testHlsParser(master, media, manifest);
@@ -3472,4 +3505,54 @@ describe('HlsParser', () => {
3472
3505
  jasmine.objectContaining(eventValue3));
3473
3506
  });
3474
3507
  });
3508
+
3509
+ it('parses media playlists directly', async () => {
3510
+ const media = [
3511
+ '#EXTM3U\n',
3512
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3513
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3514
+ '#EXTINF:5,\n',
3515
+ '#EXT-X-BYTERANGE:121090@616\n',
3516
+ 'main.mp4',
3517
+ ].join('');
3518
+
3519
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3520
+ manifest.sequenceMode = true;
3521
+ manifest.anyTimeline();
3522
+ manifest.addPartialVariant((variant) => {
3523
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3524
+ stream.mime('video/mp2t', 'avc1.42E01E, mp4a.40.2');
3525
+ });
3526
+ });
3527
+ });
3528
+
3529
+ await testHlsParser(media, '', manifest);
3530
+ });
3531
+
3532
+ it('honors hls.mediaPlaylistFullMimeType', async () => {
3533
+ const media = [
3534
+ '#EXTM3U\n',
3535
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3536
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3537
+ '#EXTINF:5,\n',
3538
+ '#EXT-X-BYTERANGE:121090@616\n',
3539
+ 'main.mp4',
3540
+ ].join('');
3541
+
3542
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
3543
+ config.hls.mediaPlaylistFullMimeType = 'audio/webm; codecs="vorbis"';
3544
+ parser.configure(config);
3545
+
3546
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3547
+ manifest.sequenceMode = true;
3548
+ manifest.anyTimeline();
3549
+ manifest.addPartialVariant((variant) => {
3550
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
3551
+ stream.mime('audio/webm', 'vorbis');
3552
+ });
3553
+ });
3554
+ });
3555
+
3556
+ await testHlsParser(media, '', manifest);
3557
+ });
3475
3558
  });