shaka-player 3.2.0 → 3.2.4
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.
- package/.github/workflows/build_and_test.yaml +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/AUTHORS +3 -0
- package/CHANGELOG.md +182 -1
- package/CONTRIBUTORS +3 -0
- package/README.md +4 -4
- package/build/checkversion.py +1 -1
- package/build/generateExterns.js +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/build/test.py +6 -0
- package/demo/common/assets.js +1 -1
- package/demo/index.html +1 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/deps.js +8 -8
- package/dist/locales.js +37 -37
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/shaka-player.compiled.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.externs.js +226 -14
- package/dist/shaka-player.compiled.debug.js +424 -399
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +226 -14
- package/dist/shaka-player.compiled.js +826 -812
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +197 -16
- package/dist/shaka-player.ui.debug.d.ts +197 -16
- package/dist/shaka-player.ui.debug.externs.js +230 -27
- package/dist/shaka-player.ui.debug.js +466 -442
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +230 -27
- package/dist/shaka-player.ui.js +946 -932
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +14 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +25 -0
- package/externs/shaka/player.js +8 -0
- package/externs/shaka/text.js +10 -0
- package/karma.conf.js +20 -15
- package/lib/ads/ad_manager.js +46 -26
- package/lib/ads/client_side_ad_manager.js +82 -88
- package/lib/ads/server_side_ad_manager.js +45 -13
- package/lib/cast/cast_proxy.js +6 -2
- package/lib/cast/cast_receiver.js +5 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cea/sei_processor.js +24 -17
- package/lib/dash/dash_parser.js +131 -24
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +74 -17
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/closed_caption_parser.js +3 -1
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/drm_engine.js +20 -1
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/playhead.js +1 -1
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/segment_index.js +2 -1
- package/lib/media/segment_reference.js +19 -1
- package/lib/media/streaming_engine.js +8 -4
- package/lib/net/networking_engine.js +6 -1
- package/lib/offline/download_manager.js +1 -2
- package/lib/offline/storage.js +8 -9
- package/lib/player.js +129 -59
- package/lib/polyfill/aria.js +12 -2
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/media_capabilities.js +73 -21
- package/lib/polyfill/mediasource.js +8 -0
- package/lib/polyfill/orientation.js +3 -1
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/storage_estimate.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/cue.js +6 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +135 -87
- package/lib/text/ui_text_displayer.js +213 -80
- package/lib/text/vtt_text_parser.js +4 -2
- package/lib/text/web_vtt_generator.js +21 -12
- package/lib/util/fake_event.js +53 -7
- package/lib/util/fake_event_target.js +21 -1
- package/lib/util/mp4_box_parsers.js +1 -1
- package/lib/util/periods.js +4 -1
- package/lib/util/platform.js +13 -1
- package/lib/util/stream_utils.js +105 -13
- package/lib/util/xml_utils.js +50 -12
- package/package.json +3 -6
- package/roadmap.md +24 -22
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -0
- package/test/dash/dash_parser_live_unit.js +43 -0
- package/test/dash/dash_parser_manifest_unit.js +222 -3
- package/test/hls/hls_parser_unit.js +79 -1
- package/test/media/closed_caption_parser_unit.js +20 -0
- package/test/media/drm_engine_integration.js +86 -2
- package/test/media/drm_engine_unit.js +57 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +145 -0
- package/test/player_integration.js +79 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +14 -4
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/assets/empty_caption_video_init.mp4 +0 -0
- package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
- package/test/test/util/fake_ad_manager.js +11 -1
- package/test/test/util/ttml_utils.js +81 -0
- package/test/test/util/util.js +10 -7
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +542 -220
- package/test/text/ui_text_displayer_unit.js +93 -20
- package/test/text/vtt_text_parser_unit.js +17 -1
- package/test/text/web_vtt_generator_unit.js +58 -3
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +21 -8
- package/test/util/mp4_box_parsers_unit.js +28 -0
- package/test/util/stream_utils_unit.js +60 -3
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +11 -6
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +6 -7
- package/ui/locales/ru.json +6 -6
- package/ui/localization.js +40 -87
- package/ui/pip_button.js +2 -3
- package/ui/seek_bar.js +1 -2
- package/ui/text_selection.js +1 -0
|
@@ -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);
|
|
@@ -975,7 +983,8 @@ describe('DashParser Manifest', () => {
|
|
|
975
983
|
|
|
976
984
|
it('duplicate Representation ids with live', async () => {
|
|
977
985
|
const source = [
|
|
978
|
-
'<MPD minBufferTime="PT75S" type="dynamic"
|
|
986
|
+
'<MPD minBufferTime="PT75S" type="dynamic"',
|
|
987
|
+
' availabilityStartTime="1970-01-01T00:00:00Z">',
|
|
979
988
|
' <Period id="1" duration="PT30S">',
|
|
980
989
|
' <AdaptationSet mimeType="video/mp4">',
|
|
981
990
|
' <Representation id="1" bandwidth="1">',
|
|
@@ -1864,7 +1873,7 @@ describe('DashParser Manifest', () => {
|
|
|
1864
1873
|
expect(textStream.kind).toBe('caption');
|
|
1865
1874
|
});
|
|
1866
1875
|
|
|
1867
|
-
it('converts Roles element to "forced"', async () => {
|
|
1876
|
+
it('converts Roles element to "forced" (old role)', async () => {
|
|
1868
1877
|
const manifestText = [
|
|
1869
1878
|
'<MPD minBufferTime="PT75S">',
|
|
1870
1879
|
' <Period id="1" duration="PT30S">',
|
|
@@ -1896,7 +1905,40 @@ describe('DashParser Manifest', () => {
|
|
|
1896
1905
|
expect(textStream.forced).toBe(true);
|
|
1897
1906
|
});
|
|
1898
1907
|
|
|
1899
|
-
it('
|
|
1908
|
+
it('converts Roles element to "forced"', async () => {
|
|
1909
|
+
const manifestText = [
|
|
1910
|
+
'<MPD minBufferTime="PT75S">',
|
|
1911
|
+
' <Period id="1" duration="PT30S">',
|
|
1912
|
+
' <AdaptationSet id="1" contentType="text">',
|
|
1913
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1914
|
+
' value="captions" />',
|
|
1915
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1916
|
+
' value="forced-subtitle" />',
|
|
1917
|
+
' <Accessibility schemeIdUri="foobar" value="bar" />',
|
|
1918
|
+
' <Representation id="text-en" mimeType="text/webvtt">',
|
|
1919
|
+
' <BaseURL>t-en.vtt</BaseURL>',
|
|
1920
|
+
' </Representation>',
|
|
1921
|
+
' </AdaptationSet>',
|
|
1922
|
+
' <AdaptationSet id="1" mimeType="video/mp4">',
|
|
1923
|
+
' <Representation id="video-sd" width="640" height="480">',
|
|
1924
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
1925
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1926
|
+
' </Representation>',
|
|
1927
|
+
' </AdaptationSet>',
|
|
1928
|
+
' </Period>',
|
|
1929
|
+
'</MPD>',
|
|
1930
|
+
].join('\n');
|
|
1931
|
+
|
|
1932
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
1933
|
+
/** @type {shaka.extern.Manifest} */
|
|
1934
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1935
|
+
const textStream = manifest.textStreams[0];
|
|
1936
|
+
expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
|
|
1937
|
+
expect(textStream.forced).toBe(true);
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
it('supports HDR signaling via profiles', async () => {
|
|
1941
|
+
// (DASH-IF IOP v4.3 10.3.3.)
|
|
1900
1942
|
const hdrProfile =
|
|
1901
1943
|
'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
|
|
1902
1944
|
const manifestText = [
|
|
@@ -1927,6 +1969,110 @@ describe('DashParser Manifest', () => {
|
|
|
1927
1969
|
expect(stream.hdr).toBe('PQ');
|
|
1928
1970
|
});
|
|
1929
1971
|
|
|
1972
|
+
it('supports HDR signaling via SupplementalProperty', async () => {
|
|
1973
|
+
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
1974
|
+
const hdrScheme = cicpScheme('TransferCharacteristics');
|
|
1975
|
+
const pq = 16;
|
|
1976
|
+
const manifestText = [
|
|
1977
|
+
'<MPD minBufferTime="PT75S">',
|
|
1978
|
+
' <Period id="1" duration="PT30S">',
|
|
1979
|
+
' <AdaptationSet id="2" mimeType="video/mp4">',
|
|
1980
|
+
` <SupplementalProperty schemeIdUri="${hdrScheme}" value="${pq}" />`,
|
|
1981
|
+
' <Representation codecs="hvc1.2.4.L150.90">',
|
|
1982
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
1983
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1984
|
+
' </Representation>',
|
|
1985
|
+
' </AdaptationSet>',
|
|
1986
|
+
' <AdaptationSet id="3" mimeType="audio/mp4">',
|
|
1987
|
+
' <Representation id="audio-en">',
|
|
1988
|
+
' <BaseURL>a-en.mp4</BaseURL>',
|
|
1989
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1990
|
+
' </Representation>',
|
|
1991
|
+
' </AdaptationSet>',
|
|
1992
|
+
' </Period>',
|
|
1993
|
+
'</MPD>',
|
|
1994
|
+
].join('\n');
|
|
1995
|
+
|
|
1996
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
1997
|
+
|
|
1998
|
+
/** @type {shaka.extern.Manifest} */
|
|
1999
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2000
|
+
expect(manifest.variants.length).toBe(1);
|
|
2001
|
+
const stream = manifest.variants[0].video;
|
|
2002
|
+
expect(stream.hdr).toBe('PQ');
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
it('supports HDR signaling via EssentialProperty', async () => {
|
|
2006
|
+
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
2007
|
+
const hdrScheme = cicpScheme('TransferCharacteristics');
|
|
2008
|
+
const hlg = 18;
|
|
2009
|
+
const manifestText = [
|
|
2010
|
+
'<MPD minBufferTime="PT75S">',
|
|
2011
|
+
' <Period id="1" duration="PT30S">',
|
|
2012
|
+
' <AdaptationSet id="2" mimeType="video/mp4">',
|
|
2013
|
+
` <EssentialProperty schemeIdUri="${hdrScheme}" value="${hlg}" />`,
|
|
2014
|
+
' <Representation codecs="hvc1.2.4.L153.B0">',
|
|
2015
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
2016
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2017
|
+
' </Representation>',
|
|
2018
|
+
' </AdaptationSet>',
|
|
2019
|
+
' <AdaptationSet id="3" mimeType="audio/mp4">',
|
|
2020
|
+
' <Representation id="audio-en">',
|
|
2021
|
+
' <BaseURL>a-en.mp4</BaseURL>',
|
|
2022
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2023
|
+
' </Representation>',
|
|
2024
|
+
' </AdaptationSet>',
|
|
2025
|
+
' </Period>',
|
|
2026
|
+
'</MPD>',
|
|
2027
|
+
].join('\n');
|
|
2028
|
+
|
|
2029
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
2030
|
+
|
|
2031
|
+
/** @type {shaka.extern.Manifest} */
|
|
2032
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2033
|
+
expect(manifest.variants.length).toBe(1);
|
|
2034
|
+
const stream = manifest.variants[0].video;
|
|
2035
|
+
expect(stream.hdr).toBe('HLG');
|
|
2036
|
+
});
|
|
2037
|
+
|
|
2038
|
+
it('supports SDR signalling via EssentialProperty', async () => {
|
|
2039
|
+
// (DASH-IF IOP v4.3 6.2.5.1.)
|
|
2040
|
+
const scheme = cicpScheme('TransferCharacteristics');
|
|
2041
|
+
const sdrValues = [1, 6, 13, 14, 15];
|
|
2042
|
+
const manifestPromises = [];
|
|
2043
|
+
for (const value of sdrValues) {
|
|
2044
|
+
const manifestText = [
|
|
2045
|
+
'<MPD minBufferTime="PT75S">',
|
|
2046
|
+
' <Period id="1" duration="PT30S">',
|
|
2047
|
+
' <AdaptationSet id="2" mimeType="video/mp4">',
|
|
2048
|
+
` <EssentialProperty schemeIdUri="${scheme}" value="${value}" />`,
|
|
2049
|
+
' <Representation codecs="avc1.640028">',
|
|
2050
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
2051
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2052
|
+
' </Representation>',
|
|
2053
|
+
' </AdaptationSet>',
|
|
2054
|
+
' <AdaptationSet id="3" mimeType="audio/mp4">',
|
|
2055
|
+
' <Representation id="audio-en">',
|
|
2056
|
+
' <BaseURL>a-en.mp4</BaseURL>',
|
|
2057
|
+
' <SegmentBase indexRange="100-200" />',
|
|
2058
|
+
' </Representation>',
|
|
2059
|
+
' </AdaptationSet>',
|
|
2060
|
+
' </Period>',
|
|
2061
|
+
'</MPD>',
|
|
2062
|
+
].join('\n');
|
|
2063
|
+
|
|
2064
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
2065
|
+
|
|
2066
|
+
manifestPromises.push(parser.start('dummy://foo', playerInterface));
|
|
2067
|
+
}
|
|
2068
|
+
const manifests = await Promise.all(manifestPromises);
|
|
2069
|
+
for (const manifest of manifests) {
|
|
2070
|
+
expect(manifest.variants.length).toBe(1);
|
|
2071
|
+
const stream = manifest.variants[0].video;
|
|
2072
|
+
expect(stream.hdr).toBe('SDR');
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
|
|
1930
2076
|
it('Does not error when image adaptation sets are present', async () => {
|
|
1931
2077
|
const manifestText = [
|
|
1932
2078
|
'<MPD minBufferTime="PT75S">',
|
|
@@ -2150,4 +2296,77 @@ describe('DashParser Manifest', () => {
|
|
|
2150
2296
|
expect(uri).not.toContain('/p2/');
|
|
2151
2297
|
}
|
|
2152
2298
|
});
|
|
2299
|
+
|
|
2300
|
+
/**
|
|
2301
|
+
* @param {!Array.<number>} periods Start time of multiple periods
|
|
2302
|
+
* @return {string}
|
|
2303
|
+
*/
|
|
2304
|
+
function buildManifestWithPeriodStartTime(periods) {
|
|
2305
|
+
const mpdTemplate = [
|
|
2306
|
+
`<MPD type="dynamic"`,
|
|
2307
|
+
'availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
2308
|
+
'timeShiftBufferDepth="PT10H">',
|
|
2309
|
+
' %(periods)s',
|
|
2310
|
+
'</MPD>',
|
|
2311
|
+
].join('\n');
|
|
2312
|
+
const periodTemplate = (id, period, duration) => {
|
|
2313
|
+
return [
|
|
2314
|
+
` <Period id="${id}" start="PT${period}S">`,
|
|
2315
|
+
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
|
|
2316
|
+
' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
|
|
2317
|
+
' <SegmentTimeline>',
|
|
2318
|
+
` <S t="0" d="${duration}" />`,
|
|
2319
|
+
' </SegmentTimeline>',
|
|
2320
|
+
' </SegmentTemplate>',
|
|
2321
|
+
' <Representation id="1"/>',
|
|
2322
|
+
' </AdaptationSet>',
|
|
2323
|
+
' </Period>',
|
|
2324
|
+
].join('\n');
|
|
2325
|
+
};
|
|
2326
|
+
const periodXmls = periods.map((period, i) => {
|
|
2327
|
+
const duration = i+1 === periods.length ? 10 : periods[i+1] - period;
|
|
2328
|
+
// Period start time as ID here. If we use index then there will be
|
|
2329
|
+
// periods with same period ID and different start time which are invalid.
|
|
2330
|
+
return periodTemplate(period, period, duration);
|
|
2331
|
+
});
|
|
2332
|
+
return sprintf(mpdTemplate, {
|
|
2333
|
+
periods: periodXmls.join('\n'),
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// Bug description: Inconsistent period start time in the manifests due
|
|
2338
|
+
// to failover triggered in backend servers
|
|
2339
|
+
|
|
2340
|
+
// When one of the servers is down, the manifest will be served by other
|
|
2341
|
+
// redundant servers. The period start time might become out of sync
|
|
2342
|
+
// during the switch-over/recovery.
|
|
2343
|
+
|
|
2344
|
+
it('skip periods that are earlier than max period start time', async () => {
|
|
2345
|
+
const sources = [
|
|
2346
|
+
buildManifestWithPeriodStartTime([5, 15]),
|
|
2347
|
+
buildManifestWithPeriodStartTime([6, 15]), // simulate out-of-sync of +1s
|
|
2348
|
+
buildManifestWithPeriodStartTime([4, 15]), // simulate out-of-sync of -1s
|
|
2349
|
+
];
|
|
2350
|
+
const segments = [];
|
|
2351
|
+
|
|
2352
|
+
for (const source of sources) {
|
|
2353
|
+
fakeNetEngine.setResponseText('dummy://foo', source);
|
|
2354
|
+
/** @type {shaka.extern.Manifest} */
|
|
2355
|
+
// eslint-disable-next-line no-await-in-loop
|
|
2356
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2357
|
+
const video = manifest.variants[0].video;
|
|
2358
|
+
// eslint-disable-next-line no-await-in-loop
|
|
2359
|
+
await video.createSegmentIndex();
|
|
2360
|
+
goog.asserts.assert(video.segmentIndex, 'Null segmentIndex!');
|
|
2361
|
+
segments.push(Array.from(video.segmentIndex));
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// Expect identical segments
|
|
2365
|
+
expect(segments[0].length).toBe(2);
|
|
2366
|
+
expect(segments[1].length).toBe(2);
|
|
2367
|
+
expect(segments[0][0].startTime).toBe(5);
|
|
2368
|
+
expect(segments[1][0].startTime).toBe(5);
|
|
2369
|
+
expect(segments[0][1].startTime).toBe(15);
|
|
2370
|
+
expect(segments[1][1].startTime).toBe(15);
|
|
2371
|
+
});
|
|
2153
2372
|
});
|
|
@@ -209,6 +209,80 @@ describe('HlsParser', () => {
|
|
|
209
209
|
expect(actual).toEqual(manifest);
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
it('parses manifest attributes with space', async () => {
|
|
213
|
+
const master = [
|
|
214
|
+
'#EXTM3U\n',
|
|
215
|
+
'#EXT-X-MEDIA:TYPE=AUDIO, GROUP-ID="aud1", LANGUAGE="eng", ',
|
|
216
|
+
'CHANNELS="16/JOC", URI="audio"\n',
|
|
217
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub1", LANGUAGE="eng", ',
|
|
218
|
+
'URI="text"\n',
|
|
219
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub2", LANGUAGE="es", ',
|
|
220
|
+
'URI="text2"\n',
|
|
221
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200, CODECS="avc1,mp4a", ',
|
|
222
|
+
'RESOLUTION=960x540, FRAME-RATE=60, AUDIO="aud1"\n',
|
|
223
|
+
'video\n',
|
|
224
|
+
].join('');
|
|
225
|
+
|
|
226
|
+
const media = [
|
|
227
|
+
'#EXTM3U\n',
|
|
228
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
229
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
230
|
+
'#EXTINF:5,\n',
|
|
231
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
232
|
+
'main.mp4',
|
|
233
|
+
].join('');
|
|
234
|
+
|
|
235
|
+
const textMedia = [
|
|
236
|
+
'#EXTM3U\n',
|
|
237
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
238
|
+
'#EXTINF:5,\n',
|
|
239
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
240
|
+
'main.vtt',
|
|
241
|
+
].join('');
|
|
242
|
+
|
|
243
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
244
|
+
manifest.anyTimeline();
|
|
245
|
+
manifest.addPartialVariant((variant) => {
|
|
246
|
+
variant.language = 'en';
|
|
247
|
+
variant.bandwidth = 200;
|
|
248
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
249
|
+
stream.frameRate = 60;
|
|
250
|
+
stream.mime('video/mp4', 'avc1');
|
|
251
|
+
stream.size(960, 540);
|
|
252
|
+
});
|
|
253
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
254
|
+
stream.language = 'en';
|
|
255
|
+
stream.channelsCount = 16;
|
|
256
|
+
stream.spatialAudio = true;
|
|
257
|
+
stream.mime('audio/mp4', 'mp4a');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
manifest.addPartialTextStream((stream) => {
|
|
261
|
+
stream.language = 'en';
|
|
262
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
263
|
+
stream.mime('text/vtt', '');
|
|
264
|
+
});
|
|
265
|
+
manifest.addPartialTextStream((stream) => {
|
|
266
|
+
stream.language = 'es';
|
|
267
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
268
|
+
stream.mime('text/vtt', '');
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
fakeNetEngine
|
|
273
|
+
.setResponseText('test:/master', master)
|
|
274
|
+
.setResponseText('test:/audio', media)
|
|
275
|
+
.setResponseText('test:/video', media)
|
|
276
|
+
.setResponseText('test:/text', textMedia)
|
|
277
|
+
.setResponseText('test:/text2', textMedia)
|
|
278
|
+
.setResponseText('test:/main.vtt', vttText)
|
|
279
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
280
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
281
|
+
|
|
282
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
283
|
+
expect(actual).toEqual(manifest);
|
|
284
|
+
});
|
|
285
|
+
|
|
212
286
|
it('prioritize AVERAGE-BANDWIDTH to BANDWIDTH', async () => {
|
|
213
287
|
const master = [
|
|
214
288
|
'#EXTM3U\n',
|
|
@@ -783,7 +857,7 @@ describe('HlsParser', () => {
|
|
|
783
857
|
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
|
|
784
858
|
'video2\n',
|
|
785
859
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
786
|
-
'URI="audio"\n',
|
|
860
|
+
'DEFAULT=YES,URI="audio"\n',
|
|
787
861
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
|
|
788
862
|
'URI="audio2"\n',
|
|
789
863
|
].join('');
|
|
@@ -801,6 +875,7 @@ describe('HlsParser', () => {
|
|
|
801
875
|
manifest.anyTimeline();
|
|
802
876
|
manifest.addPartialVariant((variant) => {
|
|
803
877
|
variant.bandwidth = 200;
|
|
878
|
+
variant.primary = true;
|
|
804
879
|
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
805
880
|
stream.size(960, 540);
|
|
806
881
|
});
|
|
@@ -810,6 +885,7 @@ describe('HlsParser', () => {
|
|
|
810
885
|
});
|
|
811
886
|
manifest.addPartialVariant((variant) => {
|
|
812
887
|
variant.bandwidth = 300;
|
|
888
|
+
variant.primary = false;
|
|
813
889
|
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
814
890
|
stream.size(960, 540);
|
|
815
891
|
});
|
|
@@ -1307,6 +1383,8 @@ describe('HlsParser', () => {
|
|
|
1307
1383
|
// stream.
|
|
1308
1384
|
const timeline = actual.presentationTimeline;
|
|
1309
1385
|
expect(timeline.getDuration()).toBe(10);
|
|
1386
|
+
expect(timeline.getSeekRangeStart()).toBe(0);
|
|
1387
|
+
expect(timeline.getSeekRangeEnd()).toBe(10);
|
|
1310
1388
|
|
|
1311
1389
|
expect(actual.textStreams.length).toBe(1);
|
|
1312
1390
|
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
|
+
});
|
|
@@ -162,7 +162,7 @@ describe('DrmEngine', () => {
|
|
|
162
162
|
document.body.removeChild(video);
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
function
|
|
165
|
+
function checkTrueDrmSupport() {
|
|
166
166
|
if (shaka.util.Platform.isXboxOne()) {
|
|
167
167
|
// Axinom won't issue a license for an Xbox One. The error message from
|
|
168
168
|
// the license server says "Your DRM client's security level is 150, but
|
|
@@ -174,7 +174,11 @@ describe('DrmEngine', () => {
|
|
|
174
174
|
return support['com.widevine.alpha'] || support['com.microsoft.playready'];
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
function checkClearKeySupport() {
|
|
178
|
+
return support['org.w3.clearkey'];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
filterDescribe('basic flow', checkTrueDrmSupport, () => {
|
|
178
182
|
drmIt('gets a license and can play encrypted segments', async () => {
|
|
179
183
|
// The error callback should not be invoked.
|
|
180
184
|
onErrorSpy.and.callFake(fail);
|
|
@@ -274,4 +278,84 @@ describe('DrmEngine', () => {
|
|
|
274
278
|
expect(video.currentTime).toBeGreaterThan(0);
|
|
275
279
|
});
|
|
276
280
|
}); // describe('basic flow')
|
|
281
|
+
|
|
282
|
+
filterDescribe('ClearKey', checkClearKeySupport, () => {
|
|
283
|
+
drmIt('plays encrypted content with the ClearKey CDM', async () => {
|
|
284
|
+
// Configure DrmEngine for ClearKey playback.
|
|
285
|
+
const config = shaka.util.PlayerConfiguration.createDefault().drm;
|
|
286
|
+
config.clearKeys = {
|
|
287
|
+
// From https://github.com/Axinom/public-test-vectors/tree/conservative#v61-multidrm
|
|
288
|
+
'6e5a1d26275747d78046eaa5d1d34b5a': '197f26f572c864d2338b3ae5d114ea9c',
|
|
289
|
+
};
|
|
290
|
+
drmEngine.configure(config);
|
|
291
|
+
|
|
292
|
+
// The error callback should not be invoked.
|
|
293
|
+
onErrorSpy.and.callFake(fail);
|
|
294
|
+
|
|
295
|
+
/** @type {!shaka.util.PublicPromise} */
|
|
296
|
+
const encryptedEventSeen = new shaka.util.PublicPromise();
|
|
297
|
+
eventManager.listen(video, 'encrypted', () => {
|
|
298
|
+
encryptedEventSeen.resolve();
|
|
299
|
+
});
|
|
300
|
+
eventManager.listen(video, 'error', () => {
|
|
301
|
+
fail('MediaError code ' + video.error.code);
|
|
302
|
+
let extended = video.error.msExtendedCode;
|
|
303
|
+
if (extended) {
|
|
304
|
+
if (extended < 0) {
|
|
305
|
+
extended += Math.pow(2, 32);
|
|
306
|
+
}
|
|
307
|
+
fail('MediaError msExtendedCode ' + extended.toString(16));
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
/** @type {!shaka.util.PublicPromise} */
|
|
312
|
+
const keyStatusEventSeen = new shaka.util.PublicPromise();
|
|
313
|
+
onKeyStatusSpy.and.callFake(() => {
|
|
314
|
+
keyStatusEventSeen.resolve();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const variants = manifest.variants;
|
|
318
|
+
|
|
319
|
+
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
|
|
320
|
+
await drmEngine.attach(video);
|
|
321
|
+
await mediaSourceEngine.appendBuffer(
|
|
322
|
+
ContentType.VIDEO, videoInitSegment, null, null,
|
|
323
|
+
/* hasClosedCaptions= */ false);
|
|
324
|
+
await mediaSourceEngine.appendBuffer(
|
|
325
|
+
ContentType.AUDIO, audioInitSegment, null, null,
|
|
326
|
+
/* hasClosedCaptions= */ false);
|
|
327
|
+
await encryptedEventSeen;
|
|
328
|
+
|
|
329
|
+
// Some platforms (notably 2017 Tizen TVs) do not fire key status
|
|
330
|
+
// events.
|
|
331
|
+
const keyStatusTimeout = shaka.test.Util.delay(5);
|
|
332
|
+
await Promise.race([keyStatusTimeout, keyStatusEventSeen]);
|
|
333
|
+
|
|
334
|
+
const call = onKeyStatusSpy.calls.mostRecent();
|
|
335
|
+
if (call) {
|
|
336
|
+
const map = /** @type {!Object} */ (call.args[0]);
|
|
337
|
+
expect(Object.keys(map).length).not.toBe(0);
|
|
338
|
+
for (const k in map) {
|
|
339
|
+
expect(map[k]).toBe('usable');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
await mediaSourceEngine.appendBuffer(
|
|
344
|
+
ContentType.VIDEO, videoSegment, 0, 10,
|
|
345
|
+
/* hasClosedCaptions= */ false);
|
|
346
|
+
await mediaSourceEngine.appendBuffer(
|
|
347
|
+
ContentType.AUDIO, audioSegment, 0, 10,
|
|
348
|
+
/* hasClosedCaptions= */ false);
|
|
349
|
+
|
|
350
|
+
expect(video.buffered.end(0)).toBeGreaterThan(0);
|
|
351
|
+
video.play();
|
|
352
|
+
|
|
353
|
+
const waiter = new shaka.test.Waiter(eventManager).timeoutAfter(15);
|
|
354
|
+
await waiter.waitForMovement(video);
|
|
355
|
+
|
|
356
|
+
// Something should have played by now.
|
|
357
|
+
expect(video.readyState).toBeGreaterThan(1);
|
|
358
|
+
expect(video.currentTime).toBeGreaterThan(0);
|
|
359
|
+
});
|
|
360
|
+
}); // describe('ClearKey')
|
|
277
361
|
});
|
|
@@ -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', () => {
|
|
@@ -2285,6 +2329,19 @@ describe('DrmEngine', () => {
|
|
|
2285
2329
|
await expectAsync(drmEngine.removeSession('abc'))
|
|
2286
2330
|
.toBeRejectedWith(expected);
|
|
2287
2331
|
});
|
|
2332
|
+
|
|
2333
|
+
// Regression test for #3534
|
|
2334
|
+
it('does not remove the same session again on destroy', async () => {
|
|
2335
|
+
updatePromise.resolve();
|
|
2336
|
+
expect(session1.remove).not.toHaveBeenCalled();
|
|
2337
|
+
await drmEngine.removeSession('abc');
|
|
2338
|
+
expect(session1.remove).toHaveBeenCalled();
|
|
2339
|
+
session1.remove.calls.reset();
|
|
2340
|
+
await drmEngine.destroy();
|
|
2341
|
+
// The session should only be removed ONCE. If it's double-removed, it
|
|
2342
|
+
// will make a (non-fatal) DOMException.
|
|
2343
|
+
expect(session1.remove).not.toHaveBeenCalled();
|
|
2344
|
+
});
|
|
2288
2345
|
});
|
|
2289
2346
|
|
|
2290
2347
|
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
|