shaka-player 4.5.0 → 4.6.0

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 +98 -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 +35 -31
  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 +786 -736
  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 +1154 -1121
  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 +877 -811
  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 +1362 -1318
  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 +249 -83
  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 +83 -26
  70. package/lib/hls/hls_classes.js +28 -43
  71. package/lib/hls/hls_parser.js +761 -649
  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 +41 -21
  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 +993 -1502
  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 +377 -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
@@ -924,7 +924,7 @@ describe('HlsParser', () => {
924
924
  '#EXT-X-STREAM-INF:BANDWIDTH=200,',
925
925
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
926
926
  'video\n',
927
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="audio"\n',
927
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",NAME="audio"\n',
928
928
  ].join('');
929
929
 
930
930
  const media = [
@@ -941,6 +941,9 @@ describe('HlsParser', () => {
941
941
  manifest.addPartialVariant((variant) => {
942
942
  variant.addPartialStream(ContentType.VIDEO, (stream) => {
943
943
  stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
944
+ stream.language = 'en';
945
+ stream.originalLanguage = 'eng';
946
+ stream.label = 'audio';
944
947
  });
945
948
  });
946
949
  manifest.sequenceMode = sequenceMode;
@@ -1488,6 +1491,72 @@ describe('HlsParser', () => {
1488
1491
  expect(actual).toEqual(manifest);
1489
1492
  });
1490
1493
 
1494
+ it('parses manifest with video layout metadata', async () => {
1495
+ const master = [
1496
+ '#EXTM3U\n',
1497
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1498
+ 'URI="audio"\n',
1499
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1500
+ 'FORCED=YES,URI="text"\n',
1501
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1502
+ 'REQ-VIDEO-LAYOUT=CH-STEREO,RESOLUTION=960x540,FRAME-RATE=60,',
1503
+ 'AUDIO="aud1",SUBTITLES="sub1"\n',
1504
+ 'video\n',
1505
+ ].join('');
1506
+
1507
+ const media = [
1508
+ '#EXTM3U\n',
1509
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1510
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1511
+ '#EXTINF:5,\n',
1512
+ '#EXT-X-BYTERANGE:121090@616\n',
1513
+ 'main.mp4',
1514
+ ].join('');
1515
+
1516
+ const textMedia = [
1517
+ '#EXTM3U\n',
1518
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1519
+ '#EXTINF:5,\n',
1520
+ '#EXT-X-BYTERANGE:121090@616\n',
1521
+ 'main.vtt',
1522
+ ].join('');
1523
+
1524
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1525
+ manifest.anyTimeline();
1526
+ manifest.addPartialVariant((variant) => {
1527
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
1528
+ stream.mime('video/mp4', 'avc1');
1529
+ stream.videoLayout = 'CH-STEREO';
1530
+ });
1531
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
1532
+ stream.mime('audio/mp4', 'mp4a');
1533
+ });
1534
+ });
1535
+ manifest.addPartialTextStream((stream) => {
1536
+ stream.language = 'en';
1537
+ stream.originalLanguage = 'eng';
1538
+ stream.forced = true;
1539
+ stream.kind = TextStreamKind.SUBTITLE;
1540
+ stream.mime('text/vtt', '');
1541
+ });
1542
+ manifest.sequenceMode = sequenceMode;
1543
+ manifest.type = shaka.media.ManifestParser.HLS;
1544
+ });
1545
+
1546
+ fakeNetEngine
1547
+ .setResponseText('test:/master', master)
1548
+ .setResponseText('test:/audio', media)
1549
+ .setResponseText('test:/video', media)
1550
+ .setResponseText('test:/text', textMedia)
1551
+ .setResponseText('test:/main.vtt', vttText)
1552
+ .setResponseValue('test:/init.mp4', initSegmentData)
1553
+ .setResponseValue('test:/main.mp4', segmentData);
1554
+
1555
+ const actual = await parser.start('test:/master', playerInterface);
1556
+ await loadAllStreamsFor(actual);
1557
+ expect(actual).toEqual(manifest);
1558
+ });
1559
+
1491
1560
  it('parses manifest with SUBTITLES', async () => {
1492
1561
  const master = [
1493
1562
  '#EXTM3U\n',
@@ -1501,7 +1570,7 @@ describe('HlsParser', () => {
1501
1570
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1502
1571
  'video\n',
1503
1572
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1504
- 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub2"\n',
1573
+ 'RESOLUTION=960x540,FRAME-RATE=90,AUDIO="aud1",SUBTITLES="sub2"\n',
1505
1574
  'video\n',
1506
1575
  ].join('');
1507
1576
 
@@ -1921,6 +1990,100 @@ describe('HlsParser', () => {
1921
1990
  }
1922
1991
  });
1923
1992
 
1993
+ it('supports EXT-X-I-FRAME-STREAM-INF with mjpg codec', async () => {
1994
+ const master = [
1995
+ '#EXTM3U\n',
1996
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1997
+ 'URI="text"\n',
1998
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1999
+ 'CHANNELS="2",URI="audio"\n',
2000
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
2001
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
2002
+ 'video\n',
2003
+ '#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="mjpg",',
2004
+ 'URI="iframe"\n',
2005
+ '#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="avc",',
2006
+ 'URI="iframeAvc"\n',
2007
+ ].join('');
2008
+
2009
+ const video = [
2010
+ '#EXTM3U\n',
2011
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2012
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2013
+ '#EXTINF:5,\n',
2014
+ 'main.mp4\n',
2015
+ '#EXTINF:5,\n',
2016
+ 'main.mp4\n',
2017
+ '#EXTINF:5,\n',
2018
+ 'main.mp4\n',
2019
+ ].join('');
2020
+
2021
+ const audio = [
2022
+ '#EXTM3U\n',
2023
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2024
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2025
+ '#EXTINF:5,\n',
2026
+ 'main.mp4\n',
2027
+ '#EXTINF:5,\n',
2028
+ 'main.mp4\n',
2029
+ '#EXTINF:5,\n',
2030
+ 'main.mp4\n',
2031
+ ].join('');
2032
+
2033
+ const text = [
2034
+ '#EXTM3U\n',
2035
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2036
+ '#EXTINF:5,\n',
2037
+ '#EXT-X-BYTERANGE:121090@616\n',
2038
+ 'main.vtt',
2039
+ ].join('');
2040
+
2041
+ const iframe = [
2042
+ '#EXTM3U\n',
2043
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2044
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2045
+ '#EXTINF:5,\n',
2046
+ 'main.mp4\n',
2047
+ '#EXTINF:5,\n',
2048
+ 'main.mp4\n',
2049
+ '#EXTINF:5,\n',
2050
+ 'main.mp4\n',
2051
+ ].join('');
2052
+
2053
+ fakeNetEngine
2054
+ .setResponseText('test:/master', master)
2055
+ .setResponseText('test:/audio', audio)
2056
+ .setResponseText('test:/video', video)
2057
+ .setResponseText('test:/text', text)
2058
+ .setResponseText('test:/iframe', iframe)
2059
+ .setResponseText('test:/main.vtt', vttText)
2060
+ .setResponseValue('test:/init.mp4', initSegmentData)
2061
+ .setResponseValue('test:/main.mp4', segmentData);
2062
+
2063
+ const actual = await parser.start('test:/master', playerInterface);
2064
+ await loadAllStreamsFor(actual);
2065
+
2066
+ expect(actual.imageStreams.length).toBe(1);
2067
+ expect(actual.textStreams.length).toBe(1);
2068
+ expect(actual.variants.length).toBe(1);
2069
+
2070
+ const thumbnails = actual.imageStreams[0];
2071
+
2072
+ expect(thumbnails.mimeType).toBe('application/mp4');
2073
+ expect(thumbnails.codecs).toBe('mjpg');
2074
+
2075
+ await thumbnails.createSegmentIndex();
2076
+ goog.asserts.assert(thumbnails.segmentIndex != null, 'Null segmentIndex!');
2077
+
2078
+ const firstThumbnailReference = thumbnails.segmentIndex.get(0);
2079
+ const secondThumbnailReference = thumbnails.segmentIndex.get(1);
2080
+ const thirdThumbnailReference = thumbnails.segmentIndex.get(2);
2081
+
2082
+ expect(firstThumbnailReference).not.toBe(null);
2083
+ expect(secondThumbnailReference).not.toBe(null);
2084
+ expect(thirdThumbnailReference).not.toBe(null);
2085
+ });
2086
+
1924
2087
  it('parse EXT-X-GAP', async () => {
1925
2088
  const master = [
1926
2089
  '#EXTM3U\n',
@@ -2587,6 +2750,106 @@ describe('HlsParser', () => {
2587
2750
  expect(actual).toEqual(manifest);
2588
2751
  });
2589
2752
 
2753
+ describe('When config.hls.disableCodecGuessing is set to true', () => {
2754
+ beforeEach(() => {
2755
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
2756
+ config.hls.disableCodecGuessing = true;
2757
+ parser.configure(config);
2758
+ });
2759
+
2760
+ it('gets codec info from media if omitted in playlist', async () => {
2761
+ const master = [
2762
+ '#EXTM3U\n',
2763
+ '#EXT-X-VERSION:3\n',
2764
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000\n',
2765
+ 'video\n',
2766
+ ].join('');
2767
+
2768
+ const media = [
2769
+ '#EXTM3U\n',
2770
+ '#EXT-X-VERSION:3\n',
2771
+ '#EXT-X-TARGETDURATION:5\n',
2772
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
2773
+ '#EXTINF:5,\n',
2774
+ 'video-0.ts\n',
2775
+ ].join('');
2776
+
2777
+ fakeNetEngine
2778
+ .setResponseText('test:/master', master)
2779
+ .setResponseText('test:/video', media)
2780
+ .setResponseValue('test:/video-0.ts', tsSegmentData);
2781
+
2782
+ const actual = await parser.start('test:/master', playerInterface);
2783
+ const variant = actual.variants[0];
2784
+
2785
+ expect(variant.audio).toBe(null);
2786
+ expect(variant.video).toBeDefined();
2787
+ expect(variant.video.codecs).toBe('avc1.42C01E');
2788
+ });
2789
+
2790
+ it('gets codecs from playlist if CODECS attribute present', async () => {
2791
+ const master = [
2792
+ '#EXTM3U\n',
2793
+ '#EXT-X-VERSION:3\n',
2794
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="foo"\n',
2795
+ 'video\n',
2796
+ ].join('');
2797
+
2798
+ const media = [
2799
+ '#EXTM3U\n',
2800
+ '#EXT-X-VERSION:3\n',
2801
+ '#EXT-X-TARGETDURATION:5\n',
2802
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
2803
+ '#EXTINF:5,\n',
2804
+ 'video-0.ts\n',
2805
+ ].join('');
2806
+
2807
+ fakeNetEngine
2808
+ .setResponseText('test:/master', master)
2809
+ .setResponseText('test:/video', media)
2810
+ .setResponseValue('test:/video-0.ts', tsSegmentData);
2811
+
2812
+ const actual = await parser.start('test:/master', playerInterface);
2813
+ const variant = actual.variants[0];
2814
+
2815
+ expect(variant.audio).toBe(null);
2816
+ expect(variant.video).toBeDefined();
2817
+ expect(variant.video.codecs).toBe('foo');
2818
+ });
2819
+
2820
+ it('falls back to default codecs if it could not find codec', async () => {
2821
+ const master = [
2822
+ '#EXTM3U\n',
2823
+ '#EXT-X-VERSION:3\n',
2824
+ '#EXT-X-STREAM-INF:BANDWIDTH=2000000\n',
2825
+ 'video\n',
2826
+ ].join('');
2827
+
2828
+ const media = [
2829
+ '#EXTM3U\n',
2830
+ '#EXT-X-VERSION:3\n',
2831
+ '#EXT-X-TARGETDURATION:5\n',
2832
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
2833
+ '#EXTINF:5,\n',
2834
+ 'video-0.ts\n',
2835
+ ].join('');
2836
+
2837
+ fakeNetEngine
2838
+ .setResponseText('test:/master', master)
2839
+ .setResponseText('test:/video', media)
2840
+ .setResponseValue('test:/video-0.ts', new Uint8Array([]));
2841
+
2842
+ const actual = await parser.start('test:/master', playerInterface);
2843
+ const variant = actual.variants[0];
2844
+
2845
+ expect(variant.video).toBeDefined();
2846
+
2847
+ const codecs = variant.video.codecs.split(',').map((c) => c.trim());
2848
+
2849
+ expect(codecs).toEqual(['avc1.42E01E', 'mp4a.40.2']);
2850
+ });
2851
+ });
2852
+
2590
2853
  describe('produces syncTime', () => {
2591
2854
  // Corresponds to "2000-01-01T00:00:00.00Z".
2592
2855
  // All the PROGRAM-DATE-TIME values in the tests below are at or after this.
@@ -2753,10 +3016,14 @@ describe('HlsParser', () => {
2753
3016
  'main.mp4',
2754
3017
  ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
2755
3018
  if (reference.startTime == 10) {
2756
- reference.partialReferences = [
2757
- makeReference(10, 12.5, syncTimeBase + 15),
2758
- makeReference(12.5, 15, syncTimeBase + 17.5),
2759
- ];
3019
+ const partialRef = makeReference(10, 12.5, syncTimeBase + 15);
3020
+ partialRef.partial = true;
3021
+ const partialRef2 = makeReference(12.5, 15, syncTimeBase + 17.5);
3022
+ partialRef2.partial = true;
3023
+ partialRef2.lastPartial = true;
3024
+
3025
+ reference.partialReferences = [partialRef, partialRef2];
3026
+ reference.allPartialSegments = true;
2760
3027
  }
2761
3028
  });
2762
3029
  });
@@ -2976,7 +3243,8 @@ describe('HlsParser', () => {
2976
3243
  const media = [
2977
3244
  '#EXTM3U\n',
2978
3245
  '#EXT-X-PLAYLIST-TYPE:VOD\n',
2979
- '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3246
+ '#EXT-X-BYTERANGE:616@0\n',
3247
+ '#EXT-X-MAP:URI="init.mp4"\n',
2980
3248
  '#EXTINF:5,\n',
2981
3249
  'main.mp4\n',
2982
3250
  '#EXT-X-MAP:URI="init2.mp4",BYTERANGE="616@0"\n',
@@ -3004,8 +3272,15 @@ describe('HlsParser', () => {
3004
3272
  // uri.
3005
3273
  const initSegments = Array.from(actualVideo.segmentIndex).map(
3006
3274
  (seg) => seg.initSegmentReference);
3007
- expect(initSegments[0].getUris()[0]).toBe('test:/init.mp4');
3008
- expect(initSegments[1].getUris()[0]).toBe('test:/init2.mp4');
3275
+ expect(initSegments.length).toBe(2);
3276
+ const firstInitSegment = initSegments[0];
3277
+ expect(firstInitSegment.getUris()[0]).toBe('test:/init.mp4');
3278
+ expect(firstInitSegment.startByte).toBe(0);
3279
+ expect(firstInitSegment.endByte).toBe(615);
3280
+ const secondInitSegment = initSegments[1];
3281
+ expect(secondInitSegment.getUris()[0]).toBe('test:/init2.mp4');
3282
+ expect(secondInitSegment.startByte).toBe(0);
3283
+ expect(secondInitSegment.endByte).toBe(615);
3009
3284
  });
3010
3285
 
3011
3286
  it('parses variants encrypted with AES-128', async () => {
@@ -3015,7 +3290,7 @@ describe('HlsParser', () => {
3015
3290
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
3016
3291
  'video\n',
3017
3292
  '#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
3018
- 'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
3293
+ 'RESOLUTION=960x540,FRAME-RATE=90,AUDIO="aud2"\n',
3019
3294
  'video2\n',
3020
3295
  '#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
3021
3296
  'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud3"\n',
@@ -4183,11 +4458,14 @@ describe('HlsParser', () => {
4183
4458
  it('parse variables master playlist', async () => {
4184
4459
  const master = [
4185
4460
  '#EXTM3U\n',
4186
- '#EXT-X-DEFINE:NAME="auth",VALUE="?token=1"\n',
4461
+ '#EXT-X-DEFINE:NAME="auth",VALUE="token=1"\n',
4462
+ '#EXT-X-DEFINE:QUERYPARAM="a"\n',
4463
+ '#EXT-X-DEFINE:QUERYPARAM="b"\n',
4187
4464
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4188
4465
  '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}"',
4466
+ 'audio.m3u8?{$auth}&a={$a}&b={$b}\n',
4467
+ '#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",',
4468
+ 'URI="video.m3u8?{$auth}&a={$a}&b={$b}"',
4191
4469
  ].join('');
4192
4470
 
4193
4471
  const media = [
@@ -4199,14 +4477,14 @@ describe('HlsParser', () => {
4199
4477
  ].join('');
4200
4478
 
4201
4479
  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)
4480
+ .setResponseText('test:/host/master.m3u8?a=1&b=2', master)
4481
+ .setResponseText('test:/host/audio.m3u8?token=1&a=1&b=2', media)
4482
+ .setResponseText('test:/host/video.m3u8?token=1&a=1&b=2', media)
4205
4483
  .setResponseValue('test:/host/init.mp4', initSegmentData)
4206
4484
  .setResponseValue('test:/host/segment.mp4', segmentData);
4207
4485
 
4208
- const actual =
4209
- await parser.start('test:/host/master.m3u8', playerInterface);
4486
+ const actual = await parser.start(
4487
+ 'test:/host/master.m3u8?a=1&b=2', playerInterface);
4210
4488
  await loadAllStreamsFor(actual);
4211
4489
  const video = actual.variants[0].video;
4212
4490
  const audio = actual.variants[0].audio;
@@ -4232,26 +4510,28 @@ describe('HlsParser', () => {
4232
4510
  '#EXTM3U\n',
4233
4511
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4234
4512
  'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
4235
- 'audio.m3u8\n',
4236
- '#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video.m3u8"',
4513
+ 'audio.m3u8?fooParam=1\n',
4514
+ '#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video.m3u8?fooParam=1"',
4237
4515
  ].join('');
4238
4516
 
4239
4517
  const media = [
4240
4518
  '#EXTM3U\n',
4241
- '#EXT-X-DEFINE:NAME="auth",VALUE="?token=1"\n',
4519
+ '#EXT-X-DEFINE:NAME="auth",VALUE="token=1"\n',
4242
4520
  '#EXT-X-DEFINE:NAME="path",VALUE="test/"\n',
4521
+ '#EXT-X-DEFINE:QUERYPARAM="fooParam"\n',
4243
4522
  '#EXT-X-PLAYLIST-TYPE:VOD\n',
4244
4523
  '#EXT-X-MAP:URI="{$path}init.mp4"\n',
4245
4524
  '#EXTINF:5,\n',
4246
- '{$path}segment.mp4{$auth}',
4525
+ '{$path}segment.mp4?{$auth}&fooParam={$fooParam}',
4247
4526
  ].join('');
4248
4527
 
4249
4528
  fakeNetEngine
4250
4529
  .setResponseText('test:/host/master.m3u8', master)
4251
- .setResponseText('test:/host/audio.m3u8', media)
4252
- .setResponseText('test:/host/video.m3u8', media)
4530
+ .setResponseText('test:/host/audio.m3u8?fooParam=1', media)
4531
+ .setResponseText('test:/host/video.m3u8?fooParam=1', media)
4253
4532
  .setResponseValue('test:/host/test/init.mp4', initSegmentData)
4254
- .setResponseValue('test:/host/test/segment.mp4?token=1', segmentData);
4533
+ .setResponseValue('test:/host/test/segment.mp4?token=1&fooParam=1',
4534
+ segmentData);
4255
4535
 
4256
4536
  const actual =
4257
4537
  await parser.start('test:/host/master.m3u8', playerInterface);
@@ -4268,11 +4548,11 @@ describe('HlsParser', () => {
4268
4548
  // flow has gone well.
4269
4549
  const videoReference = Array.from(video.segmentIndex)[0];
4270
4550
  expect(videoReference.getUris())
4271
- .toEqual(['test:/host/test/segment.mp4?token=1']);
4551
+ .toEqual(['test:/host/test/segment.mp4?token=1&fooParam=1']);
4272
4552
 
4273
4553
  const audioReference = Array.from(audio.segmentIndex)[0];
4274
4554
  expect(audioReference.getUris())
4275
- .toEqual(['test:/host/test/segment.mp4?token=1']);
4555
+ .toEqual(['test:/host/test/segment.mp4?token=1&fooParam=1']);
4276
4556
  });
4277
4557
 
4278
4558
  it('import variables in media from master playlist', async () => {
@@ -4883,4 +5163,74 @@ describe('HlsParser', () => {
4883
5163
  expect(video.width).toBe(256);
4884
5164
  expect(video.height).toBe(110);
4885
5165
  });
5166
+
5167
+ it('supports ContentSteering', async () => {
5168
+ const master = [
5169
+ '#EXTM3U\n',
5170
+ '#EXT-X-CONTENT-STEERING:SERVER-URI="http://contentsteering",',
5171
+ 'PATHWAY-ID="a"\n',
5172
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a",LANGUAGE="eng",',
5173
+ 'URI="audio/a/media.m3u8"\n',
5174
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="b",LANGUAGE="eng",',
5175
+ 'URI="audio/b/media.m3u8"\n',
5176
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc,mp4a",',
5177
+ 'AUDIO="a",PATHWAY-ID="a"\n',
5178
+ 'a/media.m3u8\n',
5179
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc,mp4a",',
5180
+ 'AUDIO="b",PATHWAY-ID="b"\n',
5181
+ 'b/media.m3u8',
5182
+ ].join('');
5183
+
5184
+ const media = [
5185
+ '#EXTM3U\n',
5186
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
5187
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
5188
+ '#EXTINF:5,\n',
5189
+ '#EXT-X-BYTERANGE:121090@616\n',
5190
+ 'main.mp4',
5191
+ ].join('');
5192
+
5193
+ const contentSteering = JSON.stringify({
5194
+ 'VERSION': 1,
5195
+ 'TTL': 1,
5196
+ 'RELOAD-URI': 'http://contentsteering/update',
5197
+ 'PATHWAY-PRIORITY': [
5198
+ 'b',
5199
+ 'a',
5200
+ ],
5201
+ });
5202
+
5203
+ fakeNetEngine
5204
+ .setResponseText('http://master', master)
5205
+ .setResponseText('http://contentsteering', contentSteering)
5206
+ .setResponseText('http://master/a/media.m3u8', media)
5207
+ .setResponseText('http://master/b/media.m3u8', media)
5208
+ .setResponseText('http://master/audio/a/media.m3u8', media)
5209
+ .setResponseText('http://master/audio/b/media.m3u8', media)
5210
+ .setMaxUris(2);
5211
+
5212
+ /** @type {shaka.extern.Manifest} */
5213
+ const manifest = await parser.start('http://master', playerInterface);
5214
+ expect(manifest.variants.length).toBe(1);
5215
+
5216
+ const audio0 = manifest.variants[0].audio;
5217
+ await audio0.createSegmentIndex();
5218
+ goog.asserts.assert(audio0.segmentIndex, 'Null segmentIndex!');
5219
+ const audioSegment0 = Array.from(audio0.segmentIndex)[0];
5220
+ const audioUri0 = audioSegment0.getUris()[0];
5221
+ const audioUri1 = audioSegment0.getUris()[1];
5222
+
5223
+ expect(audioUri0).toBe('http://master/audio/b/main.mp4');
5224
+ expect(audioUri1).toBe('http://master/audio/a/main.mp4');
5225
+
5226
+ const video0 = manifest.variants[0].video;
5227
+ await video0.createSegmentIndex();
5228
+ goog.asserts.assert(video0.segmentIndex, 'Null segmentIndex!');
5229
+ const videoSegment0 = Array.from(video0.segmentIndex)[0];
5230
+ const videoUri0 = videoSegment0.getUris()[0];
5231
+ const videoUri1 = videoSegment0.getUris()[1];
5232
+
5233
+ expect(videoUri0).toBe('http://master/b/main.mp4');
5234
+ expect(videoUri1).toBe('http://master/a/main.mp4');
5235
+ });
4886
5236
  });