shaka-player 3.2.1 → 3.3.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 (213) 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 +9 -5
  5. package/CHANGELOG.md +358 -0
  6. package/CONTRIBUTORS +7 -2
  7. package/build/conformance.textproto +183 -169
  8. package/build/generateExterns.js +1 -1
  9. package/build/types/core +3 -0
  10. package/build/types/offline +1 -0
  11. package/build/types/polyfill +1 -0
  12. package/build/types/ui +2 -0
  13. package/demo/common/assets.js +19 -1
  14. package/demo/common/message_ids.js +13 -1
  15. package/demo/config.js +31 -1
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +12 -0
  18. package/demo/locales/source.json +48 -0
  19. package/dist/controls.css +26 -2
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +20 -17
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +20 -17
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +19 -12
  26. package/dist/locales.js +39 -37
  27. package/dist/receiver.compiled.debug.js +12 -10
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +12 -10
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +231 -18
  32. package/dist/shaka-player.compiled.debug.d.ts +231 -18
  33. package/dist/shaka-player.compiled.debug.externs.js +279 -8
  34. package/dist/shaka-player.compiled.debug.js +512 -449
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +279 -8
  37. package/dist/shaka-player.compiled.js +854 -821
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +247 -19
  40. package/dist/shaka-player.ui.debug.d.ts +247 -19
  41. package/dist/shaka-player.ui.debug.externs.js +307 -9
  42. package/dist/shaka-player.ui.debug.js +662 -580
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +307 -9
  45. package/dist/shaka-player.ui.js +1047 -1000
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/bg-fetch-after.gv +29 -0
  48. package/docs/design/bg-fetch-after.gv.png +0 -0
  49. package/docs/design/bg-fetch-before.gv +18 -0
  50. package/docs/design/bg-fetch-before.gv.png +0 -0
  51. package/docs/design/bg-fetch.md +134 -0
  52. package/docs/tutorials/blob-url.md +15 -0
  53. package/docs/tutorials/fairplay.md +1 -1
  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/cmcd.js +186 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +5 -0
  60. package/externs/shaka/manifest.js +12 -1
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +31 -2
  64. package/externs/shaka/player.js +138 -5
  65. package/externs/shaka/text.js +24 -0
  66. package/externs/webcrypto.js +18 -0
  67. package/karma.conf.js +1 -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/client_side_ad.js +8 -0
  72. package/lib/ads/client_side_ad_manager.js +54 -90
  73. package/lib/ads/server_side_ad.js +7 -0
  74. package/lib/ads/server_side_ad_manager.js +7 -10
  75. package/lib/cast/cast_proxy.js +3 -2
  76. package/lib/cast/cast_receiver.js +2 -1
  77. package/lib/cast/cast_sender.js +2 -2
  78. package/lib/cast/cast_utils.js +1 -0
  79. package/lib/dash/dash_parser.js +141 -16
  80. package/lib/dash/segment_base.js +25 -1
  81. package/lib/dash/segment_list.js +19 -10
  82. package/lib/dash/segment_template.js +24 -11
  83. package/lib/hls/hls_parser.js +146 -40
  84. package/lib/media/drm_engine.js +7 -0
  85. package/lib/media/gap_jumping_controller.js +4 -2
  86. package/lib/media/media_source_engine.js +1 -2
  87. package/lib/media/quality_observer.js +291 -0
  88. package/lib/media/segment_index.js +8 -1
  89. package/lib/media/segment_reference.js +37 -4
  90. package/lib/media/streaming_engine.js +49 -7
  91. package/lib/net/http_fetch_plugin.js +31 -11
  92. package/lib/net/http_plugin_utils.js +1 -0
  93. package/lib/net/http_xhr_plugin.js +40 -14
  94. package/lib/net/networking_engine.js +83 -19
  95. package/lib/offline/download_info.js +68 -0
  96. package/lib/offline/download_manager.js +13 -5
  97. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  98. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  99. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  100. package/lib/offline/storage.js +393 -284
  101. package/lib/offline/stored_content_utils.js +2 -0
  102. package/lib/player.js +249 -43
  103. package/lib/polyfill/media_capabilities.js +36 -15
  104. package/lib/polyfill/mediasource.js +6 -0
  105. package/lib/polyfill/orientation.js +1 -1
  106. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  107. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  108. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  109. package/lib/polyfill/random_uuid.js +52 -0
  110. package/lib/text/cue.js +18 -0
  111. package/lib/text/mp4_ttml_parser.js +3 -0
  112. package/lib/text/text_engine.js +14 -2
  113. package/lib/text/ttml_text_parser.js +114 -60
  114. package/lib/text/ui_text_displayer.js +95 -41
  115. package/lib/util/cmcd_manager.js +672 -0
  116. package/lib/util/error.js +6 -0
  117. package/lib/util/fake_event.js +53 -7
  118. package/lib/util/language_utils.js +12 -0
  119. package/lib/util/mutex.js +52 -0
  120. package/lib/util/periods.js +102 -65
  121. package/lib/util/platform.js +13 -1
  122. package/lib/util/player_configuration.js +18 -0
  123. package/lib/util/stream_utils.js +99 -10
  124. package/lib/util/xml_utils.js +19 -0
  125. package/package.json +2 -2
  126. package/shaka-player.uncompiled.js +3 -0
  127. package/test/cast/cast_proxy_unit.js +12 -6
  128. package/test/cast/cast_utils_unit.js +1 -1
  129. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  130. package/test/dash/dash_parser_live_unit.js +101 -1
  131. package/test/dash/dash_parser_manifest_unit.js +222 -2
  132. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  133. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  134. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  135. package/test/hls/hls_live_unit.js +2 -0
  136. package/test/hls/hls_parser_unit.js +7 -1
  137. package/test/media/closed_caption_parser_unit.js +6 -1
  138. package/test/media/drm_engine_unit.js +47 -3
  139. package/test/media/quality_observer_unit.js +139 -0
  140. package/test/media/segment_reference_unit.js +16 -1
  141. package/test/media/streaming_engine_integration.js +4 -2
  142. package/test/media/streaming_engine_unit.js +147 -0
  143. package/test/net/http_plugin_unit.js +20 -15
  144. package/test/offline/storage_integration.js +108 -4
  145. package/test/player_integration.js +178 -8
  146. package/test/player_src_equals_integration.js +41 -8
  147. package/test/player_unit.js +19 -9
  148. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  149. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  150. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  151. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  152. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  153. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  154. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  155. package/test/test/assets/3675/dash_0.mpd +30 -0
  156. package/test/test/assets/3675/dash_1.mpd +30 -0
  157. package/test/test/assets/3675/dash_2.mpd +30 -0
  158. package/test/test/assets/3675/dash_3.mpd +30 -0
  159. package/test/test/assets/3675/dash_4.mpd +30 -0
  160. package/test/test/assets/3675/dash_5.mpd +26 -0
  161. package/test/test/assets/3675/init-stream0.m4s +0 -0
  162. package/test/test/assets/chapters.vtt +2 -2
  163. package/test/test/assets/chapters2.vtt +10 -0
  164. package/test/test/util/dash_parser_util.js +4 -0
  165. package/test/test/util/fake_ad.js +7 -0
  166. package/test/test/util/fake_ad_manager.js +1 -1
  167. package/test/test/util/fake_drm_engine.js +9 -2
  168. package/test/test/util/manifest_generator.js +3 -2
  169. package/test/test/util/test_scheme.js +14 -1
  170. package/test/test/util/ttml_utils.js +81 -0
  171. package/test/text/mp4_ttml_parser_unit.js +11 -18
  172. package/test/text/ttml_text_parser_unit.js +552 -227
  173. package/test/text/ui_text_displayer_unit.js +29 -17
  174. package/test/ui/localization_unit.js +15 -14
  175. package/test/ui/text_displayer_layout_unit.js +13 -1
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +291 -0
  178. package/test/util/stream_utils_unit.js +45 -4
  179. package/third_party/closure-uri/uri.js +21 -0
  180. package/ui/airplay_button.js +1 -0
  181. package/ui/audio_language_selection.js +7 -0
  182. package/ui/cast_button.js +3 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +45 -13
  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 +14 -5
  189. package/ui/fullscreen_button.js +1 -0
  190. package/ui/less/buttons.less +0 -4
  191. package/ui/less/containers.less +77 -7
  192. package/ui/less/overflow_menu.less +0 -10
  193. package/ui/less/tooltip.less +97 -0
  194. package/ui/locales/en.json +1 -0
  195. package/ui/locales/ru.json +6 -6
  196. package/ui/locales/source.json +4 -0
  197. package/ui/localization.js +28 -51
  198. package/ui/loop_button.js +5 -0
  199. package/ui/mute_button.js +2 -1
  200. package/ui/overflow_menu.js +5 -25
  201. package/ui/pip_button.js +7 -3
  202. package/ui/play_button.js +2 -2
  203. package/ui/playback_rate_selection.js +8 -9
  204. package/ui/resolution_selection.js +7 -0
  205. package/ui/rewind_button.js +15 -6
  206. package/ui/seek_bar.js +4 -2
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/statistics_button.js +253 -0
  210. package/ui/text_selection.js +12 -4
  211. package/ui/ui.js +30 -0
  212. package/ui/volume_bar.js +1 -1
  213. package/externs/media_capabilities.js +0 -16
