shaka-player 3.1.1 → 3.1.5

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 (151) hide show
  1. package/AUTHORS +7 -2
  2. package/CHANGELOG.md +199 -0
  3. package/CONTRIBUTORS +9 -3
  4. package/build/generateExterns.js +1 -1
  5. package/build/misspellings.txt +1 -0
  6. package/build/shakaBuildHelpers.py +6 -5
  7. package/build/subprocessWindowsPatch.py +103 -0
  8. package/demo/common/assets.js +1 -1
  9. package/dist/controls.css +2 -2
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.compiled.debug.js +1 -1
  12. package/dist/demo.compiled.js +1 -1
  13. package/dist/deps.js +7 -7
  14. package/dist/locales.js +35 -33
  15. package/dist/receiver.compiled.debug.js +1 -1
  16. package/dist/receiver.compiled.js +1 -1
  17. package/dist/shaka-player.compiled.d.ts +171 -3
  18. package/dist/shaka-player.compiled.debug.d.ts +171 -3
  19. package/dist/shaka-player.compiled.debug.externs.js +179 -1
  20. package/dist/shaka-player.compiled.debug.js +365 -344
  21. package/dist/shaka-player.compiled.debug.map +3 -3
  22. package/dist/shaka-player.compiled.externs.js +179 -1
  23. package/dist/shaka-player.compiled.js +806 -798
  24. package/dist/shaka-player.compiled.map +3 -3
  25. package/dist/shaka-player.ui.d.ts +171 -3
  26. package/dist/shaka-player.ui.debug.d.ts +171 -3
  27. package/dist/shaka-player.ui.debug.externs.js +181 -3
  28. package/dist/shaka-player.ui.debug.js +458 -435
  29. package/dist/shaka-player.ui.debug.map +4 -4
  30. package/dist/shaka-player.ui.externs.js +181 -3
  31. package/dist/shaka-player.ui.js +927 -918
  32. package/dist/shaka-player.ui.map +4 -4
  33. package/docs/tutorials/fairplay.md +1 -1
  34. package/docs/tutorials/faq.md +14 -0
  35. package/docs/tutorials/index.json +2 -2
  36. package/docs/tutorials/upgrade-v3.md +38 -0
  37. package/docs/tutorials/upgrade.md +3 -3
  38. package/externs/ima.js +4 -0
  39. package/externs/shaka/ads.js +2 -0
  40. package/externs/shaka/player.js +6 -0
  41. package/externs/shaka/text.js +10 -0
  42. package/karma.conf.js +1 -0
  43. package/lib/ads/ad_manager.js +29 -11
  44. package/lib/ads/client_side_ad_manager.js +76 -86
  45. package/lib/ads/server_side_ad_manager.js +26 -11
  46. package/lib/cast/cast_proxy.js +3 -2
  47. package/lib/cast/cast_receiver.js +2 -1
  48. package/lib/cast/cast_sender.js +2 -2
  49. package/lib/cea/sei_processor.js +24 -17
  50. package/lib/dash/dash_parser.js +100 -5
  51. package/lib/dash/segment_list.js +1 -1
  52. package/lib/dash/segment_template.js +7 -4
  53. package/lib/hls/hls_parser.js +44 -17
  54. package/lib/media/closed_caption_parser.js +3 -1
  55. package/lib/media/content_workarounds.js +12 -0
  56. package/lib/media/drm_engine.js +20 -1
  57. package/lib/media/gap_jumping_controller.js +8 -4
  58. package/lib/media/media_source_engine.js +1 -2
  59. package/lib/media/playhead.js +1 -1
  60. package/lib/media/presentation_timeline.js +3 -1
  61. package/lib/media/segment_reference.js +8 -0
  62. package/lib/media/streaming_engine.js +28 -19
  63. package/lib/net/networking_engine.js +2 -1
  64. package/lib/player.js +108 -54
  65. package/lib/polyfill/encryption_scheme.js +2 -0
  66. package/lib/polyfill/fullscreen.js +2 -0
  67. package/lib/polyfill/mathround.js +2 -0
  68. package/lib/polyfill/media_capabilities.js +76 -22
  69. package/lib/polyfill/mediasource.js +8 -0
  70. package/lib/polyfill/orientation.js +3 -1
  71. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  72. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  73. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  74. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  75. package/lib/polyfill/pip_webkit.js +2 -0
  76. package/lib/polyfill/storage_estimate.js +2 -0
  77. package/lib/polyfill/video_play_promise.js +2 -0
  78. package/lib/polyfill/videoplaybackquality.js +2 -0
  79. package/lib/polyfill/vttcue.js +2 -0
  80. package/lib/text/cue.js +17 -0
  81. package/lib/text/mp4_ttml_parser.js +3 -0
  82. package/lib/text/text_engine.js +14 -2
  83. package/lib/text/ttml_text_parser.js +135 -87
  84. package/lib/text/ui_text_displayer.js +233 -83
  85. package/lib/text/vtt_text_parser.js +4 -2
  86. package/lib/util/fake_event.js +53 -7
  87. package/lib/util/mp4_box_parsers.js +1 -1
  88. package/lib/util/periods.js +111 -35
  89. package/lib/util/platform.js +13 -1
  90. package/lib/util/stream_utils.js +79 -10
  91. package/lib/util/xml_utils.js +19 -0
  92. package/package.json +2 -2
  93. package/test/cast/cast_proxy_unit.js +12 -6
  94. package/test/cast/cast_receiver_integration.js +1 -1
  95. package/test/cast/cast_utils_unit.js +1 -1
  96. package/test/cea/mp4_cea_parser_unit.js +26 -0
  97. package/test/dash/dash_parser_live_unit.js +1 -0
  98. package/test/dash/dash_parser_manifest_unit.js +220 -2
  99. package/test/dash/dash_parser_segment_template_unit.js +3 -3
  100. package/test/hls/hls_parser_unit.js +5 -1
  101. package/test/media/closed_caption_parser_unit.js +20 -0
  102. package/test/media/drm_engine_unit.js +56 -0
  103. package/test/media/playhead_unit.js +27 -1
  104. package/test/media/presentation_timeline_unit.js +19 -0
  105. package/test/media/streaming_engine_integration.js +2 -1
  106. package/test/media/streaming_engine_unit.js +148 -3
  107. package/test/player_integration.js +38 -0
  108. package/test/player_unit.js +101 -0
  109. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  110. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  111. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  112. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  113. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  114. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  115. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  116. package/test/test/assets/3675/dash_0.mpd +30 -0
  117. package/test/test/assets/3675/dash_1.mpd +30 -0
  118. package/test/test/assets/3675/dash_2.mpd +30 -0
  119. package/test/test/assets/3675/dash_3.mpd +30 -0
  120. package/test/test/assets/3675/dash_4.mpd +30 -0
  121. package/test/test/assets/3675/dash_5.mpd +26 -0
  122. package/test/test/assets/3675/init-stream0.m4s +0 -0
  123. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  124. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  125. package/test/test/util/fake_ad_manager.js +4 -1
  126. package/test/test/util/simple_fakes.js +5 -3
  127. package/test/test/util/ttml_utils.js +81 -0
  128. package/test/text/mp4_ttml_parser_unit.js +11 -18
  129. package/test/text/ttml_text_parser_unit.js +542 -220
  130. package/test/text/ui_text_displayer_unit.js +95 -18
  131. package/test/text/vtt_text_parser_unit.js +17 -1
  132. package/test/ui/localization_unit.js +15 -14
  133. package/test/ui/text_displayer_layout_unit.js +52 -1
  134. package/test/util/mp4_box_parsers_unit.js +28 -0
  135. package/test/util/periods_unit.js +190 -2
  136. package/test/util/stream_utils_unit.js +59 -2
  137. package/ui/airplay_button.js +5 -0
  138. package/ui/big_play_button.js +10 -0
  139. package/ui/cast_button.js +2 -3
  140. package/ui/controls.js +8 -6
  141. package/ui/less/buttons.less +0 -4
  142. package/ui/less/containers.less +10 -5
  143. package/ui/locales/en.json +1 -0
  144. package/ui/locales/ru.json +6 -6
  145. package/ui/locales/source.json +4 -0
  146. package/ui/localization.js +28 -51
  147. package/ui/pip_button.js +2 -3
  148. package/ui/play_button.js +10 -10
  149. package/ui/small_play_button.js +12 -0
  150. package/ui/text_selection.js +1 -0
  151. package/docs/tutorials/upgrade-v2-4.md +0 -481
