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
|
@@ -15,6 +15,9 @@ describe('RegionObserver', () => {
|
|
|
15
15
|
/** @type {!shaka.media.RegionObserver} */
|
|
16
16
|
let observer;
|
|
17
17
|
|
|
18
|
+
/** @type {!jasmine.Spy} */
|
|
19
|
+
let onSeekRange;
|
|
20
|
+
|
|
18
21
|
/** @type {!jasmine.Spy} */
|
|
19
22
|
let onEnterRegion;
|
|
20
23
|
/** @type {!jasmine.Spy} */
|
|
@@ -23,18 +26,26 @@ describe('RegionObserver', () => {
|
|
|
23
26
|
let onSkipRegion;
|
|
24
27
|
|
|
25
28
|
beforeEach(() => {
|
|
29
|
+
onSeekRange = jasmine.createSpy('onSeekRange');
|
|
30
|
+
onSeekRange.and.returnValue({start: 0, end: 100});
|
|
31
|
+
|
|
26
32
|
onEnterRegion = jasmine.createSpy('onEnterRegion');
|
|
27
33
|
onExitRegion = jasmine.createSpy('onExitRegion');
|
|
28
34
|
onSkipRegion = jasmine.createSpy('onSkipRegion');
|
|
29
35
|
|
|
30
36
|
timeline = new shaka.media.RegionTimeline(
|
|
31
|
-
()
|
|
37
|
+
shaka.test.Util.spyFunc(onSeekRange));
|
|
32
38
|
|
|
33
39
|
observer = new shaka.media.RegionObserver(timeline);
|
|
34
|
-
observer.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
observer.addEventListener('enter', (event) => {
|
|
41
|
+
shaka.test.Util.spyFunc(onEnterRegion)(event['region'], event['seeking']);
|
|
42
|
+
});
|
|
43
|
+
observer.addEventListener('exit', (event) => {
|
|
44
|
+
shaka.test.Util.spyFunc(onExitRegion)(event['region'], event['seeking']);
|
|
45
|
+
});
|
|
46
|
+
observer.addEventListener('skip', (event) => {
|
|
47
|
+
shaka.test.Util.spyFunc(onSkipRegion)(event['region'], event['seeking']);
|
|
48
|
+
});
|
|
38
49
|
});
|
|
39
50
|
|
|
40
51
|
it('fires enter event when adding a region the playhead is in', () => {
|
|
@@ -313,6 +324,30 @@ describe('RegionObserver', () => {
|
|
|
313
324
|
expect(onSkipRegion).not.toHaveBeenCalled();
|
|
314
325
|
});
|
|
315
326
|
|
|
327
|
+
// See https://github.com/google/shaka-player/issues/3949
|
|
328
|
+
it('cleans up references to regions', async () => {
|
|
329
|
+
const region = createRegion('my-region', 0, 10);
|
|
330
|
+
timeline.addRegion(region);
|
|
331
|
+
|
|
332
|
+
// Poll so that RegionObserver will store regions.
|
|
333
|
+
poll(observer,
|
|
334
|
+
/* timeInSeconds= */ 5,
|
|
335
|
+
/* seeking= */ false);
|
|
336
|
+
|
|
337
|
+
// Hack to get a private member without the compiler complaining:
|
|
338
|
+
/** @type {Map} */
|
|
339
|
+
const regionMap = (/** @type {?} */(observer))['oldPosition_'];
|
|
340
|
+
expect(regionMap.size).not.toBe(0);
|
|
341
|
+
|
|
342
|
+
// Move the seek range so that the region will be removed.
|
|
343
|
+
onSeekRange.and.returnValue({start: 20, end: 100});
|
|
344
|
+
// Give the timeline time to filter regions
|
|
345
|
+
await shaka.test.Util.delay(
|
|
346
|
+
shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
|
|
347
|
+
|
|
348
|
+
expect(regionMap.size).toBe(0);
|
|
349
|
+
});
|
|
350
|
+
|
|
316
351
|
/**
|
|
317
352
|
* @param {string} id
|
|
318
353
|
* @param {number} startTimeSeconds
|
|
@@ -14,18 +14,27 @@ describe('RegionTimeline', () => {
|
|
|
14
14
|
/** @type {!jasmine.Spy} */
|
|
15
15
|
let onNewRegion;
|
|
16
16
|
|
|
17
|
+
/** @type {!jasmine.Spy} */
|
|
18
|
+
let onRemoveRegion;
|
|
19
|
+
|
|
17
20
|
/** @type {!jasmine.Spy} */
|
|
18
21
|
let onSeekRange;
|
|
19
22
|
|
|
20
23
|
beforeEach(() => {
|
|
21
|
-
onNewRegion = jasmine.createSpy('onNewRegion');
|
|
22
|
-
|
|
23
24
|
onSeekRange = jasmine.createSpy('onSeekRange');
|
|
24
25
|
onSeekRange.and.returnValue({start: 0, end: 100});
|
|
25
|
-
|
|
26
26
|
timeline = new shaka.media.RegionTimeline(
|
|
27
27
|
shaka.test.Util.spyFunc(onSeekRange));
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
onNewRegion = jasmine.createSpy('onNewRegion');
|
|
30
|
+
timeline.addEventListener('regionadd', (event) => {
|
|
31
|
+
shaka.test.Util.spyFunc(onNewRegion)(event['region']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
onRemoveRegion = jasmine.createSpy('onRemoveRegion');
|
|
35
|
+
timeline.addEventListener('regionremove', (event) => {
|
|
36
|
+
shaka.test.Util.spyFunc(onRemoveRegion)(event['region']);
|
|
37
|
+
});
|
|
29
38
|
});
|
|
30
39
|
|
|
31
40
|
afterEach(() => {
|
|
@@ -85,24 +94,33 @@ describe('RegionTimeline', () => {
|
|
|
85
94
|
]);
|
|
86
95
|
});
|
|
87
96
|
|
|
88
|
-
it('filters
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
it('filters regions that end before the seek range', async () => {
|
|
98
|
+
onSeekRange.and.returnValue({start: 5, end: 100});
|
|
99
|
+
|
|
100
|
+
const region1 = createRegion('urn:foo', 'my-region', 0, 3);
|
|
101
|
+
const region2 = createRegion('urn:foo', 'my-region', 3, 10);
|
|
102
|
+
const region3 = createRegion('urn:foo', 'my-region', 5, 10);
|
|
103
|
+
|
|
104
|
+
timeline.addRegion(region1);
|
|
105
|
+
timeline.addRegion(region2);
|
|
106
|
+
timeline.addRegion(region3);
|
|
107
|
+
expect(onNewRegion).toHaveBeenCalledTimes(3);
|
|
108
|
+
expect(onNewRegion).toHaveBeenCalledWith(region1);
|
|
109
|
+
expect(onNewRegion).toHaveBeenCalledWith(region2);
|
|
110
|
+
expect(onNewRegion).toHaveBeenCalledWith(region3);
|
|
111
|
+
|
|
112
|
+
let regions = Array.from(timeline.regions());
|
|
113
|
+
expect(regions.length).toBe(3);
|
|
114
|
+
|
|
115
|
+
// Give the timeline time to filter regions
|
|
116
|
+
await shaka.test.Util.delay(
|
|
117
|
+
shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
|
|
118
|
+
|
|
119
|
+
regions = Array.from(timeline.regions());
|
|
120
|
+
expect(regions).toEqual([region2, region3]);
|
|
121
|
+
|
|
122
|
+
expect(onRemoveRegion).toHaveBeenCalledWith(region1);
|
|
123
|
+
});
|
|
106
124
|
|
|
107
125
|
/**
|
|
108
126
|
* @param {string} schemeIdUri
|
|
@@ -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(
|
|
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);
|
|
@@ -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);
|
|
@@ -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.
|
|
@@ -652,6 +652,44 @@ describe('Player', () => {
|
|
|
652
652
|
});
|
|
653
653
|
});
|
|
654
654
|
|
|
655
|
+
describe('seek range', () => {
|
|
656
|
+
// Regression test for Issue #3675.
|
|
657
|
+
it('has a reasonable seek range after going from live to VOD', async () => {
|
|
658
|
+
const netEngine = player.getNetworkingEngine();
|
|
659
|
+
const startTime = Date.now();
|
|
660
|
+
netEngine.registerRequestFilter((type, request) => {
|
|
661
|
+
if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
// Simulate a live stream by providing different manifests over time.
|
|
665
|
+
const time = (Date.now() - startTime) / 1000;
|
|
666
|
+
const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
|
|
667
|
+
request.uris = [
|
|
668
|
+
'/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
|
|
669
|
+
];
|
|
670
|
+
console.log('getting manifest', request.uris);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Play the stream.
|
|
674
|
+
await player.load('/base/test/test/assets/3675/dash_0.mpd');
|
|
675
|
+
video.play();
|
|
676
|
+
|
|
677
|
+
// Wait for the stream to be over.
|
|
678
|
+
eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
|
|
679
|
+
/** @type {shaka.test.Waiter} */
|
|
680
|
+
const waiter = new shaka.test.Waiter(eventManager)
|
|
681
|
+
.timeoutAfter(20).failOnTimeout(true);
|
|
682
|
+
await waiter.waitForEnd(video);
|
|
683
|
+
|
|
684
|
+
// The stream should have transitioned to VOD by now.
|
|
685
|
+
expect(player.isLive()).toBe(false);
|
|
686
|
+
|
|
687
|
+
// Check that the final seek range is as expected.
|
|
688
|
+
const seekRange = player.seekRange();
|
|
689
|
+
expect(seekRange.end).toBe(14);
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
|
|
655
693
|
describe('buffering', () => {
|
|
656
694
|
const startBuffering = jasmine.objectContaining({buffering: true});
|
|
657
695
|
const endBuffering = jasmine.objectContaining({buffering: false});
|
|
@@ -926,9 +964,9 @@ describe('Player', () => {
|
|
|
926
964
|
it('add external chapters in vtt format', async () => {
|
|
927
965
|
await player.load('test:sintel_no_text_compiled');
|
|
928
966
|
const locationUri = new goog.Uri(location.href);
|
|
929
|
-
const
|
|
930
|
-
const
|
|
931
|
-
await player.addChaptersTrack(
|
|
967
|
+
const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
|
|
968
|
+
const absoluteUri1 = locationUri.resolve(partialUri1);
|
|
969
|
+
await player.addChaptersTrack(absoluteUri1.toString(), 'en');
|
|
932
970
|
|
|
933
971
|
await shaka.test.Util.delay(1.5);
|
|
934
972
|
|
|
@@ -941,11 +979,44 @@ describe('Player', () => {
|
|
|
941
979
|
const chapter2 = chapters[1];
|
|
942
980
|
expect(chapter2.title).toBe('Chapter 2');
|
|
943
981
|
expect(chapter2.startTime).toBe(5);
|
|
944
|
-
expect(chapter2.endTime).toBe(
|
|
982
|
+
expect(chapter2.endTime).toBe(10);
|
|
945
983
|
const chapter3 = chapters[2];
|
|
946
984
|
expect(chapter3.title).toBe('Chapter 3');
|
|
947
|
-
expect(chapter3.startTime).toBe(
|
|
948
|
-
expect(chapter3.endTime).toBe(
|
|
985
|
+
expect(chapter3.startTime).toBe(10);
|
|
986
|
+
expect(chapter3.endTime).toBe(20);
|
|
987
|
+
|
|
988
|
+
const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
|
|
989
|
+
const absoluteUri2 = locationUri.resolve(partialUri2);
|
|
990
|
+
await player.addChaptersTrack(absoluteUri2.toString(), 'en');
|
|
991
|
+
|
|
992
|
+
await shaka.test.Util.delay(1.5);
|
|
993
|
+
|
|
994
|
+
const chaptersUpdated = player.getChapters('en');
|
|
995
|
+
expect(chaptersUpdated.length).toBe(6);
|
|
996
|
+
const chapterUpdated1 = chaptersUpdated[0];
|
|
997
|
+
expect(chapterUpdated1.title).toBe('Chapter 1');
|
|
998
|
+
expect(chapterUpdated1.startTime).toBe(0);
|
|
999
|
+
expect(chapterUpdated1.endTime).toBe(5);
|
|
1000
|
+
const chapterUpdated2 = chaptersUpdated[1];
|
|
1001
|
+
expect(chapterUpdated2.title).toBe('Chapter 2');
|
|
1002
|
+
expect(chapterUpdated2.startTime).toBe(5);
|
|
1003
|
+
expect(chapterUpdated2.endTime).toBe(10);
|
|
1004
|
+
const chapterUpdated3 = chaptersUpdated[2];
|
|
1005
|
+
expect(chapterUpdated3.title).toBe('Chapter 3');
|
|
1006
|
+
expect(chapterUpdated3.startTime).toBe(10);
|
|
1007
|
+
expect(chapterUpdated3.endTime).toBe(20);
|
|
1008
|
+
const chapterUpdated4 = chaptersUpdated[3];
|
|
1009
|
+
expect(chapterUpdated4.title).toBe('Chapter 4');
|
|
1010
|
+
expect(chapterUpdated4.startTime).toBe(20);
|
|
1011
|
+
expect(chapterUpdated4.endTime).toBe(30);
|
|
1012
|
+
const chapterUpdated5 = chaptersUpdated[4];
|
|
1013
|
+
expect(chapterUpdated5.title).toBe('Chapter 5');
|
|
1014
|
+
expect(chapterUpdated5.startTime).toBe(30);
|
|
1015
|
+
expect(chapterUpdated5.endTime).toBe(40);
|
|
1016
|
+
const chapterUpdated6 = chaptersUpdated[5];
|
|
1017
|
+
expect(chapterUpdated6.title).toBe('Chapter 6');
|
|
1018
|
+
expect(chapterUpdated6.startTime).toBe(40);
|
|
1019
|
+
expect(chapterUpdated6.endTime).toBe(61.349);
|
|
949
1020
|
});
|
|
950
1021
|
|
|
951
1022
|
it('add external chapters in srt format', async () => {
|
|
@@ -953,11 +1024,11 @@ describe('Player', () => {
|
|
|
953
1024
|
const locationUri = new goog.Uri(location.href);
|
|
954
1025
|
const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
|
|
955
1026
|
const absoluteUri = locationUri.resolve(partialUri);
|
|
956
|
-
await player.addChaptersTrack(absoluteUri.toString(), '
|
|
1027
|
+
await player.addChaptersTrack(absoluteUri.toString(), 'es');
|
|
957
1028
|
|
|
958
1029
|
await shaka.test.Util.delay(1.5);
|
|
959
1030
|
|
|
960
|
-
const chapters = player.getChapters('
|
|
1031
|
+
const chapters = player.getChapters('es');
|
|
961
1032
|
expect(chapters.length).toBe(3);
|
|
962
1033
|
const chapter1 = chapters[0];
|
|
963
1034
|
expect(chapter1.title).toBe('Chapter 1');
|
|
@@ -330,9 +330,9 @@ describe('Player Src Equals', () => {
|
|
|
330
330
|
await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
|
|
331
331
|
|
|
332
332
|
const locationUri = new goog.Uri(location.href);
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
await player.addChaptersTrack(
|
|
333
|
+
const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
|
|
334
|
+
const absoluteUri1 = locationUri.resolve(partialUri1);
|
|
335
|
+
await player.addChaptersTrack(absoluteUri1.toString(), 'en');
|
|
336
336
|
|
|
337
337
|
await shaka.test.Util.delay(1.5);
|
|
338
338
|
|
|
@@ -345,11 +345,44 @@ describe('Player Src Equals', () => {
|
|
|
345
345
|
const chapter2 = chapters[1];
|
|
346
346
|
expect(chapter2.title).toBe('Chapter 2');
|
|
347
347
|
expect(chapter2.startTime).toBe(5);
|
|
348
|
-
expect(chapter2.endTime).toBe(
|
|
348
|
+
expect(chapter2.endTime).toBe(10);
|
|
349
349
|
const chapter3 = chapters[2];
|
|
350
350
|
expect(chapter3.title).toBe('Chapter 3');
|
|
351
|
-
expect(chapter3.startTime).toBe(
|
|
352
|
-
expect(chapter3.endTime).toBe(
|
|
351
|
+
expect(chapter3.startTime).toBe(10);
|
|
352
|
+
expect(chapter3.endTime).toBe(20);
|
|
353
|
+
|
|
354
|
+
const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
|
|
355
|
+
const absoluteUri2 = locationUri.resolve(partialUri2);
|
|
356
|
+
await player.addChaptersTrack(absoluteUri2.toString(), 'en');
|
|
357
|
+
|
|
358
|
+
await shaka.test.Util.delay(1.5);
|
|
359
|
+
|
|
360
|
+
const chaptersUpdated = player.getChapters('en');
|
|
361
|
+
expect(chaptersUpdated.length).toBe(6);
|
|
362
|
+
const chapterUpdated1 = chaptersUpdated[0];
|
|
363
|
+
expect(chapterUpdated1.title).toBe('Chapter 1');
|
|
364
|
+
expect(chapterUpdated1.startTime).toBe(0);
|
|
365
|
+
expect(chapterUpdated1.endTime).toBe(5);
|
|
366
|
+
const chapterUpdated2 = chaptersUpdated[1];
|
|
367
|
+
expect(chapterUpdated2.title).toBe('Chapter 2');
|
|
368
|
+
expect(chapterUpdated2.startTime).toBe(5);
|
|
369
|
+
expect(chapterUpdated2.endTime).toBe(10);
|
|
370
|
+
const chapterUpdated3 = chaptersUpdated[2];
|
|
371
|
+
expect(chapterUpdated3.title).toBe('Chapter 3');
|
|
372
|
+
expect(chapterUpdated3.startTime).toBe(10);
|
|
373
|
+
expect(chapterUpdated3.endTime).toBe(20);
|
|
374
|
+
const chapterUpdated4 = chaptersUpdated[3];
|
|
375
|
+
expect(chapterUpdated4.title).toBe('Chapter 4');
|
|
376
|
+
expect(chapterUpdated4.startTime).toBe(20);
|
|
377
|
+
expect(chapterUpdated4.endTime).toBe(30);
|
|
378
|
+
const chapterUpdated5 = chaptersUpdated[4];
|
|
379
|
+
expect(chapterUpdated5.title).toBe('Chapter 5');
|
|
380
|
+
expect(chapterUpdated5.startTime).toBe(30);
|
|
381
|
+
expect(chapterUpdated5.endTime).toBe(40);
|
|
382
|
+
const chapterUpdated6 = chaptersUpdated[5];
|
|
383
|
+
expect(chapterUpdated6.title).toBe('Chapter 6');
|
|
384
|
+
expect(chapterUpdated6.startTime).toBe(40);
|
|
385
|
+
expect(chapterUpdated6.endTime).toBe(61.349);
|
|
353
386
|
});
|
|
354
387
|
|
|
355
388
|
it('add external chapters in srt format', async () => {
|
|
@@ -358,11 +391,11 @@ describe('Player Src Equals', () => {
|
|
|
358
391
|
const locationUri = new goog.Uri(location.href);
|
|
359
392
|
const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
|
|
360
393
|
const absoluteUri = locationUri.resolve(partialUri);
|
|
361
|
-
await player.addChaptersTrack(absoluteUri.toString(), '
|
|
394
|
+
await player.addChaptersTrack(absoluteUri.toString(), 'es');
|
|
362
395
|
|
|
363
396
|
await shaka.test.Util.delay(1.5);
|
|
364
397
|
|
|
365
|
-
const chapters = player.getChapters('
|
|
398
|
+
const chapters = player.getChapters('es');
|
|
366
399
|
expect(chapters.length).toBe(3);
|
|
367
400
|
const chapter1 = chapters[0];
|
|
368
401
|
expect(chapter1.title).toBe('Chapter 1');
|
package/test/player_unit.js
CHANGED
|
@@ -1092,8 +1092,8 @@ describe('Player', () => {
|
|
|
1092
1092
|
// Image tracks
|
|
1093
1093
|
manifest.addImageStream(53, (stream) => {
|
|
1094
1094
|
stream.originalId = 'thumbnail';
|
|
1095
|
-
stream.width =
|
|
1096
|
-
stream.height =
|
|
1095
|
+
stream.width = 200;
|
|
1096
|
+
stream.height = 400;
|
|
1097
1097
|
stream.bandwidth = 10;
|
|
1098
1098
|
stream.mimeType = 'image/jpeg';
|
|
1099
1099
|
stream.tilesLayout = '1x1';
|
|
@@ -1503,8 +1503,8 @@ describe('Player', () => {
|
|
|
1503
1503
|
audioBandwidth: null,
|
|
1504
1504
|
videoBandwidth: null,
|
|
1505
1505
|
bandwidth: 10,
|
|
1506
|
-
width:
|
|
1507
|
-
height:
|
|
1506
|
+
width: 200,
|
|
1507
|
+
height: 400,
|
|
1508
1508
|
frameRate: null,
|
|
1509
1509
|
pixelAspectRatio: null,
|
|
1510
1510
|
hdr: null,
|
|
@@ -3537,29 +3537,39 @@ describe('Player', () => {
|
|
|
3537
3537
|
|
|
3538
3538
|
await player.load(fakeManifestUri, 0, fakeMimeType);
|
|
3539
3539
|
|
|
3540
|
+
expect(player.getImageTracks()[0].width).toBe(100);
|
|
3541
|
+
expect(player.getImageTracks()[0].height).toBe(50);
|
|
3540
3542
|
const thumbnail0 = await player.getThumbnails(5, 0);
|
|
3541
3543
|
const thumbnail1 = await player.getThumbnails(5, 11);
|
|
3542
3544
|
const thumbnail2 = await player.getThumbnails(5, 21);
|
|
3543
3545
|
const thumbnail5 = await player.getThumbnails(5, 51);
|
|
3544
3546
|
expect(thumbnail0).toEqual(jasmine.objectContaining({
|
|
3547
|
+
imageHeight: 150,
|
|
3548
|
+
imageWidth: 200,
|
|
3545
3549
|
positionX: 0,
|
|
3546
3550
|
positionY: 0,
|
|
3547
3551
|
width: 100,
|
|
3548
3552
|
height: 50,
|
|
3549
3553
|
}));
|
|
3550
3554
|
expect(thumbnail1).toEqual(jasmine.objectContaining({
|
|
3555
|
+
imageHeight: 150,
|
|
3556
|
+
imageWidth: 200,
|
|
3551
3557
|
positionX: 100,
|
|
3552
3558
|
positionY: 0,
|
|
3553
3559
|
width: 100,
|
|
3554
3560
|
height: 50,
|
|
3555
3561
|
}));
|
|
3556
3562
|
expect(thumbnail2).toEqual(jasmine.objectContaining({
|
|
3563
|
+
imageHeight: 150,
|
|
3564
|
+
imageWidth: 200,
|
|
3557
3565
|
positionX: 0,
|
|
3558
3566
|
positionY: 50,
|
|
3559
3567
|
width: 100,
|
|
3560
3568
|
height: 50,
|
|
3561
3569
|
}));
|
|
3562
3570
|
expect(thumbnail5).toEqual(jasmine.objectContaining({
|
|
3571
|
+
imageHeight: 150,
|
|
3572
|
+
imageWidth: 200,
|
|
3563
3573
|
positionX: 100,
|
|
3564
3574
|
positionY: 100,
|
|
3565
3575
|
width: 100,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
3
|
+
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
|
4
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
5
|
+
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
|
6
|
+
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
|
7
|
+
type="dynamic"
|
|
8
|
+
minimumUpdatePeriod="PT2S"
|
|
9
|
+
suggestedPresentationDelay="PT2S"
|
|
10
|
+
availabilityStartTime="2021-10-28T07:36:27.755Z"
|
|
11
|
+
publishTime="2021-10-28T07:36:29.744Z"
|
|
12
|
+
timeShiftBufferDepth="PT10.0S"
|
|
13
|
+
maxSegmentDuration="PT2.0S"
|
|
14
|
+
minBufferTime="PT4.0S">
|
|
15
|
+
<ProgramInformation>
|
|
16
|
+
</ProgramInformation>
|
|
17
|
+
<ServiceDescription id="0">
|
|
18
|
+
</ServiceDescription>
|
|
19
|
+
<Period id="0" start="PT0.0S">
|
|
20
|
+
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="30/1" maxWidth="1280" maxHeight="720" par="16:9">
|
|
21
|
+
<Representation id="0" mimeType="video/mp4" codecs="avc1.64001f" bandwidth="122012" width="1280" height="720" sar="1:1">
|
|
22
|
+
<SegmentTemplate timescale="15360" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
|
23
|
+
<SegmentTimeline>
|
|
24
|
+
<S t="0" d="30720" />
|
|
25
|
+
</SegmentTimeline>
|
|
26
|
+
</SegmentTemplate>
|
|
27
|
+
</Representation>
|
|
28
|
+
</AdaptationSet>
|
|
29
|
+
</Period>
|
|
30
|
+
</MPD>
|