shaka-player 4.5.0 → 4.6.1

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 (214) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/AUTHORS +2 -0
  3. package/CHANGELOG.md +109 -0
  4. package/CODE_OF_CONDUCT.md +1 -1
  5. package/CONTRIBUTORS +2 -0
  6. package/README.md +38 -32
  7. package/demo/common/asset.js +11 -17
  8. package/demo/common/assets.js +117 -11
  9. package/demo/config.js +28 -1
  10. package/demo/custom.js +52 -38
  11. package/demo/main.js +11 -3
  12. package/demo/search.js +7 -0
  13. package/dist/controls.css +1 -1
  14. package/dist/controls.css.map +1 -1
  15. package/dist/demo.compiled.debug.js +133 -124
  16. package/dist/demo.compiled.debug.map +3 -3
  17. package/dist/demo.compiled.js +133 -124
  18. package/dist/demo.compiled.map +3 -3
  19. package/dist/deps.js +20 -18
  20. package/dist/locales.js +34 -30
  21. package/dist/receiver.compiled.debug.js +58 -50
  22. package/dist/receiver.compiled.debug.map +3 -3
  23. package/dist/receiver.compiled.js +58 -50
  24. package/dist/receiver.compiled.map +3 -3
  25. package/dist/shaka-player.compiled.d.ts +440 -390
  26. package/dist/shaka-player.compiled.debug.d.ts +440 -390
  27. package/dist/shaka-player.compiled.debug.externs.js +162 -77
  28. package/dist/shaka-player.compiled.debug.js +789 -738
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +162 -77
  31. package/dist/shaka-player.compiled.js +1147 -1113
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +491 -437
  34. package/dist/shaka-player.ui.debug.d.ts +491 -437
  35. package/dist/shaka-player.ui.debug.externs.js +167 -77
  36. package/dist/shaka-player.ui.debug.js +857 -791
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +167 -77
  39. package/dist/shaka-player.ui.js +1359 -1314
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/basic-usage.md +2 -1
  43. package/docs/tutorials/errors.md +2 -1
  44. package/docs/tutorials/offline.md +10 -8
  45. package/docs/tutorials/plugins.md +5 -0
  46. package/docs/tutorials/ui-customization.md +13 -0
  47. package/docs/tutorials/ui.md +2 -1
  48. package/docs/tutorials/upgrade.md +3 -0
  49. package/externs/managedmediasource.js +24 -0
  50. package/externs/shaka/abr_manager.js +6 -2
  51. package/externs/shaka/cea.js +7 -0
  52. package/externs/shaka/codecs.js +5 -3
  53. package/externs/shaka/manifest.js +21 -5
  54. package/externs/shaka/manifest_parser.js +17 -0
  55. package/externs/shaka/net.js +2 -2
  56. package/externs/shaka/offline.js +9 -2
  57. package/externs/shaka/player.js +53 -7
  58. package/externs/shaka/text.js +3 -1
  59. package/externs/shaka/transmuxer.js +1 -1
  60. package/lib/abr/simple_abr_manager.js +25 -8
  61. package/lib/ads/media_tailor_ad_manager.js +14 -3
  62. package/lib/cast/cast_receiver.js +1 -1
  63. package/lib/cea/cea_decoder.js +28 -0
  64. package/lib/cea/dummy_caption_decoder.js +5 -0
  65. package/lib/dash/dash_parser.js +251 -84
  66. package/lib/dash/mpd_utils.js +52 -31
  67. package/lib/dash/segment_base.js +4 -5
  68. package/lib/dash/segment_list.js +5 -4
  69. package/lib/dash/segment_template.js +92 -29
  70. package/lib/hls/hls_classes.js +28 -43
  71. package/lib/hls/hls_parser.js +770 -652
  72. package/lib/hls/hls_utils.js +64 -5
  73. package/lib/hls/manifest_text_parser.js +7 -11
  74. package/lib/media/adaptation_set.js +20 -13
  75. package/lib/media/adaptation_set_criteria.js +70 -18
  76. package/lib/media/closed_caption_parser.js +13 -0
  77. package/lib/media/content_workarounds.js +6 -0
  78. package/lib/media/drm_engine.js +8 -13
  79. package/lib/media/manifest_parser.js +8 -113
  80. package/lib/media/media_source_capabilities.js +10 -3
  81. package/lib/media/media_source_engine.js +75 -21
  82. package/lib/media/play_rate_controller.js +8 -38
  83. package/lib/media/presentation_timeline.js +7 -1
  84. package/lib/media/quality_observer.js +1 -1
  85. package/lib/media/segment_index.js +5 -5
  86. package/lib/media/segment_prefetch.js +92 -24
  87. package/lib/media/segment_reference.js +67 -1
  88. package/lib/media/segment_utils.js +343 -0
  89. package/lib/media/stall_detector.js +0 -3
  90. package/lib/media/streaming_engine.js +72 -44
  91. package/lib/mss/mss_parser.js +18 -2
  92. package/lib/net/networking_engine.js +66 -12
  93. package/lib/net/networking_utils.js +95 -0
  94. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  95. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  96. package/lib/offline/manifest_converter.js +2 -0
  97. package/lib/offline/offline_manifest_parser.js +10 -0
  98. package/lib/offline/storage.js +11 -7
  99. package/lib/player.js +998 -1503
  100. package/lib/polyfill/mediasource.js +22 -1
  101. package/lib/text/mp4_ttml_parser.js +2 -2
  102. package/lib/text/srt_text_parser.js +2 -2
  103. package/lib/text/stub_text_displayer.js +51 -0
  104. package/lib/text/text_engine.js +3 -2
  105. package/lib/text/ttml_text_parser.js +28 -5
  106. package/lib/text/ui_text_displayer.js +4 -0
  107. package/lib/text/vtt_text_parser.js +7 -2
  108. package/lib/transmuxer/aac_transmuxer.js +1 -1
  109. package/lib/transmuxer/ac3_transmuxer.js +1 -1
  110. package/lib/transmuxer/ec3_transmuxer.js +1 -1
  111. package/lib/transmuxer/h264.js +5 -4
  112. package/lib/transmuxer/h265.js +5 -4
  113. package/lib/transmuxer/mp3_transmuxer.js +1 -1
  114. package/lib/transmuxer/mpeg_ts_transmuxer.js +1 -1
  115. package/lib/transmuxer/mss_transmuxer.js +1 -1
  116. package/lib/transmuxer/ts_transmuxer.js +33 -13
  117. package/lib/util/buffer_utils.js +26 -4
  118. package/lib/util/content_steering_manager.js +358 -0
  119. package/lib/util/error.js +2 -6
  120. package/lib/util/fairplay_utils.js +1 -1
  121. package/lib/util/fake_event.js +5 -1
  122. package/lib/util/id3_utils.js +50 -1
  123. package/lib/util/manifest_parser_utils.js +12 -8
  124. package/lib/util/mp4_generator.js +2 -4
  125. package/lib/util/mutex.js +59 -0
  126. package/lib/util/object_utils.js +1 -1
  127. package/lib/util/periods.js +154 -179
  128. package/lib/util/platform.js +4 -3
  129. package/lib/util/player_configuration.js +35 -6
  130. package/lib/util/stream_utils.js +105 -98
  131. package/lib/util/ts_parser.js +124 -73
  132. package/lib/util/uint8array_utils.js +5 -1
  133. package/lib/util/xml_utils.js +5 -0
  134. package/maintained-branches.md +2 -2
  135. package/package.json +1 -1
  136. package/roadmap.md +15 -5
  137. package/shaka-player.uncompiled.js +0 -1
  138. package/test/abr/simple_abr_manager_unit.js +33 -33
  139. package/test/ads/ad_manager_unit.js +3 -2
  140. package/test/cast/cast_receiver_integration.js +3 -4
  141. package/test/cast/cast_utils_unit.js +2 -0
  142. package/test/cea/cea_decoder_unit.js +7 -0
  143. package/test/codec_switching/codec_switching_integration.js +2 -1
  144. package/test/dash/dash_parser_integration.js +2 -1
  145. package/test/dash/dash_parser_live_unit.js +0 -19
  146. package/test/dash/dash_parser_manifest_unit.js +494 -2
  147. package/test/dash/mpd_utils_unit.js +61 -30
  148. package/test/hls/hls_live_unit.js +12 -3
  149. package/test/hls/hls_parser_unit.js +417 -27
  150. package/test/hls/manifest_text_parser_unit.js +32 -99
  151. package/test/media/adaptation_set_criteria_unit.js +172 -21
  152. package/test/media/adaptation_set_unit.js +1 -0
  153. package/test/media/content_workarounds_unit.js +1 -1
  154. package/test/media/drm_engine_unit.js +3 -3
  155. package/test/media/media_source_engine_unit.js +1 -1
  156. package/test/media/play_rate_controller_unit.js +0 -48
  157. package/test/media/segment_index_unit.js +2 -1
  158. package/test/mss/mss_player_integration.js +3 -2
  159. package/test/net/networking_engine_unit.js +3 -3
  160. package/test/{media/manifest_parser_unit.js → net/networking_utils_unit.js} +2 -2
  161. package/test/offline/manifest_convert_unit.js +9 -0
  162. package/test/offline/offline_integration.js +2 -1
  163. package/test/offline/storage_integration.js +21 -9
  164. package/test/player_external.js +4 -2
  165. package/test/player_integration.js +15 -4
  166. package/test/player_load_graph_integration.js +47 -151
  167. package/test/player_src_equals_external.js +1 -0
  168. package/test/player_src_equals_integration.js +71 -4
  169. package/test/player_unit.js +80 -27
  170. package/test/test/util/fake_media_source_engine.js +5 -0
  171. package/test/test/util/fake_networking_engine.js +9 -1
  172. package/test/test/util/fake_segment_prefetch.js +3 -0
  173. package/test/test/util/manifest_generator.js +4 -0
  174. package/test/test/util/manifest_parser_util.js +14 -2
  175. package/test/test/util/offline_utils.js +1 -0
  176. package/test/test/util/simple_fakes.js +2 -2
  177. package/test/test/util/streaming_engine_util.js +3 -0
  178. package/test/test/util/test_scheme.js +6 -0
  179. package/test/test/util/ui_utils.js +4 -3
  180. package/test/text/mp4_ttml_parser_unit.js +4 -4
  181. package/test/text/srt_text_parser_unit.js +1 -1
  182. package/test/text/text_displayer_layout_unit.js +1 -1
  183. package/test/text/text_engine_unit.js +6 -0
  184. package/test/text/ttml_text_parser_unit.js +33 -2
  185. package/test/transmuxer/transmuxer_engine_integration.js +9 -0
  186. package/test/transmuxer/transmuxer_integration.js +33 -3
  187. package/test/ui/ad_ui_unit.js +2 -2
  188. package/test/ui/ui_customization_unit.js +17 -16
  189. package/test/ui/ui_integration.js +30 -20
  190. package/test/ui/ui_unit.js +25 -19
  191. package/test/util/buffer_utils_unit.js +1 -1
  192. package/test/util/content_steering_manager_unit.js +427 -0
  193. package/test/util/id3_utils_unit.js +52 -0
  194. package/test/util/stream_utils_unit.js +17 -113
  195. package/test/util/ts_parser_unit.js +3 -3
  196. package/third_party/closure-uri/uri.js +13 -1
  197. package/ui/audio_language_selection.js +9 -2
  198. package/ui/externs/ui.js +17 -2
  199. package/ui/language_utils.js +43 -8
  200. package/ui/less/range_elements.less +33 -0
  201. package/ui/locales/en.json +1 -0
  202. package/ui/locales/es-419.json +1 -0
  203. package/ui/locales/es.json +1 -0
  204. package/ui/locales/source.json +4 -0
  205. package/ui/localization.js +10 -0
  206. package/ui/remote_button.js +3 -1
  207. package/ui/resolution_selection.js +47 -10
  208. package/ui/seek_bar.js +296 -3
  209. package/ui/text_selection.js +2 -1
  210. package/ui/ui.js +6 -2
  211. package/lib/routing/node.js +0 -23
  212. package/lib/routing/payload.js +0 -47
  213. package/lib/routing/walker.js +0 -587
  214. package/test/routing/walker_unit.js +0 -505
