shaka-player 3.1.4 → 3.3.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/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +6 -5
  5. package/CHANGELOG.md +363 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/README.md +4 -4
  8. package/build/types/core +3 -0
  9. package/build/types/offline +1 -0
  10. package/build/types/polyfill +2 -0
  11. package/build/types/ui +2 -0
  12. package/demo/common/asset.js +9 -7
  13. package/demo/common/assets.js +28 -1
  14. package/demo/common/message_ids.js +15 -2
  15. package/demo/config.js +35 -2
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +14 -1
  18. package/demo/locales/source.json +56 -4
  19. package/demo/main.js +2 -1
  20. package/dist/controls.css +26 -2
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +24 -21
  23. package/dist/demo.compiled.debug.map +3 -3
  24. package/dist/demo.compiled.js +24 -21
  25. package/dist/demo.compiled.map +3 -3
  26. package/dist/deps.js +33 -25
  27. package/dist/locales.js +31 -29
  28. package/dist/receiver.compiled.debug.js +14 -12
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +14 -12
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +258 -20
  33. package/dist/shaka-player.compiled.debug.d.ts +258 -20
  34. package/dist/shaka-player.compiled.debug.externs.js +1005 -695
  35. package/dist/shaka-player.compiled.debug.js +720 -640
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +1005 -695
  38. package/dist/shaka-player.compiled.js +850 -817
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +274 -21
  41. package/dist/shaka-player.ui.debug.d.ts +274 -21
  42. package/dist/shaka-player.ui.debug.externs.js +766 -429
  43. package/dist/shaka-player.ui.debug.js +863 -765
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +766 -429
  46. package/dist/shaka-player.ui.js +1042 -996
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/bg-fetch-after.gv +29 -0
  49. package/docs/design/bg-fetch-after.gv.png +0 -0
  50. package/docs/design/bg-fetch-before.gv +18 -0
  51. package/docs/design/bg-fetch-before.gv.png +0 -0
  52. package/docs/design/bg-fetch.md +134 -0
  53. package/docs/tutorials/blob-url.md +15 -0
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/aria.js +6 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +29 -1
  60. package/externs/shaka/manifest.js +18 -2
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +36 -2
  64. package/externs/shaka/player.js +182 -13
  65. package/externs/shaka/text.js +24 -10
  66. package/externs/texttrack.js +3 -0
  67. package/externs/webcrypto.js +18 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/ad_manager.js +16 -15
  72. package/lib/ads/client_side_ad.js +8 -0
  73. package/lib/ads/client_side_ad_manager.js +14 -6
  74. package/lib/ads/server_side_ad.js +7 -0
  75. package/lib/ads/server_side_ad_manager.js +19 -2
  76. package/lib/cast/cast_proxy.js +8 -0
  77. package/lib/cast/cast_utils.js +7 -1
  78. package/lib/cea/cea608_data_channel.js +56 -56
  79. package/lib/cea/cea608_memory.js +33 -33
  80. package/lib/cea/cea708_service.js +43 -43
  81. package/lib/cea/cea708_window.js +38 -38
  82. package/lib/cea/cea_decoder.js +13 -14
  83. package/lib/cea/cea_utils.js +23 -23
  84. package/lib/cea/dtvcc_packet_builder.js +7 -7
  85. package/lib/cea/i_caption_decoder.js +2 -3
  86. package/lib/cea/i_cea_parser.js +2 -2
  87. package/lib/cea/mp4_cea_parser.js +6 -6
  88. package/lib/dash/dash_parser.js +53 -14
  89. package/lib/dash/segment_base.js +25 -1
  90. package/lib/dash/segment_list.js +18 -9
  91. package/lib/dash/segment_template.js +23 -10
  92. package/lib/hls/hls_parser.js +210 -30
  93. package/lib/media/closed_caption_parser.js +1 -2
  94. package/lib/media/drm_engine.js +162 -192
  95. package/lib/media/quality_observer.js +291 -0
  96. package/lib/media/segment_index.js +9 -1
  97. package/lib/media/segment_reference.js +37 -5
  98. package/lib/media/streaming_engine.js +48 -10
  99. package/lib/net/http_fetch_plugin.js +31 -11
  100. package/lib/net/http_plugin_utils.js +1 -0
  101. package/lib/net/http_xhr_plugin.js +40 -14
  102. package/lib/net/networking_engine.js +82 -18
  103. package/lib/offline/download_info.js +68 -0
  104. package/lib/offline/download_manager.js +14 -7
  105. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  106. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  107. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  108. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  109. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  110. package/lib/offline/manifest_converter.js +3 -1
  111. package/lib/offline/storage.js +404 -297
  112. package/lib/offline/stored_content_utils.js +2 -0
  113. package/lib/player.js +622 -153
  114. package/lib/polyfill/aria.js +82 -0
  115. package/lib/polyfill/random_uuid.js +52 -0
  116. package/lib/routing/walker.js +2 -3
  117. package/lib/text/cue.js +12 -0
  118. package/lib/text/srt_text_parser.js +4 -4
  119. package/lib/text/text_engine.js +2 -2
  120. package/lib/text/ttml_text_parser.js +25 -2
  121. package/lib/text/ui_text_displayer.js +2 -2
  122. package/lib/text/vtt_text_parser.js +2 -2
  123. package/lib/text/web_vtt_generator.js +55 -12
  124. package/lib/util/cmcd_manager.js +826 -0
  125. package/lib/util/error.js +12 -0
  126. package/lib/util/language_utils.js +12 -0
  127. package/lib/util/manifest_parser_utils.js +1 -0
  128. package/lib/util/mp4_box_parsers.js +20 -20
  129. package/lib/util/multi_map.js +8 -0
  130. package/lib/util/mutex.js +52 -0
  131. package/lib/util/periods.js +98 -64
  132. package/lib/util/player_configuration.js +31 -1
  133. package/lib/util/stream_utils.js +140 -85
  134. package/lib/util/xml_utils.js +31 -12
  135. package/package.json +1 -1
  136. package/roadmap.md +24 -22
  137. package/shaka-player.uncompiled.js +4 -0
  138. package/test/cast/cast_utils_unit.js +1 -0
  139. package/test/cea/cea608_memory_unit.js +1 -1
  140. package/test/cea/cea708_service_unit.js +8 -8
  141. package/test/cea/cea708_window_unit.js +6 -6
  142. package/test/cea/cea_decoder_unit.js +1 -1
  143. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  144. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  145. package/test/dash/dash_parser_live_unit.js +101 -1
  146. package/test/dash/dash_parser_manifest_unit.js +78 -2
  147. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  148. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  149. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  150. package/test/hls/hls_live_unit.js +2 -0
  151. package/test/hls/hls_parser_unit.js +252 -0
  152. package/test/media/drm_engine_integration.js +86 -12
  153. package/test/media/drm_engine_unit.js +310 -450
  154. package/test/media/quality_observer_unit.js +139 -0
  155. package/test/media/segment_reference_unit.js +16 -1
  156. package/test/media/streaming_engine_integration.js +2 -1
  157. package/test/media/streaming_engine_unit.js +2 -0
  158. package/test/net/http_plugin_unit.js +20 -15
  159. package/test/offline/manifest_convert_unit.js +2 -0
  160. package/test/offline/storage_integration.js +117 -23
  161. package/test/player_integration.js +151 -0
  162. package/test/player_src_equals_integration.js +52 -0
  163. package/test/player_unit.js +11 -33
  164. package/test/test/assets/chapters.srt +11 -0
  165. package/test/test/assets/chapters.vtt +10 -0
  166. package/test/test/util/cea_utils.js +12 -12
  167. package/test/test/util/dash_parser_util.js +4 -0
  168. package/test/test/util/fake_ad.js +7 -0
  169. package/test/test/util/fake_ad_manager.js +7 -0
  170. package/test/test/util/fake_drm_engine.js +9 -2
  171. package/test/test/util/manifest_generator.js +5 -2
  172. package/test/test/util/manifest_parser_util.js +3 -1
  173. package/test/test/util/test_scheme.js +14 -1
  174. package/test/text/ttml_text_parser_unit.js +47 -0
  175. package/test/text/web_vtt_generator_unit.js +81 -7
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +277 -0
  178. package/test/util/stream_utils_unit.js +118 -10
  179. package/ui/airplay_button.js +2 -1
  180. package/ui/audio_language_selection.js +9 -5
  181. package/ui/cast_button.js +5 -6
  182. package/ui/constants.js +0 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +41 -7
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +16 -8
  189. package/ui/fullscreen_button.js +2 -3
  190. package/ui/language_utils.js +1 -1
  191. package/ui/less/buttons.less +0 -4
  192. package/ui/less/containers.less +74 -0
  193. package/ui/less/overflow_menu.less +0 -10
  194. package/ui/less/tooltip.less +97 -0
  195. package/ui/locales/en.json +1 -0
  196. package/ui/locales/source.json +4 -0
  197. package/ui/loop_button.js +6 -3
  198. package/ui/mute_button.js +3 -4
  199. package/ui/overflow_menu.js +7 -28
  200. package/ui/pip_button.js +10 -7
  201. package/ui/play_button.js +2 -2
  202. package/ui/playback_rate_selection.js +11 -15
  203. package/ui/presentation_time.js +1 -3
  204. package/ui/resolution_selection.js +11 -7
  205. package/ui/rewind_button.js +17 -9
  206. package/ui/seek_bar.js +6 -6
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/spacer.js +1 -1
  210. package/ui/statistics_button.js +253 -0
  211. package/ui/text_selection.js +19 -15
  212. package/ui/ui.js +30 -0
  213. package/ui/ui_utils.js +1 -1
  214. package/ui/volume_bar.js +2 -4