@@ -38,6 +38,14 @@ describe('DashParser Manifest', () => {
38
38
 
39
39
  /** @type {string} */
40
40
  const thumbnailScheme = 'http://dashif.org/guidelines/thumbnail_tile';
41
+ /**
42
+ * CICP scheme. parameter must be one of the following: "ColourPrimaries",
43
+ * "TransferCharacteristics", or "MatrixCoefficients".
44
+ *
45
+ * @param {string} parameter
46
+ * @return {string}
47
+ */
48
+ const cicpScheme = (parameter) => `urn:mpeg:mpegB:cicp:${parameter}`;
41
49
 
42
50
  beforeAll(async () => {
43
51
  mp4Index = await shaka.test.Util.fetch(mp4IndexSegmentUri);
@@ -49,6 +57,8 @@ describe('DashParser Manifest', () => {
49
57
  onEventSpy = jasmine.createSpy('onEvent');
50
58
  playerInterface = {
51
59
  networkingEngine: fakeNetEngine,
60
+ modifyManifestRequest: (request, manifestInfo) => {},
61
+ modifySegmentRequest: (request, segmentInfo) => {},
52
62
  filter: (manifest) => Promise.resolve(),
53
63
  makeTextStreamsForClosedCaptions: (manifest) => {},
54
64
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -1864,7 +1874,7 @@ describe('DashParser Manifest', () => {
1864
1874
  expect(textStream.kind).toBe('caption');
1865
1875
  });
1866
1876
 
1867
- it('converts Roles element to "forced"', async () => {
1877
+ it('converts Roles element to "forced" (old role)', async () => {
1868
1878
  const manifestText = [
1869
1879
  '<MPD minBufferTime="PT75S">',
1870
1880
  ' <Period id="1" duration="PT30S">',
@@ -1896,7 +1906,40 @@ describe('DashParser Manifest', () => {
1896
1906
  expect(textStream.forced).toBe(true);
1897
1907
  });
1898
1908
 
1899
- it('supports HDR signaling', async () => {
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
+
1941
+ it('supports HDR signaling via profiles', async () => {
1942
+ // (DASH-IF IOP v4.3 10.3.3.)
1900
1943
  const hdrProfile =
1901
1944
  'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
1902
1945
  const manifestText = [
@@ -1927,6 +1970,110 @@ describe('DashParser Manifest', () => {
1927
1970
  expect(stream.hdr).toBe('PQ');
1928
1971
  });
1929
1972
 
1973
+ it('supports HDR signaling via SupplementalProperty', async () => {
1974
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1975
+ const hdrScheme = cicpScheme('TransferCharacteristics');
1976
+ const pq = 16;
1977
+ const manifestText = [
1978
+ '<MPD minBufferTime="PT75S">',
1979
+ ' <Period id="1" duration="PT30S">',
1980
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1981
+ ` <SupplementalProperty schemeIdUri="${hdrScheme}" value="${pq}" />`,
1982
+ ' <Representation codecs="hvc1.2.4.L150.90">',
1983
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1984
+ ' <SegmentBase indexRange="100-200" />',
1985
+ ' </Representation>',
1986
+ ' </AdaptationSet>',
1987
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1988
+ ' <Representation id="audio-en">',
1989
+ ' <BaseURL>a-en.mp4</BaseURL>',
1990
+ ' <SegmentBase indexRange="100-200" />',
1991
+ ' </Representation>',
1992
+ ' </AdaptationSet>',
1993
+ ' </Period>',
1994
+ '</MPD>',
1995
+ ].join('\n');
1996
+
1997
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1998
+
1999
+ /** @type {shaka.extern.Manifest} */
2000
+ const manifest = await parser.start('dummy://foo', playerInterface);
2001
+ expect(manifest.variants.length).toBe(1);
2002
+ const stream = manifest.variants[0].video;
2003
+ expect(stream.hdr).toBe('PQ');
2004
+ });
2005
+
2006
+ it('supports HDR signaling via EssentialProperty', async () => {
2007
+ // (DASH-IF IOP v4.3 6.2.5.1.)
2008
+ const hdrScheme = cicpScheme('TransferCharacteristics');
2009
+ const hlg = 18;
2010
+ const manifestText = [
2011
+ '<MPD minBufferTime="PT75S">',
2012
+ ' <Period id="1" duration="PT30S">',
2013
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2014
+ ` <EssentialProperty schemeIdUri="${hdrScheme}" value="${hlg}" />`,
2015
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
2016
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2017
+ ' <SegmentBase indexRange="100-200" />',
2018
+ ' </Representation>',
2019
+ ' </AdaptationSet>',
2020
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2021
+ ' <Representation id="audio-en">',
2022
+ ' <BaseURL>a-en.mp4</BaseURL>',
2023
+ ' <SegmentBase indexRange="100-200" />',
2024
+ ' </Representation>',
2025
+ ' </AdaptationSet>',
2026
+ ' </Period>',
2027
+ '</MPD>',
2028
+ ].join('\n');
2029
+
2030
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2031
+
2032
+ /** @type {shaka.extern.Manifest} */
2033
+ const manifest = await parser.start('dummy://foo', playerInterface);
2034
+ expect(manifest.variants.length).toBe(1);
2035
+ const stream = manifest.variants[0].video;
2036
+ expect(stream.hdr).toBe('HLG');
2037
+ });
2038
+
2039
+ it('supports SDR signalling via EssentialProperty', async () => {
2040
+ // (DASH-IF IOP v4.3 6.2.5.1.)
2041
+ const scheme = cicpScheme('TransferCharacteristics');
2042
+ const sdrValues = [1, 6, 13, 14, 15];
2043
+ const manifestPromises = [];
2044
+ for (const value of sdrValues) {
2045
+ const manifestText = [
2046
+ '<MPD minBufferTime="PT75S">',
2047
+ ' <Period id="1" duration="PT30S">',
2048
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2049
+ ` <EssentialProperty schemeIdUri="${scheme}" value="${value}" />`,
2050
+ ' <Representation codecs="avc1.640028">',
2051
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2052
+ ' <SegmentBase indexRange="100-200" />',
2053
+ ' </Representation>',
2054
+ ' </AdaptationSet>',
2055
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2056
+ ' <Representation id="audio-en">',
2057
+ ' <BaseURL>a-en.mp4</BaseURL>',
2058
+ ' <SegmentBase indexRange="100-200" />',
2059
+ ' </Representation>',
2060
+ ' </AdaptationSet>',
2061
+ ' </Period>',
2062
+ '</MPD>',
2063
+ ].join('\n');
2064
+
2065
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2066
+
2067
+ manifestPromises.push(parser.start('dummy://foo', playerInterface));
2068
+ }
2069
+ const manifests = await Promise.all(manifestPromises);
2070
+ for (const manifest of manifests) {
2071
+ expect(manifest.variants.length).toBe(1);
2072
+ const stream = manifest.variants[0].video;
2073
+ expect(stream.hdr).toBe('SDR');
2074
+ }
2075
+ });
2076
+
1930
2077
  it('Does not error when image adaptation sets are present', async () => {
1931
2078
  const manifestText = [
1932
2079
  '<MPD minBufferTime="PT75S">',
@@ -2150,4 +2297,77 @@ describe('DashParser Manifest', () => {
2150
2297
  expect(uri).not.toContain('/p2/');
2151
2298
  }
2152
2299
  });
2300
+
2301
+ /**
2302
+ * @param {!Array.<number>} periods Start time of multiple periods
2303
+ * @return {string}
2304
+ */
2305
+ function buildManifestWithPeriodStartTime(periods) {
2306
+ const mpdTemplate = [
2307
+ `<MPD type="dynamic"`,
2308
+ 'availabilityStartTime="1970-01-01T00:00:00Z"',
2309
+ 'timeShiftBufferDepth="PT10H">',
2310
+ ' %(periods)s',
2311
+ '</MPD>',
2312
+ ].join('\n');
2313
+ const periodTemplate = (id, period, duration) => {
2314
+ return [
2315
+ ` <Period id="${id}" start="PT${period}S">`,
2316
+ ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
2317
+ ' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
2318
+ ' <SegmentTimeline>',
2319
+ ` <S t="0" d="${duration}" />`,
2320
+ ' </SegmentTimeline>',
2321
+ ' </SegmentTemplate>',
2322
+ ' <Representation id="1"/>',
2323
+ ' </AdaptationSet>',
2324
+ ' </Period>',
2325
+ ].join('\n');
2326
+ };
2327
+ const periodXmls = periods.map((period, i) => {
2328
+ const duration = i+1 === periods.length ? 10 : periods[i+1] - period;
2329
+ // Period start time as ID here. If we use index then there will be
2330
+ // periods with same period ID and different start time which are invalid.
2331
+ return periodTemplate(period, period, duration);
2332
+ });
2333
+ return sprintf(mpdTemplate, {
2334
+ periods: periodXmls.join('\n'),
2335
+ });
2336
+ }
2337
+
2338
+ // Bug description: Inconsistent period start time in the manifests due
2339
+ // to failover triggered in backend servers
2340
+
2341
+ // When one of the servers is down, the manifest will be served by other
2342
+ // redundant servers. The period start time might become out of sync
2343
+ // during the switch-over/recovery.
2344
+
2345
+ it('skip periods that are earlier than max period start time', async () => {
2346
+ const sources = [
2347
+ buildManifestWithPeriodStartTime([5, 15]),
2348
+ buildManifestWithPeriodStartTime([6, 15]), // simulate out-of-sync of +1s
2349
+ buildManifestWithPeriodStartTime([4, 15]), // simulate out-of-sync of -1s
2350
+ ];
2351
+ const segments = [];
2352
+
2353
+ for (const source of sources) {
2354
+ fakeNetEngine.setResponseText('dummy://foo', source);
2355
+ /** @type {shaka.extern.Manifest} */
2356
+ // eslint-disable-next-line no-await-in-loop
2357
+ const manifest = await parser.start('dummy://foo', playerInterface);
2358
+ const video = manifest.variants[0].video;
2359
+ // eslint-disable-next-line no-await-in-loop
2360
+ await video.createSegmentIndex();
2361
+ goog.asserts.assert(video.segmentIndex, 'Null segmentIndex!');
2362
+ segments.push(Array.from(video.segmentIndex));
2363
+ }
2364
+
2365
+ // Expect identical segments
2366
+ expect(segments[0].length).toBe(2);
2367
+ expect(segments[1].length).toBe(2);
2368
+ expect(segments[0][0].startTime).toBe(5);
2369
+ expect(segments[1][0].startTime).toBe(5);
2370
+ expect(segments[0][1].startTime).toBe(15);
2371
+ expect(segments[1][1].startTime).toBe(15);
2372
+ });
2153
2373
  });
@@ -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,
@@ -783,7 +785,7 @@ describe('HlsParser', () => {
783
785
  'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
784
786
  'video2\n',
785
787
  '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
786
- 'URI="audio"\n',
788
+ 'DEFAULT=YES,URI="audio"\n',
787
789
  '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
788
790
  'URI="audio2"\n',
789
791
  ].join('');
@@ -801,6 +803,7 @@ describe('HlsParser', () => {
801
803
  manifest.anyTimeline();
802
804
  manifest.addPartialVariant((variant) => {
803
805
  variant.bandwidth = 200;
806
+ variant.primary = true;
804
807
  variant.addPartialStream(ContentType.VIDEO, (stream) => {
805
808
  stream.size(960, 540);
806
809
  });
@@ -810,6 +813,7 @@ describe('HlsParser', () => {
810
813
  });
811
814
  manifest.addPartialVariant((variant) => {
812
815
  variant.bandwidth = 300;
816
+ variant.primary = false;
813
817
  variant.addPartialStream(ContentType.VIDEO, (stream) => {
814
818
  stream.size(960, 540);
815
819
  });
@@ -1307,6 +1311,8 @@ describe('HlsParser', () => {
1307
1311
  // stream.
1308
1312
  const timeline = actual.presentationTimeline;
1309
1313
  expect(timeline.getDuration()).toBe(10);
1314
+ expect(timeline.getSeekRangeStart()).toBe(0);
1315
+ expect(timeline.getSeekRangeEnd()).toBe(10);
1310
1316
 
1311
1317
  expect(actual.textStreams.length).toBe(1);
1312
1318
  expect(actual.variants.length).toBe(1);
@@ -1,3 +1,9 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  goog.require('shaka.media.ClosedCaptionParser');
2
8
  goog.require('shaka.test.Util');
3
9
 
@@ -12,4 +18,3 @@ describe('ClosedCaptionParser', () => {
12
18
  parser.parseFrom(videoSegment);
13
19
  });
14
20
  });
15
-
@@ -31,7 +31,7 @@ describe('DrmEngine', () => {
31
31
  navigator.requestMediaKeySystemAccess;
32
32
  const originalLogError = shaka.log.error;
33
33
  const originalBatchTime = shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME;
34
- const originalDecodingInfo = window.shakaMediaCapabilities.decodingInfo;
34
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
35
35
 
36
36
  /** @type {!jasmine.Spy} */
37
37
  let decodingInfoSpy;
@@ -76,7 +76,7 @@ describe('DrmEngine', () => {
76
76
 
77
77
  beforeEach(() => {
78
78
  decodingInfoSpy = jasmine.createSpy('decodingInfo');
79
- window.shakaMediaCapabilities.decodingInfo =
79
+ navigator.mediaCapabilities.decodingInfo =
80
80
  shaka.test.Util.spyFunc(decodingInfoSpy);
81
81
 
82
82
  logErrorSpy = jasmine.createSpy('shaka.log.error');
@@ -153,7 +153,7 @@ describe('DrmEngine', () => {
153
153
 
154
154
  navigator.requestMediaKeySystemAccess =
155
155
  originalRequestMediaKeySystemAccess;
156
- window.shakaMediaCapabilities.decodingInfo = originalDecodingInfo;
156
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
157
157
  shaka.log.error = originalLogError;
158
158
  });
159
159
 
@@ -2152,6 +2152,50 @@ describe('DrmEngine', () => {
2152
2152
  [drmInfoAudio]);
2153
2153
  expect(returned).toEqual([drmInfoDesired]);
2154
2154
  });
2155
+
2156
+ it('dedupes the merged init data based on keyId matching', () => {
2157
+ const serverCert = new Uint8Array(0);
2158
+ const drmInfoVideo = {
2159
+ keySystem: 'drm.abc',
2160
+ licenseServerUri: 'http://abc.drm/license',
2161
+ distinctiveIdentifierRequired: false,
2162
+ persistentStateRequired: true,
2163
+ videoRobustness: 'really_really_ridiculously_good',
2164
+ serverCertificate: serverCert,
2165
+ serverCertificateUri: '',
2166
+ initData: [{keyId: 'v-init'}],
2167
+ keyIds: new Set(['deadbeefdeadbeefdeadbeefdeadbeef']),
2168
+ };
2169
+ const drmInfoAudio = {
2170
+ keySystem: 'drm.abc',
2171
+ licenseServerUri: undefined,
2172
+ distinctiveIdentifierRequired: true,
2173
+ persistentStateRequired: false,
2174
+ audioRobustness: 'good',
2175
+ serverCertificate: undefined,
2176
+ serverCertificateUri: '',
2177
+ initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
2178
+ keyIds: new Set(['eadbeefdeadbeefdeadbeefdeadbeefd']),
2179
+ };
2180
+ const drmInfoDesired = {
2181
+ keySystem: 'drm.abc',
2182
+ licenseServerUri: 'http://abc.drm/license',
2183
+ distinctiveIdentifierRequired: true,
2184
+ persistentStateRequired: true,
2185
+ audioRobustness: 'good',
2186
+ videoRobustness: 'really_really_ridiculously_good',
2187
+ serverCertificate: serverCert,
2188
+ serverCertificateUri: '',
2189
+ initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
2190
+ keyIds: new Set([
2191
+ 'deadbeefdeadbeefdeadbeefdeadbeef',
2192
+ 'eadbeefdeadbeefdeadbeefdeadbeefd',
2193
+ ]),
2194
+ };
2195
+ const returned = shaka.media.DrmEngine.getCommonDrmInfos([drmInfoVideo],
2196
+ [drmInfoAudio]);
2197
+ expect(returned).toEqual([drmInfoDesired]);
2198
+ });
2155
2199
  }); // describe('getCommonDrmInfos')
2156
2200
 
2157
2201
  describe('configure', () => {
@@ -0,0 +1,139 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.require('shaka.media.QualityObserver');
8
+ goog.require('shaka.test.Util');
9
+
10
+
11
+ describe('QualityObserver', () => {
12
+ /** @type {!shaka.media.QualityObserver} */
13
+ let observer;
14
+
15
+ /** @type {!jasmine.Spy} */
16
+ let onQualityChange;
17
+
18
+ const createQualityInfo = (contentType, bandwidth) => {
19
+ return {
20
+ bandwidth: bandwidth,
21
+ audioSamplingRate: 444000,
22
+ codecs: 'my codec',
23
+ contentType: contentType,
24
+ frameRate: 30,
25
+ height: 720,
26
+ mimeType: 'mime type',
27
+ channelsCount: 2,
28
+ pixelAspectRatio: '1:1',
29
+ width: 1280,
30
+ };
31
+ };
32
+ let emptyBuffer = true;
33
+ let bufferStart = 0;
34
+ let bufferEnd = 0;
35
+
36
+ const quality1 = createQualityInfo('video', 1);
37
+ const quality2 = createQualityInfo('video', 2);
38
+
39
+ const getBufferedInfo = () => {
40
+ if (emptyBuffer) {
41
+ return {
42
+ video: [],
43
+ };
44
+ }
45
+ return {
46
+ video: [{start: bufferStart, end: bufferEnd}],
47
+ };
48
+ };
49
+
50
+ beforeEach(() => {
51
+ onQualityChange = jasmine.createSpy('onQualityChange');
52
+ observer = new shaka.media.QualityObserver(getBufferedInfo);
53
+ observer.setListeners(shaka.test.Util.spyFunc(onQualityChange));
54
+ emptyBuffer = true;
55
+ bufferStart = 0;
56
+ bufferEnd = 0;
57
+ });
58
+
59
+ it('does not call onQualityChange when there are no quality changes', () => {
60
+ observer.poll(10, false);
61
+ expect(onQualityChange).not.toHaveBeenCalled();
62
+ });
63
+
64
+ it('calls onQualityChange when position is after 1st quality change', () => {
65
+ observer.addMediaQualityChange(quality1, 10);
66
+ emptyBuffer = false;
67
+ bufferStart = 10;
68
+ bufferEnd = 20;
69
+ observer.poll(10, false);
70
+ expect(onQualityChange).toHaveBeenCalledWith(quality1, 10);
71
+ });
72
+
73
+ it('does not call onQualityChange when pos advances with no change', () => {
74
+ observer.addMediaQualityChange(quality1, 10);
75
+ emptyBuffer = false;
76
+ bufferStart = 10;
77
+ bufferEnd = 20;
78
+ observer.poll(10, false);
79
+ expect(onQualityChange).toHaveBeenCalledWith(quality1, 10);
80
+ observer.poll(11, false);
81
+ expect(onQualityChange).toHaveBeenCalledTimes(1);
82
+ });
83
+
84
+ it('does not call onQualityChange on seek to unbuffered position', () => {
85
+ observer.addMediaQualityChange(quality1, 10);
86
+ emptyBuffer = false;
87
+ bufferStart = 10;
88
+ bufferEnd = 20;
89
+ observer.poll(15, false);
90
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 15]);
91
+ observer.addMediaQualityChange(quality2, 20);
92
+ observer.poll(25, true);
93
+ expect(onQualityChange).not.toHaveBeenCalledOnceMore();
94
+ bufferEnd = 30;
95
+ observer.poll(26, false);
96
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 26]);
97
+ });
98
+
99
+ it('calls onQualityChange when position advances over 2nd quality change',
100
+ () => {
101
+ observer.addMediaQualityChange(quality1, 10);
102
+ emptyBuffer = false;
103
+ bufferStart = 10;
104
+ bufferEnd = 20;
105
+ observer.poll(10, false);
106
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 10]);
107
+ observer.addMediaQualityChange(quality2, 20);
108
+ bufferStart = 10;
109
+ bufferEnd = 30;
110
+ observer.poll(20, false);
111
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 20]);
112
+ });
113
+
114
+ it('calls onQualityChange when position moves back over a quality chanage',
115
+ () => {
116
+ observer.addMediaQualityChange(quality1, 10);
117
+ emptyBuffer = false;
118
+ bufferStart = 10;
119
+ bufferEnd = 20;
120
+ observer.addMediaQualityChange(quality2, 20);
121
+ bufferStart = 10;
122
+ bufferEnd = 30;
123
+ observer.poll(25, false);
124
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 25]);
125
+ observer.poll(15, false);
126
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 15]);
127
+ });
128
+
129
+ it('uses last applied quality when there are two at the same position',
130
+ () => {
131
+ observer.addMediaQualityChange(quality1, 10);
132
+ observer.addMediaQualityChange(quality2, 10);
133
+ emptyBuffer = false;
134
+ bufferStart = 10;
135
+ bufferEnd = 20;
136
+ observer.poll(15, false);
137
+ expect(onQualityChange).toHaveBeenCalledWith(quality2, 15);
138
+ });
139
+ });
@@ -38,14 +38,29 @@ describe('SegmentReference', () => {
38
38
  });
39
39
 
40
40
  describe('InitSegmentReference', () => {
41
+ const mediaQuality = {
42
+ bandwidth: 1,
43
+ audioSamplingRate: 444000,
44
+ codecs: 'my codec',
45
+ contentType: 'video',
46
+ frameRate: 30,
47
+ height: 720,
48
+ mimeType: 'mime type',
49
+ channelsCount: 2,
50
+ pixelAspectRatio: '1:1',
51
+ width: 1280,
52
+ };
53
+
41
54
  it('returns in getters values from constructor parameters', () => {
42
55
  const reference = new shaka.media.InitSegmentReference(
43
56
  /* getUris= */ () => ['x', 'y'],
44
57
  /* startByte= */ 4,
45
- /* endByte= */ 5);
58
+ /* endByte= */ 5,
59
+ mediaQuality);
46
60
 
47
61
  expect(reference.getUris()).toEqual(['x', 'y']);
48
62
  expect(reference.getStartByte()).toBe(4);
49
63
  expect(reference.getEndByte()).toBe(5);
64
+ expect(reference.getMediaQuality()).toBe(mediaQuality);
50
65
  });
51
66
  });
@@ -60,7 +60,6 @@ describe('StreamingEngine', () => {
60
60
  /** @type {!shaka.media.StreamingEngine} */
61
61
  let streamingEngine;
62
62
 
63
-
64
63
  /** @type {shaka.extern.Variant} */
65
64
  let variant;
66
65
 
@@ -262,6 +261,7 @@ describe('StreamingEngine', () => {
262
261
 
263
262
  function createStreamingEngine() {
264
263
  const playerInterface = {
264
+ modifySegmentRequest: (request, segmentInfo) => {},
265
265
  getPresentationTime: () => playhead.getTime(),
266
266
  getBandwidthEstimate: () => 1e6,
267
267
  mediaSourceEngine: mediaSourceEngine,
@@ -270,6 +270,7 @@ describe('StreamingEngine', () => {
270
270
  onEvent: Util.spyFunc(onEvent),
271
271
  onManifestUpdate: () => {},
272
272
  onSegmentAppended: () => playhead.notifyOfBufferingChange(),
273
+ onInitSegmentAppended: () => {},
273
274
  };
274
275
  streamingEngine = new shaka.media.StreamingEngine(
275
276
  /** @type {shaka.extern.Manifest} */(manifest), playerInterface);
@@ -419,8 +420,9 @@ describe('StreamingEngine', () => {
419
420
  // 3. Playhead seeks to force us back inside the window
420
421
  // 4. (maybe) seek if there is a gap at the period boundary
421
422
  // 5. (maybe) seek to flush a pipeline stall
423
+ // 6. (maybe) on slower platforms (e.g. GitHub actions)
422
424
  expect(seekCount).toBeGreaterThan(2);
423
- expect(seekCount).toBeLessThan(6);
425
+ expect(seekCount).toBeLessThan(7);
424
426
  });
425
427
  });
426
428