@@ -750,6 +750,46 @@ describe('HlsParser', () => {
750
750
  await testHlsParser(master, media, manifest);
751
751
  });
752
752
 
753
+ it('parses audio+video variant with global closed captions', async () => {
754
+ const master = [
755
+ '#EXTM3U\n',
756
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",CHANNELS="2",',
757
+ 'URI="audio"\n',
758
+ '#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cap1",LANGUAGE="eng",',
759
+ 'INSTREAM-ID="CC1"\n',
760
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
761
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
762
+ 'video\n',
763
+ ].join('');
764
+
765
+ const media = [
766
+ '#EXTM3U\n',
767
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
768
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
769
+ '#EXTINF:5,\n',
770
+ '#EXT-X-BYTERANGE:121090@616\n',
771
+ 'main.mp4',
772
+ ].join('');
773
+
774
+ const closedCaptions = new Map([['CC1', 'en']]);
775
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
776
+ manifest.anyTimeline();
777
+ manifest.addPartialVariant((variant) => {
778
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
779
+ stream.closedCaptions = closedCaptions;
780
+ stream.mime('video/mp4', 'avc1');
781
+ });
782
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
783
+ stream.mime('audio/mp4', 'mp4a');
784
+ });
785
+ });
786
+ manifest.sequenceMode = sequenceMode;
787
+ manifest.type = shaka.media.ManifestParser.HLS;
788
+ });
789
+
790
+ await testHlsParser(master, media, manifest);
791
+ });
792
+
753
793
  it('parses audio+video variant with no closed captions', async () => {
754
794
  const master = [
755
795
  '#EXTM3U\n',
@@ -924,7 +964,7 @@ describe('HlsParser', () => {
924
964
  '#EXT-X-STREAM-INF:BANDWIDTH=200,',
925
965
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
926
966
  'video\n',
927
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="audio"\n',
967
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",NAME="audio"\n',
928
968
  ].join('');
929
969
 
930
970
  const media = [
@@ -941,6 +981,9 @@ describe('HlsParser', () => {
941
981
  manifest.addPartialVariant((variant) => {
942
982
  variant.addPartialStream(ContentType.VIDEO, (stream) => {
943
983
  stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
984
+ stream.language = 'en';
985
+ stream.originalLanguage = 'eng';
986
+ stream.label = 'audio';
944
987
  });
945
988
  });
946
989
  manifest.sequenceMode = sequenceMode;
@@ -1488,6 +1531,72 @@ describe('HlsParser', () => {
1488
1531
  expect(actual).toEqual(manifest);
1489
1532
  });
1490
1533
 
1534
+ it('parses manifest with video layout metadata', async () => {
1535
+ const master = [
1536
+ '#EXTM3U\n',
1537
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1538
+ 'URI="audio"\n',
1539
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1540
+ 'FORCED=YES,URI="text"\n',
1541
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1542
+ 'REQ-VIDEO-LAYOUT=CH-STEREO,RESOLUTION=960x540,FRAME-RATE=60,',
1543
+ 'AUDIO="aud1",SUBTITLES="sub1"\n',
1544
+ 'video\n',
1545
+ ].join('');
1546
+
1547
+ const media = [
1548
+ '#EXTM3U\n',
1549
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1550
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1551
+ '#EXTINF:5,\n',
1552
+ '#EXT-X-BYTERANGE:121090@616\n',
1553
+ 'main.mp4',
1554
+ ].join('');
1555
+
1556
+ const textMedia = [
1557
+ '#EXTM3U\n',
1558
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1559
+ '#EXTINF:5,\n',
1560
+ '#EXT-X-BYTERANGE:121090@616\n',
1561
+ 'main.vtt',
1562
+ ].join('');
1563
+
1564
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1565
+ manifest.anyTimeline();
1566
+ manifest.addPartialVariant((variant) => {
1567
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
1568
+ stream.mime('video/mp4', 'avc1');
1569
+ stream.videoLayout = 'CH-STEREO';
1570
+ });
1571
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
1572
+ stream.mime('audio/mp4', 'mp4a');
1573
+ });
1574
+ });
1575
+ manifest.addPartialTextStream((stream) => {
1576
+ stream.language = 'en';
1577
+ stream.originalLanguage = 'eng';
1578
+ stream.forced = true;
1579
+ stream.kind = TextStreamKind.SUBTITLE;
1580
+ stream.mime('text/vtt', '');
1581
+ });
1582
+ manifest.sequenceMode = sequenceMode;
1583
+ manifest.type = shaka.media.ManifestParser.HLS;
1584
+ });
1585
+
1586
+ fakeNetEngine
1587
+ .setResponseText('test:/master', master)
1588
+ .setResponseText('test:/audio', media)
1589
+ .setResponseText('test:/video', media)
1590
+ .setResponseText('test:/text', textMedia)
1591
+ .setResponseText('test:/main.vtt', vttText)
1592
+ .setResponseValue('test:/init.mp4', initSegmentData)
1593
+ .setResponseValue('test:/main.mp4', segmentData);
1594
+
1595
+ const actual = await parser.start('test:/master', playerInterface);
1596
+ await loadAllStreamsFor(actual);
1597
+ expect(actual).toEqual(manifest);
1598
+ });
1599
+
1491
1600
  it('parses manifest with SUBTITLES', async () => {
1492
1601
  const master = [
1493
1602
  '#EXTM3U\n',
@@ -1501,7 +1610,7 @@ describe('HlsParser', () => {
1501
1610
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1502
1611
  'video\n',
1503
1612
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1504
- 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub2"\n',
1613
+ 'RESOLUTION=960x540,FRAME-RATE=90,AUDIO="aud1",SUBTITLES="sub2"\n',
1505
1614
  'video\n',
1506
1615
  ].join('');
1507
1616
 
@@ -1921,6 +2030,100 @@ describe('HlsParser', () => {
1921
2030
  }
1922
2031
  });
1923
2032
 
2033
+ it('supports EXT-X-I-FRAME-STREAM-INF with mjpg codec', async () => {
2034
+ const master = [
2035
+ '#EXTM3U\n',
2036
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
2037
+ 'URI="text"\n',
2038
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
2039
+ 'CHANNELS="2",URI="audio"\n',
2040
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
2041
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
2042
+ 'video\n',
2043
+ '#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="mjpg",',
2044
+ 'URI="iframe"\n',
2045
+ '#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="avc",',
2046
+ 'URI="iframeAvc"\n',
2047
+ ].join('');
2048
+
2049
+ const video = [
2050
+ '#EXTM3U\n',
2051
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2052
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2053
+ '#EXTINF:5,\n',
2054
+ 'main.mp4\n',
2055
+ '#EXTINF:5,\n',
2056
+ 'main.mp4\n',
2057
+ '#EXTINF:5,\n',
2058
+ 'main.mp4\n',
2059
+ ].join('');
2060
+
2061
+ const audio = [
2062
+ '#EXTM3U\n',
2063
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2064
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2065
+ '#EXTINF:5,\n',
2066
+ 'main.mp4\n',
2067
+ '#EXTINF:5,\n',
2068
+ 'main.mp4\n',
2069
+ '#EXTINF:5,\n',
2070
+ 'main.mp4\n',
2071
+ ].join('');
2072
+
2073
+ const text = [
2074
+ '#EXTM3U\n',
2075
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2076
+ '#EXTINF:5,\n',
2077
+ '#EXT-X-BYTERANGE:121090@616\n',
2078
+ 'main.vtt',
2079
+ ].join('');
2080
+
2081
+ const iframe = [
2082
+ '#EXTM3U\n',
2083
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2084
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2085
+ '#EXTINF:5,\n',
2086
+ 'main.mp4\n',
2087
+ '#EXTINF:5,\n',
2088
+ 'main.mp4\n',
2089
+ '#EXTINF:5,\n',
2090
+ 'main.mp4\n',
2091
+ ].join('');
2092
+
2093
+ fakeNetEngine
2094
+ .setResponseText('test:/master', master)
2095
+ .setResponseText('test:/audio', audio)
2096
+ .setResponseText('test:/video', video)
2097
+ .setResponseText('test:/text', text)
2098
+ .setResponseText('test:/iframe', iframe)
2099
+ .setResponseText('test:/main.vtt', vttText)
2100
+ .setResponseValue('test:/init.mp4', initSegmentData)
2101
+ .setResponseValue('test:/main.mp4', segmentData);
2102
+
2103
+ const actual = await parser.start('test:/master', playerInterface);
2104
+ await loadAllStreamsFor(actual);
2105
+
2106
+ expect(actual.imageStreams.length).toBe(1);
2107
+ expect(actual.textStreams.length).toBe(1);
2108
+ expect(actual.variants.length).toBe(1);
2109
+
2110
+ const thumbnails = actual.imageStreams[0];
2111
+
2112
+ expect(thumbnails.mimeType).toBe('application/mp4');
2113
+ expect(thumbnails.codecs).toBe('mjpg');
2114
+
2115
+ await thumbnails.createSegmentIndex();
2116
+ goog.asserts.assert(thumbnails.segmentIndex != null, 'Null segmentIndex!');
2117
+
2118
+ const firstThumbnailReference = thumbnails.segmentIndex.get(0);
2119
+ const secondThumbnailReference = thumbnails.segmentIndex.get(1);
2120
+ const thirdThumbnailReference = thumbnails.segmentIndex.get(2);
2121
+
2122
+ expect(firstThumbnailReference).not.toBe(null);
2123
+ expect(secondThumbnailReference).not.toBe(null);
2124
+ expect(thirdThumbnailReference).not.toBe(null);
2125
+ });
2126
+
1924
2127
  it('parse EXT-X-GAP', async () => {
1925
2128
  const master = [
1926
2129
  '#EXTM3U\n',
@@ -2587,6 +2790,106 @@ describe('HlsParser', () => {
2587
2790
  expect(actual).toEqual(manifest);
2588
2791
  });
2589
2792
 
2793
+ describe('When config.hls.disableCodecGuessing is set to true', () => {
2794
+ beforeEach(() => {
2795
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
2796
+ config.hls.disableCodecGuessing = true;
2797
+ parser.configure(config);
2798
+ });
2799
+
2800
+ it('gets codec info from media if omitted in playlist', async () => {
2801
+ const master = [
2802
+ '#EXTM3U\n',
2803
+ '#EXT-X-VERSION:3\n',
2804
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000\n',
2805
+ 'video\n',
2806
+ ].join('');
2807
+
2808
+ const media = [
2809
+ '#EXTM3U\n',
2810
+ '#EXT-X-VERSION:3\n',
2811
+ '#EXT-X-TARGETDURATION:5\n',
2812
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
2813
+ '#EXTINF:5,\n',
2814
+ 'video-0.ts\n',
2815
+ ].join('');
2816
+
2817
+ fakeNetEngine
2818
+ .setResponseText('test:/master', master)
2819
+ .setResponseText('test:/video', media)
2820
+ .setResponseValue('test:/video-0.ts', tsSegmentData);
2821
+
2822
+ const actual = await parser.start('test:/master', playerInterface);
2823
+ const variant = actual.variants[0];
2824
+
2825
+ expect(variant.audio).toBe(null);
2826
+ expect(variant.video).toBeDefined();
2827
+ expect(variant.video.codecs).toBe('avc1.42C01E');
2828
+ });
2829
+
2830
+ it('gets codecs from playlist if CODECS attribute present', async () => {
2831
+ const master = [
2832
+ '#EXTM3U\n',
2833
+ '#EXT-X-VERSION:3\n',
2834
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="foo"\n',
2835
+ 'video\n',
2836
+ ].join('');
2837
+
2838
+ const media = [
2839
+ '#EXTM3U\n',
2840
+ '#EXT-X-VERSION:3\n',
2841
+ '#EXT-X-TARGETDURATION:5\n',
2842
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
2843
+ '#EXTINF:5,\n',
2844
+ 'video-0.ts\n',
2845
+ ].join('');
2846
+
2847
+ fakeNetEngine
2848
+ .setResponseText('test:/master', master)
2849
+ .setResponseText('test:/video', media)
2850
+ .setResponseValue('test:/video-0.ts', tsSegmentData);
2851
+
2852
+ const actual = await parser.start('test:/master', playerInterface);
2853
+ const variant = actual.variants[0];
2854
+
2855
+ expect(variant.audio).toBe(null);
2856
+ expect(variant.video).toBeDefined();
2857
+ expect(variant.video.codecs).toBe('foo');
2858
+ });
2859
+
2860
+ it('falls back to default codecs if it could not find codec', async () => {
2861
+ const master = [
2862
+ '#EXTM3U\n',
2863
+ '#EXT-X-VERSION:3\n',
2864
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000\n',
2865
+ 'video\n',
2866
+ ].join('');
2867
+
2868
+ const media = [
2869
+ '#EXTM3U\n',
2870
+ '#EXT-X-VERSION:3\n',
2871
+ '#EXT-X-TARGETDURATION:5\n',
2872
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
2873
+ '#EXTINF:5,\n',
2874
+ 'video-0.ts\n',
2875
+ ].join('');
2876
+
2877
+ fakeNetEngine
2878
+ .setResponseText('test:/master', master)
2879
+ .setResponseText('test:/video', media)
2880
+ .setResponseValue('test:/video-0.ts', new Uint8Array([]));
2881
+
2882
+ const actual = await parser.start('test:/master', playerInterface);
2883
+ const variant = actual.variants[0];
2884
+
2885
+ expect(variant.video).toBeDefined();
2886
+
2887
+ const codecs = variant.video.codecs.split(',').map((c) => c.trim());
2888
+
2889
+ expect(codecs).toEqual(['avc1.42E01E', 'mp4a.40.2']);
2890
+ });
2891
+ });
2892
+
2590
2893
  describe('produces syncTime', () => {
2591
2894
  // Corresponds to "2000-01-01T00:00:00.00Z".
2592
2895
  // All the PROGRAM-DATE-TIME values in the tests below are at or after this.
@@ -2753,10 +3056,14 @@ describe('HlsParser', () => {
2753
3056
  'main.mp4',
2754
3057
  ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
2755
3058
  if (reference.startTime == 10) {
2756
- reference.partialReferences = [
2757
- makeReference(10, 12.5, syncTimeBase + 15),
2758
- makeReference(12.5, 15, syncTimeBase + 17.5),
2759
- ];
3059
+ const partialRef = makeReference(10, 12.5, syncTimeBase + 15);
3060
+ partialRef.partial = true;
3061
+ const partialRef2 = makeReference(12.5, 15, syncTimeBase + 17.5);
3062
+ partialRef2.partial = true;
3063
+ partialRef2.lastPartial = true;
3064
+
3065
+ reference.partialReferences = [partialRef, partialRef2];
3066
+ reference.allPartialSegments = true;
2760
3067
  }
2761
3068
  });
2762
3069
  });
@@ -2976,7 +3283,8 @@ describe('HlsParser', () => {
2976
3283
  const media = [
2977
3284
  '#EXTM3U\n',
2978
3285
  '#EXT-X-PLAYLIST-TYPE:VOD\n',
2979
- '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3286
+ '#EXT-X-BYTERANGE:616@0\n',
3287
+ '#EXT-X-MAP:URI="init.mp4"\n',
2980
3288
  '#EXTINF:5,\n',
2981
3289
  'main.mp4\n',
2982
3290
  '#EXT-X-MAP:URI="init2.mp4",BYTERANGE="616@0"\n',
@@ -3004,8 +3312,15 @@ describe('HlsParser', () => {
3004
3312
  // uri.
3005
3313
  const initSegments = Array.from(actualVideo.segmentIndex).map(
3006
3314
  (seg) => seg.initSegmentReference);
3007
- expect(initSegments[0].getUris()[0]).toBe('test:/init.mp4');
3008
- expect(initSegments[1].getUris()[0]).toBe('test:/init2.mp4');
3315
+ expect(initSegments.length).toBe(2);
3316
+ const firstInitSegment = initSegments[0];
3317
+ expect(firstInitSegment.getUris()[0]).toBe('test:/init.mp4');
3318
+ expect(firstInitSegment.startByte).toBe(0);
3319
+ expect(firstInitSegment.endByte).toBe(615);
3320
+ const secondInitSegment = initSegments[1];
3321
+ expect(secondInitSegment.getUris()[0]).toBe('test:/init2.mp4');
3322
+ expect(secondInitSegment.startByte).toBe(0);
3323
+ expect(secondInitSegment.endByte).toBe(615);
3009
3324
  });
3010
3325
 
3011
3326
  it('parses variants encrypted with AES-128', async () => {
@@ -3015,7 +3330,7 @@ describe('HlsParser', () => {
3015
3330
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
3016
3331
  'video\n',
3017
3332
  '#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
3018
- 'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
3333
+ 'RESOLUTION=960x540,FRAME-RATE=90,AUDIO="aud2"\n',
3019
3334
  'video2\n',
3020
3335
  '#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
3021
3336
  'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud3"\n',
@@ -4183,11 +4498,14 @@ describe('HlsParser', () => {
4183
4498
  it('parse variables master playlist', async () => {
4184
4499
  const master = [
4185
4500
  '#EXTM3U\n',
4186
- '#EXT-X-DEFINE:NAME="auth",VALUE="?token=1"\n',
4501
+ '#EXT-X-DEFINE:NAME="auth",VALUE="token=1"\n',
4502
+ '#EXT-X-DEFINE:QUERYPARAM="a"\n',
4503
+ '#EXT-X-DEFINE:QUERYPARAM="b"\n',
4187
4504
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4188
4505
  'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
4189
- 'audio.m3u8{$auth}\n',
4190
- '#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video.m3u8{$auth}"',
4506
+ 'audio.m3u8?{$auth}&a={$a}&b={$b}\n',
4507
+ '#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",',
4508
+ 'URI="video.m3u8?{$auth}&a={$a}&b={$b}"',
4191
4509
  ].join('');
4192
4510
 
4193
4511
  const media = [
@@ -4199,14 +4517,14 @@ describe('HlsParser', () => {
4199
4517
  ].join('');
4200
4518
 
4201
4519
  fakeNetEngine
4202
- .setResponseText('test:/host/master.m3u8', master)
4203
- .setResponseText('test:/host/audio.m3u8?token=1', media)
4204
- .setResponseText('test:/host/video.m3u8?token=1', media)
4520
+ .setResponseText('test:/host/master.m3u8?a=1&b=2', master)
4521
+ .setResponseText('test:/host/audio.m3u8?token=1&a=1&b=2', media)
4522
+ .setResponseText('test:/host/video.m3u8?token=1&a=1&b=2', media)
4205
4523
  .setResponseValue('test:/host/init.mp4', initSegmentData)
4206
4524
  .setResponseValue('test:/host/segment.mp4', segmentData);
4207
4525
 
4208
- const actual =
4209
- await parser.start('test:/host/master.m3u8', playerInterface);
4526
+ const actual = await parser.start(
4527
+ 'test:/host/master.m3u8?a=1&b=2', playerInterface);
4210
4528
  await loadAllStreamsFor(actual);
4211
4529
  const video = actual.variants[0].video;
4212
4530
  const audio = actual.variants[0].audio;
@@ -4232,26 +4550,28 @@ describe('HlsParser', () => {
4232
4550
  '#EXTM3U\n',
4233
4551
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4234
4552
  'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
4235
- 'audio.m3u8\n',
4236
- '#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video.m3u8"',
4553
+ 'audio.m3u8?fooParam=1\n',
4554
+ '#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video.m3u8?fooParam=1"',
4237
4555
  ].join('');
4238
4556
 
4239
4557
  const media = [
4240
4558
  '#EXTM3U\n',
4241
- '#EXT-X-DEFINE:NAME="auth",VALUE="?token=1"\n',
4559
+ '#EXT-X-DEFINE:NAME="auth",VALUE="token=1"\n',
4242
4560
  '#EXT-X-DEFINE:NAME="path",VALUE="test/"\n',
4561
+ '#EXT-X-DEFINE:QUERYPARAM="fooParam"\n',
4243
4562
  '#EXT-X-PLAYLIST-TYPE:VOD\n',
4244
4563
  '#EXT-X-MAP:URI="{$path}init.mp4"\n',
4245
4564
  '#EXTINF:5,\n',
4246
- '{$path}segment.mp4{$auth}',
4565
+ '{$path}segment.mp4?{$auth}&fooParam={$fooParam}',
4247
4566
  ].join('');
4248
4567
 
4249
4568
  fakeNetEngine
4250
4569
  .setResponseText('test:/host/master.m3u8', master)
4251
- .setResponseText('test:/host/audio.m3u8', media)
4252
- .setResponseText('test:/host/video.m3u8', media)
4570
+ .setResponseText('test:/host/audio.m3u8?fooParam=1', media)
4571
+ .setResponseText('test:/host/video.m3u8?fooParam=1', media)
4253
4572
  .setResponseValue('test:/host/test/init.mp4', initSegmentData)
4254
- .setResponseValue('test:/host/test/segment.mp4?token=1', segmentData);
4573
+ .setResponseValue('test:/host/test/segment.mp4?token=1&fooParam=1',
4574
+ segmentData);
4255
4575
 
4256
4576
  const actual =
4257
4577
  await parser.start('test:/host/master.m3u8', playerInterface);
@@ -4268,11 +4588,11 @@ describe('HlsParser', () => {
4268
4588
  // flow has gone well.
4269
4589
  const videoReference = Array.from(video.segmentIndex)[0];
4270
4590
  expect(videoReference.getUris())
4271
- .toEqual(['test:/host/test/segment.mp4?token=1']);
4591
+ .toEqual(['test:/host/test/segment.mp4?token=1&fooParam=1']);
4272
4592
 
4273
4593
  const audioReference = Array.from(audio.segmentIndex)[0];
4274
4594
  expect(audioReference.getUris())
4275
- .toEqual(['test:/host/test/segment.mp4?token=1']);
4595
+ .toEqual(['test:/host/test/segment.mp4?token=1&fooParam=1']);
4276
4596
  });
4277
4597
 
4278
4598
  it('import variables in media from master playlist', async () => {
@@ -4883,4 +5203,74 @@ describe('HlsParser', () => {
4883
5203
  expect(video.width).toBe(256);
4884
5204
  expect(video.height).toBe(110);
4885
5205
  });
5206
+
5207
+ it('supports ContentSteering', async () => {
5208
+ const master = [
5209
+ '#EXTM3U\n',
5210
+ '#EXT-X-CONTENT-STEERING:SERVER-URI="http://contentsteering",',
5211
+ 'PATHWAY-ID="a"\n',
5212
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a",LANGUAGE="eng",',
5213
+ 'URI="audio/a/media.m3u8"\n',
5214
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="b",LANGUAGE="eng",',
5215
+ 'URI="audio/b/media.m3u8"\n',
5216
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc,mp4a",',
5217
+ 'AUDIO="a",PATHWAY-ID="a"\n',
5218
+ 'a/media.m3u8\n',
5219
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc,mp4a",',
5220
+ 'AUDIO="b",PATHWAY-ID="b"\n',
5221
+ 'b/media.m3u8',
5222
+ ].join('');
5223
+
5224
+ const media = [
5225
+ '#EXTM3U\n',
5226
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
5227
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
5228
+ '#EXTINF:5,\n',
5229
+ '#EXT-X-BYTERANGE:121090@616\n',
5230
+ 'main.mp4',
5231
+ ].join('');
5232
+
5233
+ const contentSteering = JSON.stringify({
5234
+ 'VERSION': 1,
5235
+ 'TTL': 1,
5236
+ 'RELOAD-URI': 'http://contentsteering/update',
5237
+ 'PATHWAY-PRIORITY': [
5238
+ 'b',
5239
+ 'a',
5240
+ ],
5241
+ });
5242
+
5243
+ fakeNetEngine
5244
+ .setResponseText('http://master', master)
5245
+ .setResponseText('http://contentsteering', contentSteering)
5246
+ .setResponseText('http://master/a/media.m3u8', media)
5247
+ .setResponseText('http://master/b/media.m3u8', media)
5248
+ .setResponseText('http://master/audio/a/media.m3u8', media)
5249
+ .setResponseText('http://master/audio/b/media.m3u8', media)
5250
+ .setMaxUris(2);
5251
+
5252
+ /** @type {shaka.extern.Manifest} */
5253
+ const manifest = await parser.start('http://master', playerInterface);
5254
+ expect(manifest.variants.length).toBe(1);
5255
+
5256
+ const audio0 = manifest.variants[0].audio;
5257
+ await audio0.createSegmentIndex();
5258
+ goog.asserts.assert(audio0.segmentIndex, 'Null segmentIndex!');
5259
+ const audioSegment0 = Array.from(audio0.segmentIndex)[0];
5260
+ const audioUri0 = audioSegment0.getUris()[0];
5261
+ const audioUri1 = audioSegment0.getUris()[1];
5262
+
5263
+ expect(audioUri0).toBe('http://master/audio/b/main.mp4');
5264
+ expect(audioUri1).toBe('http://master/audio/a/main.mp4');
5265
+
5266
+ const video0 = manifest.variants[0].video;
5267
+ await video0.createSegmentIndex();
5268
+ goog.asserts.assert(video0.segmentIndex, 'Null segmentIndex!');
5269
+ const videoSegment0 = Array.from(video0.segmentIndex)[0];
5270
+ const videoUri0 = videoSegment0.getUris()[0];
5271
+ const videoUri1 = videoSegment0.getUris()[1];
5272
+
5273
+ expect(videoUri0).toBe('http://master/b/main.mp4');
5274
+ expect(videoUri1).toBe('http://master/a/main.mp4');
5275
+ });
4886
5276
  });