@@ -39,6 +39,8 @@ describe('DashParser Live', () => {
39
39
  parser.configure(shaka.util.PlayerConfiguration.createDefault().manifest);
40
40
  playerInterface = {
41
41
  networkingEngine: fakeNetEngine,
42
+ modifyManifestRequest: (request, manifestInfo) => {},
43
+ modifySegmentRequest: (request, segmentInfo) => {},
42
44
  filter: (manifest) => Promise.resolve(),
43
45
  makeTextStreamsForClosedCaptions: (manifest) => {},
44
46
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -364,7 +366,7 @@ describe('DashParser Live', () => {
364
366
  });
365
367
  }
366
368
 
367
- it('can add Periods', async () => {
369
+ it('can add Periods with SegmentTemplate', async () => {
368
370
  const template1 = [
369
371
  '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
370
372
  ' suggestedPresentationDelay="PT5S"',
@@ -440,6 +442,104 @@ describe('DashParser Live', () => {
440
442
  // two segments (10-14s).
441
443
  expect(stream.segmentIndex.find(9)).not.toBe(null);
442
444
  expect(stream.segmentIndex.find(13)).not.toBe(null);
445
+
446
+ stream.closeSegmentIndex();
447
+ await stream.createSegmentIndex();
448
+
449
+ expect(stream.segmentIndex.find(9)).not.toBe(null);
450
+ expect(stream.segmentIndex.find(13)).not.toBe(null);
451
+ });
452
+
453
+ it('can add Periods with SegmentList', async () => {
454
+ const list1 = [
455
+ '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
456
+ ' suggestedPresentationDelay="PT5S"',
457
+ ' minimumUpdatePeriod="PT%(updateTime)dS">',
458
+ ' <Period id="1">',
459
+ ' <AdaptationSet mimeType="video/mp4">',
460
+ ' <Representation id="1" bandwidth="500">',
461
+ ' <BaseURL>http://example.com</BaseURL>',
462
+ ' <SegmentList>',
463
+ ' <SegmentURL media="s1.mp4" />',
464
+ ' <SegmentURL media="s2.mp4" />',
465
+ ' <SegmentURL media="s3.mp4" />',
466
+ ' <SegmentTimeline>',
467
+ ' <S d="10" t="0" r="2"/>',
468
+ ' </SegmentTimeline>',
469
+ ' </SegmentList>',
470
+ ' </Representation>',
471
+ ' </AdaptationSet>',
472
+ ' </Period>',
473
+ '</MPD>',
474
+ ].join('\n');
475
+ const list2 = [
476
+ '<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
477
+ ' suggestedPresentationDelay="PT5S"',
478
+ ' minimumUpdatePeriod="PT%(updateTime)dS">',
479
+ ' <Period id="1" duration="PT40S">',
480
+ ' <AdaptationSet mimeType="video/mp4">',
481
+ ' <Representation id="1" bandwidth="500">',
482
+ ' <BaseURL>http://example.com</BaseURL>',
483
+ ' <SegmentList>',
484
+ ' <SegmentURL media="s1.mp4" />',
485
+ ' <SegmentURL media="s2.mp4" />',
486
+ ' <SegmentURL media="s3.mp4" />',
487
+ ' <SegmentURL media="s4.mp4" />',
488
+ ' <SegmentTimeline>',
489
+ ' <S d="10" t="0" r="3"/>',
490
+ ' </SegmentTimeline>',
491
+ ' </SegmentList>',
492
+ ' </Representation>',
493
+ ' </AdaptationSet>',
494
+ ' </Period>',
495
+ ' <Period id="2">',
496
+ ' <AdaptationSet mimeType="video/mp4">',
497
+ ' <Representation id="2" bandwidth="500">',
498
+ ' <BaseURL>http://example.com</BaseURL>',
499
+ ' <SegmentList>',
500
+ ' <SegmentURL media="s1.mp4" />',
501
+ ' <SegmentURL media="s2.mp4" />',
502
+ ' <SegmentTimeline>',
503
+ ' <S d="10" t="0" r="1"/>',
504
+ ' </SegmentTimeline>',
505
+ ' </SegmentList>',
506
+ ' </Representation>',
507
+ ' </AdaptationSet>',
508
+ ' </Period>',
509
+ '</MPD>',
510
+ ].join('\n');
511
+ const firstManifest = sprintf(list1, {updateTime: updateTime});
512
+ const secondManifest = sprintf(list2, {updateTime: updateTime});
513
+
514
+ fakeNetEngine.setResponseText('dummy://foo', firstManifest);
515
+ // First three segments should exist.
516
+ Date.now = () => 5;
517
+
518
+ const manifest = await parser.start('dummy://foo', playerInterface);
519
+ const variant = manifest.variants[0];
520
+ const stream = variant.video;
521
+ await stream.createSegmentIndex();
522
+
523
+ // First three segments exist, but not the fourth.
524
+ expect(stream.segmentIndex.find(25)).not.toBe(null);
525
+ expect(stream.segmentIndex.find(45)).toBe(null);
526
+
527
+ fakeNetEngine.setResponseText('dummy://foo', secondManifest);
528
+ Date.now = () => 25;
529
+
530
+ await updateManifest();
531
+
532
+ // The update should have affected the same variant object we captured
533
+ // before. Now the entire first period should exist (0-40s), plus the next
534
+ // two segments of the second period(40-60s).
535
+ expect(stream.segmentIndex.find(25)).not.toBe(null);
536
+ expect(stream.segmentIndex.find(45)).not.toBe(null);
537
+
538
+ stream.closeSegmentIndex();
539
+ await stream.createSegmentIndex();
540
+
541
+ expect(stream.segmentIndex.find(25)).not.toBe(null);
542
+ expect(stream.segmentIndex.find(45)).not.toBe(null);
443
543
  });
444
544
 
445
545
  it('uses redirect URL for manifest BaseURL and updates', async () => {
@@ -36,7 +36,7 @@ describe('DashParser Manifest', () => {
36
36
  /** @type {!ArrayBuffer} */
37
37
  let mp4Index;
38
38
 
39
- /** @type {!string} */
39
+ /** @type {string} */
40
40
  const thumbnailScheme = 'http://dashif.org/guidelines/thumbnail_tile';
41
41
  /**
42
42
  * CICP scheme. parameter must be one of the following: "ColourPrimaries",
@@ -57,6 +57,8 @@ describe('DashParser Manifest', () => {
57
57
  onEventSpy = jasmine.createSpy('onEvent');
58
58
  playerInterface = {
59
59
  networkingEngine: fakeNetEngine,
60
+ modifyManifestRequest: (request, manifestInfo) => {},
61
+ modifySegmentRequest: (request, segmentInfo) => {},
60
62
  filter: (manifest) => Promise.resolve(),
61
63
  makeTextStreamsForClosedCaptions: (manifest) => {},
62
64
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -1798,6 +1800,48 @@ describe('DashParser Manifest', () => {
1798
1800
  expect(manifest.presentationTimeline).toBeTruthy();
1799
1801
  });
1800
1802
 
1803
+ it('Invokes manifestPreprocessor in config', async () => {
1804
+ const manifestText = [
1805
+ '<MPD minBufferTime="PT75S">',
1806
+ ' <Period id="1" duration="PT30S">',
1807
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1808
+ ' <Representation id="video-sd" width="640" height="480">',
1809
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1810
+ ' <SegmentBase indexRange="100-200" />',
1811
+ ' </Representation>',
1812
+ ' </AdaptationSet>',
1813
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1814
+ ' <Representation id="audio-en">',
1815
+ ' <BaseURL>a-en.mp4</BaseURL>',
1816
+ ' <SegmentBase indexRange="100-200" />',
1817
+ ' </Representation>',
1818
+ ' </AdaptationSet>',
1819
+ ' <AdaptationSet mimeType="text/vtt" lang="de">',
1820
+ ' <Representation>',
1821
+ ' <BaseURL>http://example.com/de.vtt</BaseURL>',
1822
+ ' </Representation>',
1823
+ ' </AdaptationSet>',
1824
+ ' </Period>',
1825
+ '</MPD>',
1826
+ ].join('\n');
1827
+
1828
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1829
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
1830
+ config.dash.manifestPreprocessor = (mpd) => {
1831
+ const selector = 'AdaptationSet[mimeType="text/vtt"';
1832
+ const vttElements = mpd.querySelectorAll(selector);
1833
+ for (const element of vttElements) {
1834
+ element.parentNode.removeChild(element);
1835
+ }
1836
+ };
1837
+ parser.configure(config);
1838
+
1839
+ /** @type {shaka.extern.Manifest} */
1840
+ const manifest = await parser.start('dummy://foo', playerInterface);
1841
+ const stream = manifest.textStreams[0];
1842
+ expect(stream).toBeUndefined();
1843
+ });
1844
+
1801
1845
  it('converts Accessibility element to "kind"', async () => {
1802
1846
  const manifestText = [
1803
1847
  '<MPD minBufferTime="PT75S">',
@@ -1830,7 +1874,7 @@ describe('DashParser Manifest', () => {
1830
1874
  expect(textStream.kind).toBe('caption');
1831
1875
  });
1832
1876
 
1833
- it('converts Roles element to "forced"', async () => {
1877
+ it('converts Roles element to "forced" (old role)', async () => {
1834
1878
  const manifestText = [
1835
1879
  '<MPD minBufferTime="PT75S">',
1836
1880
  ' <Period id="1" duration="PT30S">',
@@ -1862,6 +1906,38 @@ describe('DashParser Manifest', () => {
1862
1906
  expect(textStream.forced).toBe(true);
1863
1907
  });
1864
1908
 
1909
+ it('converts Roles element to "forced"', async () => {
1910
+ const manifestText = [
1911
+ '<MPD minBufferTime="PT75S">',
1912
+ ' <Period id="1" duration="PT30S">',
1913
+ ' <AdaptationSet id="1" contentType="text">',
1914
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1915
+ ' value="captions" />',
1916
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1917
+ ' value="forced-subtitle" />',
1918
+ ' <Accessibility schemeIdUri="foobar" value="bar" />',
1919
+ ' <Representation id="text-en" mimeType="text/webvtt">',
1920
+ ' <BaseURL>t-en.vtt</BaseURL>',
1921
+ ' </Representation>',
1922
+ ' </AdaptationSet>',
1923
+ ' <AdaptationSet id="1" mimeType="video/mp4">',
1924
+ ' <Representation id="video-sd" width="640" height="480">',
1925
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1926
+ ' <SegmentBase indexRange="100-200" />',
1927
+ ' </Representation>',
1928
+ ' </AdaptationSet>',
1929
+ ' </Period>',
1930
+ '</MPD>',
1931
+ ].join('\n');
1932
+
1933
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1934
+ /** @type {shaka.extern.Manifest} */
1935
+ const manifest = await parser.start('dummy://foo', playerInterface);
1936
+ const textStream = manifest.textStreams[0];
1937
+ expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
1938
+ expect(textStream.forced).toBe(true);
1939
+ });
1940
+
1865
1941
  it('supports HDR signaling via profiles', async () => {
1866
1942
  // (DASH-IF IOP v4.3 10.3.3.)
1867
1943
  const hdrProfile =
@@ -35,6 +35,8 @@ describe('DashParser SegmentBase', () => {
35
35
 
36
36
  playerInterface = {
37
37
  networkingEngine: fakeNetEngine,
38
+ modifyManifestRequest: (request, manifestInfo) => {},
39
+ modifySegmentRequest: (request, segmentInfo) => {},
38
40
  filter: (manifest) => Promise.resolve(),
39
41
  makeTextStreamsForClosedCaptions: (manifest) => {},
40
42
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -344,6 +344,8 @@ describe('DashParser SegmentList', () => {
344
344
 
345
345
  const playerInterface = {
346
346
  networkingEngine: networkingEngine,
347
+ modifyManifestRequest: (request, manifestInfo) => {},
348
+ modifySegmentRequest: (request, segmentInfo) => {},
347
349
  filter: () => {},
348
350
  makeTextStreamsForClosedCaptions: (manifest) => {},
349
351
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -374,4 +376,3 @@ describe('DashParser SegmentList', () => {
374
376
  expect(actual).toEqual(expected);
375
377
  });
376
378
  });
377
-
@@ -46,6 +46,8 @@ describe('DashParser SegmentTemplate', () => {
46
46
 
47
47
  playerInterface = {
48
48
  networkingEngine: fakeNetEngine,
49
+ modifyManifestRequest: (request, manifestInfo) => {},
50
+ modifySegmentRequest: (request, segmentInfo) => {},
49
51
  filter: (manifest) => Promise.resolve(),
50
52
  makeTextStreamsForClosedCaptions: (manifest) => {},
51
53
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -585,4 +587,3 @@ describe('DashParser SegmentTemplate', () => {
585
587
  });
586
588
  });
587
589
  });
588
-
@@ -116,6 +116,8 @@ describe('HlsParser live', () => {
116
116
 
117
117
  config = shaka.util.PlayerConfiguration.createDefault().manifest;
118
118
  playerInterface = {
119
+ modifyManifestRequest: (request, manifestInfo) => {},
120
+ modifySegmentRequest: (request, segmentInfo) => {},
119
121
  filter: () => Promise.resolve(),
120
122
  makeTextStreamsForClosedCaptions: (manifest) => {},
121
123
  networkingEngine: fakeNetEngine,
@@ -94,6 +94,8 @@ describe('HlsParser', () => {
94
94
  config = shaka.util.PlayerConfiguration.createDefault().manifest;
95
95
  onEventSpy = jasmine.createSpy('onEvent');
96
96
  playerInterface = {
97
+ modifyManifestRequest: (request, manifestInfo) => {},
98
+ modifySegmentRequest: (request, segmentInfo) => {},
97
99
  filter: () => Promise.resolve(),
98
100
  makeTextStreamsForClosedCaptions: (manifest) => {},
99
101
  networkingEngine: fakeNetEngine,
@@ -1318,6 +1320,103 @@ describe('HlsParser', () => {
1318
1320
  expect(actual.variants[0].video).toBeTruthy();
1319
1321
  });
1320
1322
 
1323
+ it('parse image streams', async () => {
1324
+ const master = [
1325
+ '#EXTM3U\n',
1326
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1327
+ 'URI="text"\n',
1328
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1329
+ 'CHANNELS="2",URI="audio"\n',
1330
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1331
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1332
+ 'video\n',
1333
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1334
+ 'URI="image"\n',
1335
+ ].join('');
1336
+
1337
+ const video = [
1338
+ '#EXTM3U\n',
1339
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1340
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1341
+ '#EXTINF:5,\n',
1342
+ 'main.mp4\n',
1343
+ '#EXTINF:5,\n',
1344
+ 'main.mp4\n',
1345
+ '#EXTINF:5,\n',
1346
+ 'main.mp4\n',
1347
+ ].join('');
1348
+
1349
+ const audio = [
1350
+ '#EXTM3U\n',
1351
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1352
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1353
+ '#EXTINF:5,\n',
1354
+ 'main.mp4\n',
1355
+ '#EXTINF:5,\n',
1356
+ 'main.mp4\n',
1357
+ '#EXTINF:5,\n',
1358
+ 'main.mp4\n',
1359
+ ].join('');
1360
+
1361
+ const text = [
1362
+ '#EXTM3U\n',
1363
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1364
+ '#EXTINF:5,\n',
1365
+ '#EXT-X-BYTERANGE:121090@616\n',
1366
+ 'main.vtt',
1367
+ ].join('');
1368
+
1369
+ const image = [
1370
+ '#EXTM3U\n',
1371
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1372
+ '#EXTINF:5,\n',
1373
+ 'image.jpg\n',
1374
+ '#EXTINF:5,\n',
1375
+ '#EXT-X-TILES:RESOLUTION=640x360,LAYOUT=5x2,DURATION=6.006\n',
1376
+ 'image.jpg\n',
1377
+ '#EXTINF:5,\n',
1378
+ 'image.jpg\n',
1379
+ ].join('');
1380
+
1381
+ fakeNetEngine
1382
+ .setResponseText('test:/master', master)
1383
+ .setResponseText('test:/audio', audio)
1384
+ .setResponseText('test:/video', video)
1385
+ .setResponseText('test:/text', text)
1386
+ .setResponseText('test:/image', image)
1387
+ .setResponseText('test:/main.vtt', vttText)
1388
+ .setResponseValue('test:/init.mp4', initSegmentData)
1389
+ .setResponseValue('test:/main.mp4', segmentData);
1390
+
1391
+ const actual = await parser.start('test:/master', playerInterface);
1392
+
1393
+ expect(actual.imageStreams.length).toBe(1);
1394
+ expect(actual.textStreams.length).toBe(1);
1395
+ expect(actual.variants.length).toBe(1);
1396
+
1397
+ const thumbnails = actual.imageStreams[0];
1398
+
1399
+ await thumbnails.createSegmentIndex();
1400
+ goog.asserts.assert(thumbnails.segmentIndex != null, 'Null segmentIndex!');
1401
+
1402
+ const firstThumbnailReference = thumbnails.segmentIndex.get(0);
1403
+ const secondThumbnailReference = thumbnails.segmentIndex.get(1);
1404
+ const thirdThumbnailReference = thumbnails.segmentIndex.get(2);
1405
+
1406
+ expect(firstThumbnailReference).not.toBe(null);
1407
+ expect(secondThumbnailReference).not.toBe(null);
1408
+ expect(thirdThumbnailReference).not.toBe(null);
1409
+ if (firstThumbnailReference) {
1410
+ expect(firstThumbnailReference.getTilesLayout()).toBe('1x1');
1411
+ }
1412
+ if (secondThumbnailReference) {
1413
+ expect(secondThumbnailReference.getTilesLayout()).toBe('5x2');
1414
+ }
1415
+ if (thirdThumbnailReference) {
1416
+ expect(thirdThumbnailReference.getTilesLayout()).toBe('1x1');
1417
+ }
1418
+ });
1419
+
1321
1420
  it('Disable audio does not create audio streams', async () => {
1322
1421
  const master = [
1323
1422
  '#EXTM3U\n',
@@ -1328,6 +1427,8 @@ describe('HlsParser', () => {
1328
1427
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1329
1428
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1330
1429
  'video\n',
1430
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1431
+ 'URI="image"\n',
1331
1432
  ].join('');
1332
1433
 
1333
1434
  const video = [
@@ -1360,11 +1461,23 @@ describe('HlsParser', () => {
1360
1461
  'main.vtt',
1361
1462
  ].join('');
1362
1463
 
1464
+ const image = [
1465
+ '#EXTM3U\n',
1466
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1467
+ '#EXTINF:5,\n',
1468
+ 'image.jpg\n',
1469
+ '#EXTINF:5,\n',
1470
+ 'image.jpg\n',
1471
+ '#EXTINF:5,\n',
1472
+ 'image.jpg\n',
1473
+ ].join('');
1474
+
1363
1475
  fakeNetEngine
1364
1476
  .setResponseText('test:/master', master)
1365
1477
  .setResponseText('test:/audio', audio)
1366
1478
  .setResponseText('test:/video', video)
1367
1479
  .setResponseText('test:/text', text)
1480
+ .setResponseText('test:/image', image)
1368
1481
  .setResponseText('test:/main.vtt', vttText)
1369
1482
  .setResponseValue('test:/init.mp4', initSegmentData)
1370
1483
  .setResponseValue('test:/main.mp4', segmentData);
@@ -1389,6 +1502,8 @@ describe('HlsParser', () => {
1389
1502
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1390
1503
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1391
1504
  'video\n',
1505
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1506
+ 'URI="image"\n',
1392
1507
  ].join('');
1393
1508
 
1394
1509
  const video = [
@@ -1421,11 +1536,23 @@ describe('HlsParser', () => {
1421
1536
  'main.vtt',
1422
1537
  ].join('');
1423
1538
 
1539
+ const image = [
1540
+ '#EXTM3U\n',
1541
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1542
+ '#EXTINF:5,\n',
1543
+ 'image.jpg\n',
1544
+ '#EXTINF:5,\n',
1545
+ 'image.jpg\n',
1546
+ '#EXTINF:5,\n',
1547
+ 'image.jpg\n',
1548
+ ].join('');
1549
+
1424
1550
  fakeNetEngine
1425
1551
  .setResponseText('test:/master', master)
1426
1552
  .setResponseText('test:/audio', audio)
1427
1553
  .setResponseText('test:/video', video)
1428
1554
  .setResponseText('test:/text', text)
1555
+ .setResponseText('test:/image', image)
1429
1556
  .setResponseText('test:/main.vtt', vttText)
1430
1557
  .setResponseValue('test:/init.mp4', initSegmentData)
1431
1558
  .setResponseValue('test:/main.mp4', segmentData);
@@ -1450,6 +1577,8 @@ describe('HlsParser', () => {
1450
1577
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1451
1578
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1452
1579
  'video\n',
1580
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1581
+ 'URI="image"\n',
1453
1582
  ].join('');
1454
1583
 
1455
1584
  const video = [
@@ -1482,11 +1611,23 @@ describe('HlsParser', () => {
1482
1611
  'main.vtt',
1483
1612
  ].join('');
1484
1613
 
1614
+ const image = [
1615
+ '#EXTM3U\n',
1616
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1617
+ '#EXTINF:5,\n',
1618
+ 'image.jpg\n',
1619
+ '#EXTINF:5,\n',
1620
+ 'image.jpg\n',
1621
+ '#EXTINF:5,\n',
1622
+ 'image.jpg\n',
1623
+ ].join('');
1624
+
1485
1625
  fakeNetEngine
1486
1626
  .setResponseText('test:/master', master)
1487
1627
  .setResponseText('test:/audio', audio)
1488
1628
  .setResponseText('test:/video', video)
1489
1629
  .setResponseText('test:/text', text)
1630
+ .setResponseText('test:/image', image)
1490
1631
  .setResponseText('test:/main.vtt', vttText)
1491
1632
  .setResponseValue('test:/init.mp4', initSegmentData)
1492
1633
  .setResponseValue('test:/main.mp4', segmentData);
@@ -1500,6 +1641,80 @@ describe('HlsParser', () => {
1500
1641
  expect(stream).toBeUndefined();
1501
1642
  });
1502
1643
 
1644
+ it('Disable thumbnails does not create image streams', async () => {
1645
+ const master = [
1646
+ '#EXTM3U\n',
1647
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1648
+ 'URI="text"\n',
1649
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1650
+ 'CHANNELS="2",URI="audio"\n',
1651
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1652
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1653
+ 'video\n',
1654
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1655
+ 'URI="image"\n',
1656
+ ].join('');
1657
+
1658
+ const video = [
1659
+ '#EXTM3U\n',
1660
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1661
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1662
+ '#EXTINF:5,\n',
1663
+ 'main.mp4\n',
1664
+ '#EXTINF:5,\n',
1665
+ 'main.mp4\n',
1666
+ '#EXTINF:5,\n',
1667
+ 'main.mp4\n',
1668
+ ].join('');
1669
+
1670
+ const audio = [
1671
+ '#EXTM3U\n',
1672
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1673
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1674
+ '#EXTINF:5,\n',
1675
+ 'main.mp4\n',
1676
+ '#EXTINF:5,\n',
1677
+ 'main.mp4\n',
1678
+ ].join('');
1679
+
1680
+ const text = [
1681
+ '#EXTM3U\n',
1682
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1683
+ '#EXTINF:5,\n',
1684
+ '#EXT-X-BYTERANGE:121090@616\n',
1685
+ 'main.vtt',
1686
+ ].join('');
1687
+
1688
+ const image = [
1689
+ '#EXTM3U\n',
1690
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1691
+ '#EXTINF:5,\n',
1692
+ 'image.jpg\n',
1693
+ '#EXTINF:5,\n',
1694
+ 'image.jpg\n',
1695
+ '#EXTINF:5,\n',
1696
+ 'image.jpg\n',
1697
+ ].join('');
1698
+
1699
+ fakeNetEngine
1700
+ .setResponseText('test:/master', master)
1701
+ .setResponseText('test:/audio', audio)
1702
+ .setResponseText('test:/video', video)
1703
+ .setResponseText('test:/text', text)
1704
+ .setResponseText('test:/image', image)
1705
+ .setResponseText('test:/main.vtt', vttText)
1706
+ .setResponseValue('test:/init.mp4', initSegmentData)
1707
+ .setResponseValue('test:/main.mp4', segmentData);
1708
+
1709
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
1710
+ config.disableThumbnails = true;
1711
+ parser.configure(config);
1712
+
1713
+ const actual = await parser.start('test:/master', playerInterface);
1714
+ const stream = actual.imageStreams[0];
1715
+ expect(stream).toBeUndefined();
1716
+ });
1717
+
1503
1718
  it('parses manifest with MP4+TTML streams', async () => {
1504
1719
  const master = [
1505
1720
  '#EXTM3U\n',
@@ -1670,6 +1885,43 @@ describe('HlsParser', () => {
1670
1885
  expect(actual).toEqual(manifest);
1671
1886
  });
1672
1887
 
1888
+ it('drops failed image streams when configured to', async () => {
1889
+ const master = [
1890
+ '#EXTM3U\n',
1891
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
1892
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
1893
+ 'video\n',
1894
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg"\n',
1895
+ ].join('');
1896
+
1897
+ const media = [
1898
+ '#EXTM3U\n',
1899
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1900
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1901
+ '#EXTINF:5,\n',
1902
+ '#EXT-X-BYTERANGE:121090@616\n',
1903
+ 'main.mp4',
1904
+ ].join('');
1905
+
1906
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1907
+ manifest.anyTimeline();
1908
+ manifest.addPartialVariant((variant) => {
1909
+ variant.addPartialStream(ContentType.VIDEO);
1910
+ });
1911
+ });
1912
+
1913
+ fakeNetEngine
1914
+ .setResponseText('test:/master', master)
1915
+ .setResponseText('test:/audio', media)
1916
+ .setResponseText('test:/video', media)
1917
+ .setResponseValue('test:/init.mp4', initSegmentData)
1918
+ .setResponseValue('test:/main.mp4', segmentData);
1919
+
1920
+ config.hls.ignoreImageStreamFailures = true;
1921
+ const actual = await parser.start('test:/master', playerInterface);
1922
+ expect(actual).toEqual(manifest);
1923
+ });
1924
+
1673
1925
  it('parses video described by a media tag', async () => {
1674
1926
  const master = [
1675
1927
  '#EXTM3U\n',