@@ -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);
@@ -1822,7 +1830,7 @@ describe('DashParser Manifest', () => {
1822
1830
  expect(textStream.kind).toBe('caption');
1823
1831
  });
1824
1832
 
1825
- it('converts Roles element to "forced"', async () => {
1833
+ it('converts Roles element to "forced" (old role)', async () => {
1826
1834
  const manifestText = [
1827
1835
  '<MPD minBufferTime="PT75S">',
1828
1836
  ' <Period id="1" duration="PT30S">',
@@ -1854,7 +1862,40 @@ describe('DashParser Manifest', () => {
1854
1862
  expect(textStream.forced).toBe(true);
1855
1863
  });
1856
1864
 
1857
- it('supports HDR signaling', async () => {
1865
+ it('converts Roles element to "forced"', async () => {
1866
+ const manifestText = [
1867
+ '<MPD minBufferTime="PT75S">',
1868
+ ' <Period id="1" duration="PT30S">',
1869
+ ' <AdaptationSet id="1" contentType="text">',
1870
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1871
+ ' value="captions" />',
1872
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1873
+ ' value="forced-subtitle" />',
1874
+ ' <Accessibility schemeIdUri="foobar" value="bar" />',
1875
+ ' <Representation id="text-en" mimeType="text/webvtt">',
1876
+ ' <BaseURL>t-en.vtt</BaseURL>',
1877
+ ' </Representation>',
1878
+ ' </AdaptationSet>',
1879
+ ' <AdaptationSet id="1" mimeType="video/mp4">',
1880
+ ' <Representation id="video-sd" width="640" height="480">',
1881
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1882
+ ' <SegmentBase indexRange="100-200" />',
1883
+ ' </Representation>',
1884
+ ' </AdaptationSet>',
1885
+ ' </Period>',
1886
+ '</MPD>',
1887
+ ].join('\n');
1888
+
1889
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1890
+ /** @type {shaka.extern.Manifest} */
1891
+ const manifest = await parser.start('dummy://foo', playerInterface);
1892
+ const textStream = manifest.textStreams[0];
1893
+ expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
1894
+ expect(textStream.forced).toBe(true);
1895
+ });
1896
+
1897
+ it('supports HDR signaling via profiles', async () => {
1898
+ // (DASH-IF IOP v4.3 10.3.3.)
1858
1899
  const hdrProfile =
1859
1900
  'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
1860
1901
  const manifestText = [
@@ -1885,6 +1926,110 @@ describe('DashParser Manifest', () => {
1885
1926
  expect(stream.hdr).toBe('PQ');
1886
1927
  });
1887
1928
 
1929
+ it('supports HDR signaling via SupplementalProperty', async () => {
1930
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1931
+ const hdrScheme = cicpScheme('TransferCharacteristics');
1932
+ const pq = 16;
1933
+ const manifestText = [
1934
+ '<MPD minBufferTime="PT75S">',
1935
+ ' <Period id="1" duration="PT30S">',
1936
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1937
+ ` <SupplementalProperty schemeIdUri="${hdrScheme}" value="${pq}" />`,
1938
+ ' <Representation codecs="hvc1.2.4.L150.90">',
1939
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1940
+ ' <SegmentBase indexRange="100-200" />',
1941
+ ' </Representation>',
1942
+ ' </AdaptationSet>',
1943
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1944
+ ' <Representation id="audio-en">',
1945
+ ' <BaseURL>a-en.mp4</BaseURL>',
1946
+ ' <SegmentBase indexRange="100-200" />',
1947
+ ' </Representation>',
1948
+ ' </AdaptationSet>',
1949
+ ' </Period>',
1950
+ '</MPD>',
1951
+ ].join('\n');
1952
+
1953
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1954
+
1955
+ /** @type {shaka.extern.Manifest} */
1956
+ const manifest = await parser.start('dummy://foo', playerInterface);
1957
+ expect(manifest.variants.length).toBe(1);
1958
+ const stream = manifest.variants[0].video;
1959
+ expect(stream.hdr).toBe('PQ');
1960
+ });
1961
+
1962
+ it('supports HDR signaling via EssentialProperty', async () => {
1963
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1964
+ const hdrScheme = cicpScheme('TransferCharacteristics');
1965
+ const hlg = 18;
1966
+ const manifestText = [
1967
+ '<MPD minBufferTime="PT75S">',
1968
+ ' <Period id="1" duration="PT30S">',
1969
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1970
+ ` <EssentialProperty schemeIdUri="${hdrScheme}" value="${hlg}" />`,
1971
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
1972
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1973
+ ' <SegmentBase indexRange="100-200" />',
1974
+ ' </Representation>',
1975
+ ' </AdaptationSet>',
1976
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1977
+ ' <Representation id="audio-en">',
1978
+ ' <BaseURL>a-en.mp4</BaseURL>',
1979
+ ' <SegmentBase indexRange="100-200" />',
1980
+ ' </Representation>',
1981
+ ' </AdaptationSet>',
1982
+ ' </Period>',
1983
+ '</MPD>',
1984
+ ].join('\n');
1985
+
1986
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1987
+
1988
+ /** @type {shaka.extern.Manifest} */
1989
+ const manifest = await parser.start('dummy://foo', playerInterface);
1990
+ expect(manifest.variants.length).toBe(1);
1991
+ const stream = manifest.variants[0].video;
1992
+ expect(stream.hdr).toBe('HLG');
1993
+ });
1994
+
1995
+ it('supports SDR signalling via EssentialProperty', async () => {
1996
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1997
+ const scheme = cicpScheme('TransferCharacteristics');
1998
+ const sdrValues = [1, 6, 13, 14, 15];
1999
+ const manifestPromises = [];
2000
+ for (const value of sdrValues) {
2001
+ const manifestText = [
2002
+ '<MPD minBufferTime="PT75S">',
2003
+ ' <Period id="1" duration="PT30S">',
2004
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2005
+ ` <EssentialProperty schemeIdUri="${scheme}" value="${value}" />`,
2006
+ ' <Representation codecs="avc1.640028">',
2007
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2008
+ ' <SegmentBase indexRange="100-200" />',
2009
+ ' </Representation>',
2010
+ ' </AdaptationSet>',
2011
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2012
+ ' <Representation id="audio-en">',
2013
+ ' <BaseURL>a-en.mp4</BaseURL>',
2014
+ ' <SegmentBase indexRange="100-200" />',
2015
+ ' </Representation>',
2016
+ ' </AdaptationSet>',
2017
+ ' </Period>',
2018
+ '</MPD>',
2019
+ ].join('\n');
2020
+
2021
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2022
+
2023
+ manifestPromises.push(parser.start('dummy://foo', playerInterface));
2024
+ }
2025
+ const manifests = await Promise.all(manifestPromises);
2026
+ for (const manifest of manifests) {
2027
+ expect(manifest.variants.length).toBe(1);
2028
+ const stream = manifest.variants[0].video;
2029
+ expect(stream.hdr).toBe('SDR');
2030
+ }
2031
+ });
2032
+
1888
2033
  it('Does not error when image adaptation sets are present', async () => {
1889
2034
  const manifestText = [
1890
2035
  '<MPD minBufferTime="PT75S">',
@@ -2108,4 +2253,77 @@ describe('DashParser Manifest', () => {
2108
2253
  expect(uri).not.toContain('/p2/');
2109
2254
  }
2110
2255
  });
2256
+
2257
+ /**
2258
+ * @param {!Array.<number>} periods Start time of multiple periods
2259
+ * @return {string}
2260
+ */
2261
+ function buildManifestWithPeriodStartTime(periods) {
2262
+ const mpdTemplate = [
2263
+ `<MPD type="dynamic"`,
2264
+ 'availabilityStartTime="1970-01-01T00:00:00Z"',
2265
+ 'timeShiftBufferDepth="PT10H">',
2266
+ ' %(periods)s',
2267
+ '</MPD>',
2268
+ ].join('\n');
2269
+ const periodTemplate = (id, period, duration) => {
2270
+ return [
2271
+ ` <Period id="${id}" start="PT${period}S">`,
2272
+ ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
2273
+ ' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
2274
+ ' <SegmentTimeline>',
2275
+ ` <S t="0" d="${duration}" />`,
2276
+ ' </SegmentTimeline>',
2277
+ ' </SegmentTemplate>',
2278
+ ' <Representation id="1"/>',
2279
+ ' </AdaptationSet>',
2280
+ ' </Period>',
2281
+ ].join('\n');
2282
+ };
2283
+ const periodXmls = periods.map((period, i) => {
2284
+ const duration = i+1 === periods.length ? 10 : periods[i+1] - period;
2285
+ // Period start time as ID here. If we use index then there will be
2286
+ // periods with same period ID and different start time which are invalid.
2287
+ return periodTemplate(period, period, duration);
2288
+ });
2289
+ return sprintf(mpdTemplate, {
2290
+ periods: periodXmls.join('\n'),
2291
+ });
2292
+ }
2293
+
2294
+ // Bug description: Inconsistent period start time in the manifests due
2295
+ // to failover triggered in backend servers
2296
+
2297
+ // When one of the servers is down, the manifest will be served by other
2298
+ // redundant servers. The period start time might become out of sync
2299
+ // during the switch-over/recovery.
2300
+
2301
+ it('skip periods that are earlier than max period start time', async () => {
2302
+ const sources = [
2303
+ buildManifestWithPeriodStartTime([5, 15]),
2304
+ buildManifestWithPeriodStartTime([6, 15]), // simulate out-of-sync of +1s
2305
+ buildManifestWithPeriodStartTime([4, 15]), // simulate out-of-sync of -1s
2306
+ ];
2307
+ const segments = [];
2308
+
2309
+ for (const source of sources) {
2310
+ fakeNetEngine.setResponseText('dummy://foo', source);
2311
+ /** @type {shaka.extern.Manifest} */
2312
+ // eslint-disable-next-line no-await-in-loop
2313
+ const manifest = await parser.start('dummy://foo', playerInterface);
2314
+ const video = manifest.variants[0].video;
2315
+ // eslint-disable-next-line no-await-in-loop
2316
+ await video.createSegmentIndex();
2317
+ goog.asserts.assert(video.segmentIndex, 'Null segmentIndex!');
2318
+ segments.push(Array.from(video.segmentIndex));
2319
+ }
2320
+
2321
+ // Expect identical segments
2322
+ expect(segments[0].length).toBe(2);
2323
+ expect(segments[1].length).toBe(2);
2324
+ expect(segments[0][0].startTime).toBe(5);
2325
+ expect(segments[1][0].startTime).toBe(5);
2326
+ expect(segments[0][1].startTime).toBe(15);
2327
+ expect(segments[1][1].startTime).toBe(15);
2328
+ });
2111
2329
  });
@@ -127,9 +127,9 @@ describe('DashParser SegmentTemplate', () => {
127
127
  // The first segment is number 1 and position 0.
128
128
  // Although the segment is 60 seconds long, it is clipped to the period
129
129
  // duration of 30 seconds.
130
- const references = [
131
- ManifestParser.makeReference('s1.mp4', 0, 30, baseUri),
132
- ];
130
+ const ref = ManifestParser.makeReference('s1.mp4', 0, 30, baseUri);
131
+ ref.trueEndTime = 60;
132
+ const references = [ref];
133
133
  await Dash.testSegmentIndex(source, references);
134
134
  });
135
135
 
@@ -783,7 +783,7 @@ describe('HlsParser', () => {
783
783
  'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
784
784
  'video2\n',
785
785
  '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
786
- 'URI="audio"\n',
786
+ 'DEFAULT=YES,URI="audio"\n',
787
787
  '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
788
788
  'URI="audio2"\n',
789
789
  ].join('');
@@ -801,6 +801,7 @@ describe('HlsParser', () => {
801
801
  manifest.anyTimeline();
802
802
  manifest.addPartialVariant((variant) => {
803
803
  variant.bandwidth = 200;
804
+ variant.primary = true;
804
805
  variant.addPartialStream(ContentType.VIDEO, (stream) => {
805
806
  stream.size(960, 540);
806
807
  });
@@ -810,6 +811,7 @@ describe('HlsParser', () => {
810
811
  });
811
812
  manifest.addPartialVariant((variant) => {
812
813
  variant.bandwidth = 300;
814
+ variant.primary = false;
813
815
  variant.addPartialStream(ContentType.VIDEO, (stream) => {
814
816
  stream.size(960, 540);
815
817
  });
@@ -1307,6 +1309,8 @@ describe('HlsParser', () => {
1307
1309
  // stream.
1308
1310
  const timeline = actual.presentationTimeline;
1309
1311
  expect(timeline.getDuration()).toBe(10);
1312
+ expect(timeline.getSeekRangeStart()).toBe(0);
1313
+ expect(timeline.getSeekRangeEnd()).toBe(10);
1310
1314
 
1311
1315
  expect(actual.textStreams.length).toBe(1);
1312
1316
  expect(actual.variants.length).toBe(1);
@@ -0,0 +1,20 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.require('shaka.media.ClosedCaptionParser');
8
+ goog.require('shaka.test.Util');
9
+
10
+ describe('ClosedCaptionParser', () => {
11
+ it('can handle empty caption packets', async () => {
12
+ const initSegment = await shaka.test.Util.fetch(
13
+ 'base/test/test/assets/empty_caption_video_init.mp4');
14
+ const videoSegment = await shaka.test.Util.fetch(
15
+ 'base/test/test/assets/empty_caption_video_segment.mp4');
16
+ const parser = new shaka.media.ClosedCaptionParser();
17
+ parser.init(initSegment);
18
+ parser.parseFrom(videoSegment);
19
+ });
20
+ });
@@ -2278,6 +2278,49 @@ function testDrmEngine(useMediaCapabilities) {
2278
2278
  [drmInfoAudio]);
2279
2279
  expect(returned).toEqual([drmInfoDesired]);
2280
2280
  });
2281
+
2282
+ it('dedupes the merged init data based on keyId matching', () => {
2283
+ const serverCert = new Uint8Array(0);
2284
+ const drmInfoVideo = {
2285
+ keySystem: 'drm.abc',
2286
+ licenseServerUri: 'http://abc.drm/license',
2287
+ distinctiveIdentifierRequired: false,
2288
+ persistentStateRequired: true,
2289
+ videoRobustness: 'really_really_ridiculously_good',
2290
+ serverCertificate: serverCert,
2291
+ serverCertificateUri: '',
2292
+ initData: [{keyId: 'v-init'}],
2293
+ keyIds: new Set(['deadbeefdeadbeefdeadbeefdeadbeef']),
2294
+ };
2295
+ const drmInfoAudio = {
2296
+ keySystem: 'drm.abc',
2297
+ licenseServerUri: undefined,
2298
+ distinctiveIdentifierRequired: true,
2299
+ persistentStateRequired: false,
2300
+ audioRobustness: 'good',
2301
+ serverCertificate: undefined,
2302
+ serverCertificateUri: '',
2303
+ initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
2304
+ keyIds: new Set(['eadbeefdeadbeefdeadbeefdeadbeefd']),
2305
+ };
2306
+ const drmInfoDesired = {
2307
+ keySystem: 'drm.abc',
2308
+ licenseServerUri: 'http://abc.drm/license',
2309
+ distinctiveIdentifierRequired: true,
2310
+ persistentStateRequired: true,
2311
+ audioRobustness: 'good',
2312
+ videoRobustness: 'really_really_ridiculously_good',
2313
+ serverCertificate: serverCert,
2314
+ initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
2315
+ keyIds: new Set([
2316
+ 'deadbeefdeadbeefdeadbeefdeadbeef',
2317
+ 'eadbeefdeadbeefdeadbeefdeadbeefd',
2318
+ ]),
2319
+ };
2320
+ const returned = shaka.media.DrmEngine.getCommonDrmInfos([drmInfoVideo],
2321
+ [drmInfoAudio]);
2322
+ expect(returned).toEqual([drmInfoDesired]);
2323
+ });
2281
2324
  }); // describe('getCommonDrmInfos')
2282
2325
 
2283
2326
  describe('configure', () => {
@@ -2412,6 +2455,19 @@ function testDrmEngine(useMediaCapabilities) {
2412
2455
  await expectAsync(drmEngine.removeSession('abc'))
2413
2456
  .toBeRejectedWith(expected);
2414
2457
  });
2458
+
2459
+ // Regression test for #3534
2460
+ it('does not remove the same session again on destroy', async () => {
2461
+ updatePromise.resolve();
2462
+ expect(session1.remove).not.toHaveBeenCalled();
2463
+ await drmEngine.removeSession('abc');
2464
+ expect(session1.remove).toHaveBeenCalled();
2465
+ session1.remove.calls.reset();
2466
+ await drmEngine.destroy();
2467
+ // The session should only be removed ONCE. If it's double-removed, it
2468
+ // will make a (non-fatal) DOMException.
2469
+ expect(session1.remove).not.toHaveBeenCalled();
2470
+ });
2415
2471
  });
2416
2472
 
2417
2473
  describe('expiration', () => {
@@ -1296,12 +1296,13 @@ describe('Playhead', () => {
1296
1296
  });
1297
1297
 
1298
1298
  // Regression test for https://github.com/google/shaka-player/issues/2987
1299
- it('does gap jump if paused at 0', () => {
1299
+ it('does gap jump if paused at 0 and has autoplay', () => {
1300
1300
  const buffered = [{start: 10, end: 20}];
1301
1301
  video.buffered = createFakeBuffered(buffered);
1302
1302
  video.currentTime = 0;
1303
1303
  video.readyState = HTMLMediaElement.HAVE_ENOUGH_DATA;
1304
1304
  video.paused = true;
1305
+ video.autoplay = true;
1305
1306
 
1306
1307
  config.jumpLargeGaps = true;
1307
1308
  playhead = new shaka.media.MediaSourcePlayhead(
@@ -1319,6 +1320,31 @@ describe('Playhead', () => {
1319
1320
  expect(video.currentTime).toBe(10);
1320
1321
  });
1321
1322
 
1323
+ // Regression test for https://github.com/google/shaka-player/issues/3451
1324
+ it('doesn\'t gap jump if paused at 0 and hasn\'t autoplay', () => {
1325
+ const buffered = [{start: 10, end: 20}];
1326
+ video.buffered = createFakeBuffered(buffered);
1327
+ video.currentTime = 0;
1328
+ video.readyState = HTMLMediaElement.HAVE_ENOUGH_DATA;
1329
+ video.paused = true;
1330
+ video.autoplay = false;
1331
+
1332
+ config.jumpLargeGaps = true;
1333
+ playhead = new shaka.media.MediaSourcePlayhead(
1334
+ video,
1335
+ manifest,
1336
+ config,
1337
+ /* startTime= */ 0,
1338
+ Util.spyFunc(onSeek),
1339
+ Util.spyFunc(onEvent));
1340
+
1341
+ playhead.notifyOfBufferingChange();
1342
+ jasmine.clock().tick(500);
1343
+
1344
+ // There should NOT have been a gap jump.
1345
+ expect(video.currentTime).toBe(0);
1346
+ });
1347
+
1322
1348
  /**
1323
1349
  * @param {string} name
1324
1350
  * @param {SeekTestInfo} data
@@ -290,6 +290,25 @@ describe('PresentationTimeline', () => {
290
290
  // last segment time (50)
291
291
  expect(timeline.getSegmentAvailabilityEnd()).toBe(50);
292
292
  });
293
+
294
+ it('calculates time when there a transition of live to static', () => {
295
+ const timeline = makeLiveTimeline(/* availability= */ 20);
296
+
297
+ const ref1 = makeSegmentReference(0, 10);
298
+ const ref2 = makeSegmentReference(10, 20);
299
+ const ref3 = makeSegmentReference(20, 30);
300
+ const ref4 = makeSegmentReference(30, 40);
301
+ const ref5 = makeSegmentReference(40, 50);
302
+
303
+ setElapsed(50);
304
+ timeline.notifySegments([ref1, ref2, ref3, ref4, ref5]);
305
+
306
+ expect(timeline.getSegmentAvailabilityEnd()).toBe(50);
307
+
308
+ timeline.setStatic(true);
309
+
310
+ expect(timeline.getSegmentAvailabilityEnd()).toBe(50);
311
+ });
293
312
  });
294
313
 
295
314
  describe('getDuration', () => {
@@ -419,8 +419,9 @@ describe('StreamingEngine', () => {
419
419
  // 3. Playhead seeks to force us back inside the window
420
420
  // 4. (maybe) seek if there is a gap at the period boundary
421
421
  // 5. (maybe) seek to flush a pipeline stall
422
+ // 6. (maybe) on slower platforms (e.g. GitHub actions)
422
423
  expect(seekCount).toBeGreaterThan(2);
423
- expect(seekCount).toBeLessThan(6);
424
+ expect(seekCount).toBeLessThan(7);
424
425
  });
425
426
  });
426
427
 
@@ -97,6 +97,9 @@ describe('StreamingEngine', () => {
97
97
  /** @type {!shaka.media.StreamingEngine} */
98
98
  let streamingEngine;
99
99
 
100
+ /** @type {function(function(), number)} */
101
+ let realSetTimeout;
102
+
100
103
  /**
101
104
  * Runs the fake event loop.
102
105
  * @param {function()=} callback An optional callback that is executed
@@ -116,6 +119,7 @@ describe('StreamingEngine', () => {
116
119
  }
117
120
 
118
121
  beforeAll(() => {
122
+ realSetTimeout = window.setTimeout;
119
123
  jasmine.clock().install();
120
124
  jasmine.clock().mockDate();
121
125
  });
@@ -822,6 +826,53 @@ describe('StreamingEngine', () => {
822
826
  .toHaveBeenCalledWith('video', 0, lt20, gt40);
823
827
  });
824
828
 
829
+ // Regression test for https://github.com/google/shaka-player/issues/3717
830
+ it('applies fudge factors for the duration', async () => {
831
+ setupVod();
832
+
833
+ // In #3717, the duration was just barely large enough to encompass an
834
+ // additional segment, but that segment didn't exist, so playback never
835
+ // completed. Here, we set the duration to just beyond the 3rd segment, and
836
+ // we make the 4th segment fail when requested.
837
+ const duration = 30.000000005;
838
+ timeline.getDuration.and.returnValue(duration);
839
+
840
+ const targetUri = '1_video_3'; // The URI of the 4th video segment.
841
+ failRequestsForTarget(netEngine, targetUri);
842
+
843
+ mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
844
+ createStreamingEngine();
845
+
846
+ // Here we go!
847
+ streamingEngine.switchVariant(variant);
848
+ streamingEngine.switchTextStream(textStream);
849
+ await streamingEngine.start();
850
+ playing = true;
851
+ await runTest();
852
+
853
+ // The end of the stream should have been reached, and the 4th segment from
854
+ // each type should never have been requested.
855
+ expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
856
+
857
+ const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
858
+
859
+ netEngine.expectRequest('0_audio_0', segmentType);
860
+ netEngine.expectRequest('0_video_0', segmentType);
861
+ netEngine.expectRequest('0_text_0', segmentType);
862
+
863
+ netEngine.expectRequest('0_audio_1', segmentType);
864
+ netEngine.expectRequest('0_video_1', segmentType);
865
+ netEngine.expectRequest('0_text_1', segmentType);
866
+
867
+ netEngine.expectRequest('1_audio_2', segmentType);
868
+ netEngine.expectRequest('1_video_2', segmentType);
869
+ netEngine.expectRequest('1_text_2', segmentType);
870
+
871
+ netEngine.expectNoRequest('1_audio_3', segmentType);
872
+ netEngine.expectNoRequest('1_video_3', segmentType);
873
+ netEngine.expectNoRequest('1_text_3', segmentType);
874
+ });
875
+
825
876
  it('does not buffer one media type ahead of another', async () => {
826
877
  setupVod();
827
878
  mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
@@ -835,7 +886,7 @@ describe('StreamingEngine', () => {
835
886
  // This would let some media types buffer faster than others if unchecked.
836
887
  netEngineDelays.text = 0.1;
837
888
  netEngineDelays.audio = 1.0;
838
- netEngineDelays.video = 10.0;
889
+ netEngineDelays.video = 5.0; // Need init segment and media segment
839
890
 
840
891
  mediaSourceEngine.appendBuffer.and.callFake((type, data, start, end) => {
841
892
  // Call to the underlying implementation.
@@ -1389,6 +1440,66 @@ describe('StreamingEngine', () => {
1389
1440
  });
1390
1441
  });
1391
1442
 
1443
+ it('into unbuffered regions when nothing is buffered ' +
1444
+ 'and mediaState is performing an update', async () => {
1445
+ // Here we go!
1446
+ streamingEngine.switchVariant(variant);
1447
+ streamingEngine.switchTextStream(textStream);
1448
+ // ensure init source buffer promise does not resolve before seeked()
1449
+ // so mediaState remains in "performingUpdate" state
1450
+ const initSourceBufferPromise = new shaka.util.PublicPromise();
1451
+ mediaSourceEngine.setStreamProperties.and
1452
+ .returnValue(initSourceBufferPromise);
1453
+ await streamingEngine.start();
1454
+ playing = true;
1455
+
1456
+ // tick to trigger mediaState updates
1457
+ jasmine.clock().tick(1);
1458
+ // give a chance for fetchAndAppend_ to be invoked
1459
+ // by async onUpdate_ callback
1460
+ await Util.shortDelay(realSetTimeout);
1461
+
1462
+ // Nothing is buffered yet.
1463
+ expect(mediaSourceEngine.segments).toEqual({
1464
+ audio: [false, false, false, false],
1465
+ video: [false, false, false, false],
1466
+ text: [false, false, false, false],
1467
+ });
1468
+
1469
+ // Seek forward to an unbuffered region in the first Period.
1470
+ presentationTimeInSeconds = 15;
1471
+ streamingEngine.seeked();
1472
+
1473
+ // resolve initSourceBufferPromise after seeked(), waitingToClearBuffer
1474
+ // should have been set, so this will now trigger the actual flush of
1475
+ // the buffer
1476
+ initSourceBufferPromise.resolve();
1477
+ mediaSourceEngine.setStreamProperties.and
1478
+ .returnValue(Promise.resolve());
1479
+
1480
+ // allow mediaState update to resolve
1481
+ await Util.shortDelay(realSetTimeout);
1482
+
1483
+ onTick.and.callFake(() => {
1484
+ expect(mediaSourceEngine.clear).toHaveBeenCalled();
1485
+ onTick.and.stub();
1486
+ });
1487
+
1488
+ await runTest(Util.spyFunc(onTick));
1489
+
1490
+ // Verify buffers.
1491
+ expect(mediaSourceEngine.initSegments).toEqual({
1492
+ audio: [false, true],
1493
+ video: [false, true],
1494
+ text: [],
1495
+ });
1496
+ expect(mediaSourceEngine.segments).toEqual({
1497
+ audio: [false, true, true, true],
1498
+ video: [false, true, true, true],
1499
+ text: [false, true, true, true],
1500
+ });
1501
+ });
1502
+
1392
1503
  it('into unbuffered regions near segment start', async () => {
1393
1504
  // Here we go!
1394
1505
  streamingEngine.switchVariant(variant);
@@ -2170,13 +2281,13 @@ describe('StreamingEngine', () => {
2170
2281
  .bind(mediaSourceEngine);
2171
2282
 
2172
2283
  mediaSourceEngine.remove.and.callFake((type, start, end) => {
2173
- expect(presentationTimeInSeconds).toBe(20);
2284
+ expect(presentationTimeInSeconds).toBeGreaterThanOrEqual(20);
2174
2285
  expect(start).toBe(0);
2175
2286
  expect(end).toBe(10);
2176
2287
 
2177
2288
  if (mediaSourceEngine.remove.calls.count() == 3) {
2178
2289
  mediaSourceEngine.remove.and.callFake((type, start, end) => {
2179
- expect(presentationTimeInSeconds).toBe(30);
2290
+ expect(presentationTimeInSeconds).toBeGreaterThanOrEqual(30);
2180
2291
  expect(start).toBe(10);
2181
2292
  expect(end).toBe(20);
2182
2293
  return originalRemove(type, start, end);
@@ -3388,6 +3499,40 @@ describe('StreamingEngine', () => {
3388
3499
  segmentAvailability.end++;
3389
3500
  }
3390
3501
 
3502
+ /**
3503
+ * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3504
+ * look-alike.
3505
+ * @param {string} targetUri
3506
+ * @param {shaka.util.Error.Code=} errorCode
3507
+ */
3508
+ function failRequestsForTarget(
3509
+ netEngine, targetUri, errorCode=shaka.util.Error.Code.BAD_HTTP_STATUS) {
3510
+ // eslint-disable-next-line no-restricted-syntax
3511
+ const originalNetEngineRequest = netEngine.request.bind(netEngine);
3512
+
3513
+ netEngine.request = jasmine.createSpy('request').and.callFake(
3514
+ (requestType, request) => {
3515
+ if (request.uris[0] == targetUri) {
3516
+ const data = [targetUri];
3517
+
3518
+ if (errorCode == shaka.util.Error.Code.BAD_HTTP_STATUS) {
3519
+ data.push(404);
3520
+ data.push('');
3521
+ }
3522
+
3523
+ // The compiler still sees the error code parameter as potentially
3524
+ // undefined, even though we gave it a default value.
3525
+ goog.asserts.assert(errorCode != undefined, 'Undefined error code');
3526
+
3527
+ return shaka.util.AbortableOperation.failed(new shaka.util.Error(
3528
+ shaka.util.Error.Severity.CRITICAL,
3529
+ shaka.util.Error.Category.NETWORK,
3530
+ errorCode, data));
3531
+ }
3532
+ return originalNetEngineRequest(requestType, request);
3533
+ });
3534
+ }
3535
+
3391
3536
  /**
3392
3537
  * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3393
3538
  * look-alike.