shaka-player 3.2.2 → 3.3.2
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/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +1 -1
- package/.github/workflows/build_and_test.yaml +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/AUTHORS +6 -5
- package/CHANGELOG.md +314 -1
- package/CONTRIBUTORS +4 -2
- package/build/checkversion.py +1 -1
- package/build/conformance.textproto +183 -169
- package/build/test.py +6 -0
- package/build/types/core +3 -0
- package/build/types/offline +1 -0
- package/build/types/polyfill +1 -0
- package/build/types/ui +2 -0
- package/demo/common/assets.js +18 -0
- package/demo/common/message_ids.js +13 -1
- package/demo/config.js +31 -1
- package/demo/index.html +1 -1
- package/demo/locales/en.json +12 -0
- package/demo/locales/source.json +48 -0
- package/dist/controls.css +26 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +19 -16
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +19 -16
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +21 -14
- package/dist/locales.js +31 -29
- package/dist/receiver.compiled.debug.js +11 -9
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +11 -9
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +232 -18
- package/dist/shaka-player.compiled.debug.d.ts +232 -18
- package/dist/shaka-player.compiled.debug.externs.js +293 -17
- package/dist/shaka-player.compiled.debug.js +481 -422
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +293 -17
- package/dist/shaka-player.compiled.js +846 -817
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +250 -30
- package/dist/shaka-player.ui.debug.d.ts +250 -30
- package/dist/shaka-player.ui.debug.externs.js +322 -28
- package/dist/shaka-player.ui.debug.js +630 -553
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +322 -28
- package/dist/shaka-player.ui.js +1048 -1006
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/bg-fetch-after.gv +29 -0
- package/docs/design/bg-fetch-after.gv.png +0 -0
- package/docs/design/bg-fetch-before.gv +18 -0
- package/docs/design/bg-fetch-before.gv.png +0 -0
- package/docs/design/bg-fetch.md +134 -0
- package/docs/tutorials/blob-url.md +15 -0
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/ui-customization.md +66 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/cmcd.js +186 -0
- package/externs/ima.js +3 -0
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/manifest.js +12 -1
- package/externs/shaka/manifest_parser.js +10 -1
- package/externs/shaka/net.js +20 -4
- package/externs/shaka/offline.js +31 -2
- package/externs/shaka/player.js +138 -5
- package/externs/shaka/text.js +24 -0
- package/externs/webcrypto.js +18 -0
- package/karma.conf.js +19 -15
- package/lib/abr/ewma.js +14 -0
- package/lib/abr/ewma_bandwidth_estimator.js +16 -2
- package/lib/abr/simple_abr_manager.js +6 -0
- package/lib/ads/ad_manager.js +1 -0
- package/lib/ads/client_side_ad.js +8 -0
- package/lib/ads/client_side_ad_manager.js +8 -4
- package/lib/ads/server_side_ad.js +7 -0
- package/lib/cast/cast_proxy.js +3 -0
- package/lib/cast/cast_receiver.js +3 -0
- package/lib/cast/cast_utils.js +1 -0
- package/lib/dash/dash_parser.js +79 -33
- package/lib/dash/segment_base.js +25 -1
- package/lib/dash/segment_list.js +18 -9
- package/lib/dash/segment_template.js +23 -10
- package/lib/hls/hls_parser.js +116 -23
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/quality_observer.js +274 -0
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/segment_index.js +8 -1
- package/lib/media/segment_reference.js +37 -4
- package/lib/media/streaming_engine.js +47 -6
- package/lib/net/http_fetch_plugin.js +31 -11
- package/lib/net/http_plugin_utils.js +1 -0
- package/lib/net/http_xhr_plugin.js +40 -14
- package/lib/net/networking_engine.js +85 -18
- package/lib/offline/download_info.js +68 -0
- package/lib/offline/download_manager.js +13 -5
- package/lib/offline/indexeddb/base_storage_cell.js +35 -0
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
- package/lib/offline/storage.js +401 -293
- package/lib/offline/stored_content_utils.js +2 -0
- package/lib/player.js +254 -27
- package/lib/polyfill/media_capabilities.js +30 -15
- package/lib/polyfill/random_uuid.js +52 -0
- package/lib/text/cue.js +18 -0
- package/lib/text/ttml_text_parser.js +109 -60
- package/lib/text/ui_text_displayer.js +19 -13
- package/lib/util/cmcd_manager.js +672 -0
- package/lib/util/error.js +6 -0
- package/lib/util/fake_event_target.js +21 -1
- package/lib/util/language_utils.js +12 -0
- package/lib/util/mutex.js +52 -0
- package/lib/util/periods.js +98 -64
- package/lib/util/player_configuration.js +18 -0
- package/lib/util/stream_utils.js +86 -10
- package/lib/util/xml_utils.js +19 -0
- package/package.json +3 -6
- package/shaka-player.uncompiled.js +3 -0
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_live_unit.js +144 -1
- package/test/dash/dash_parser_manifest_unit.js +37 -2
- package/test/dash/dash_parser_segment_base_unit.js +2 -0
- package/test/dash/dash_parser_segment_list_unit.js +2 -1
- package/test/dash/dash_parser_segment_template_unit.js +2 -1
- package/test/hls/hls_live_unit.js +2 -0
- package/test/hls/hls_parser_unit.js +76 -0
- package/test/media/drm_engine_unit.js +3 -3
- package/test/media/quality_observer_unit.js +142 -0
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/media/segment_reference_unit.js +16 -1
- package/test/media/streaming_engine_integration.js +4 -2
- package/test/media/streaming_engine_unit.js +147 -0
- package/test/net/http_plugin_unit.js +20 -15
- package/test/offline/storage_integration.js +108 -4
- package/test/player_integration.js +140 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +19 -9
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/util/dash_parser_util.js +4 -0
- package/test/test/util/fake_ad.js +7 -0
- package/test/test/util/fake_drm_engine.js +9 -2
- package/test/test/util/manifest_generator.js +3 -2
- package/test/test/util/test_scheme.js +14 -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 +544 -228
- package/test/ui/text_displayer_layout_unit.js +8 -7
- package/test/ui/ui_unit.js +250 -0
- package/test/util/cmcd_manager_unit.js +291 -0
- package/test/util/stream_utils_unit.js +36 -30
- package/third_party/closure-uri/uri.js +21 -0
- package/ui/airplay_button.js +1 -0
- package/ui/audio_language_selection.js +7 -0
- package/ui/cast_button.js +1 -0
- package/ui/context_menu.js +109 -0
- package/ui/controls.js +44 -7
- package/ui/controls.less +1 -0
- package/ui/enums.js +4 -0
- package/ui/externs/ui.js +28 -1
- package/ui/fast_forward_button.js +14 -5
- package/ui/fullscreen_button.js +1 -0
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +74 -0
- package/ui/less/overflow_menu.less +0 -10
- package/ui/less/tooltip.less +97 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +12 -36
- package/ui/loop_button.js +5 -0
- package/ui/mute_button.js +2 -1
- package/ui/overflow_menu.js +5 -25
- package/ui/pip_button.js +5 -0
- package/ui/play_button.js +2 -2
- package/ui/playback_rate_selection.js +8 -9
- package/ui/resolution_selection.js +7 -0
- package/ui/rewind_button.js +15 -6
- package/ui/seek_bar.js +4 -2
- package/ui/settings_menu.js +36 -4
- package/ui/small_play_button.js +1 -0
- package/ui/statistics_button.js +253 -0
- package/ui/text_selection.js +12 -4
- package/ui/ui.js +30 -0
- package/ui/volume_bar.js +1 -1
- package/externs/media_capabilities.js +0 -16
package/lib/util/xml_utils.js
CHANGED
|
@@ -103,6 +103,25 @@ shaka.util.XmlUtils = class {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Gets a namespace-qualified attribute.
|
|
108
|
+
* @param {!Element} elem The element to get from.
|
|
109
|
+
* @param {!Array.<string>} nsList The lis of namespace URIs.
|
|
110
|
+
* @param {string} name The local name of the attribute.
|
|
111
|
+
* @return {?string} The attribute's value, or null if not present.
|
|
112
|
+
*/
|
|
113
|
+
static getAttributeNSList(elem, nsList, name) {
|
|
114
|
+
// Some browsers return the empty string when the attribute is missing,
|
|
115
|
+
// so check if it exists first. See: https://mzl.la/2L7F0UK
|
|
116
|
+
for (const ns of nsList) {
|
|
117
|
+
if (elem.hasAttributeNS(ns, name)) {
|
|
118
|
+
return elem.getAttributeNS(ns, name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
106
125
|
/**
|
|
107
126
|
* Gets the text contents of a node.
|
|
108
127
|
* @param {!Node} elem The XML element.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shaka-player",
|
|
3
3
|
"description": "DASH/EME video player library",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.3.2",
|
|
5
5
|
"homepage": "https://github.com/google/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
|
|
15
14
|
"@teppeis/clutz": "^1.0.29-4c95e12.v20190929",
|
|
16
15
|
"awesomplete": "^1.1.4",
|
|
17
16
|
"babel-core": "^6.26.3",
|
|
@@ -36,15 +35,12 @@
|
|
|
36
35
|
"jsdoc": "github:joeyparrish/jsdoc#45c271fa",
|
|
37
36
|
"karma": "^6.2.0",
|
|
38
37
|
"karma-babel-preprocessor": "^7.0.0",
|
|
39
|
-
"karma-chrome-launcher": "^2.2.0",
|
|
40
38
|
"karma-coverage": "^1.1.2",
|
|
41
|
-
"karma-edge-launcher": "^0.4.2",
|
|
42
|
-
"karma-firefox-launcher": "^1.1.0",
|
|
43
39
|
"karma-ie-launcher": "^1.0.0",
|
|
44
40
|
"karma-jasmine": "^3.3.1",
|
|
45
41
|
"karma-jasmine-ajax": "^0.1.13",
|
|
42
|
+
"karma-local-wd-launcher": "^1.4.1",
|
|
46
43
|
"karma-opera-launcher": "^1.0.0",
|
|
47
|
-
"karma-safari-launcher": "^1.0.0",
|
|
48
44
|
"karma-sourcemap-loader": "^0.3.7",
|
|
49
45
|
"karma-spec-reporter": "0.0.32",
|
|
50
46
|
"karma-webdriver-launcher": "^1.0.8",
|
|
@@ -55,6 +51,7 @@
|
|
|
55
51
|
"pwacompat": "^2.0.10",
|
|
56
52
|
"rimraf": "^2.6.3",
|
|
57
53
|
"sprintf-js": "^1.1.2",
|
|
54
|
+
"ssim.js": "^3.5.0",
|
|
58
55
|
"stylelint": "^13.8.0",
|
|
59
56
|
"stylelint-config-standard": "^20.0.0",
|
|
60
57
|
"tippy.js": "^4.3.1",
|
|
@@ -44,6 +44,7 @@ goog.require('shaka.polyfill.PatchedMediaKeysMs');
|
|
|
44
44
|
goog.require('shaka.polyfill.PatchedMediaKeysNop');
|
|
45
45
|
goog.require('shaka.polyfill.PatchedMediaKeysWebkit');
|
|
46
46
|
goog.require('shaka.polyfill.PiPWebkit');
|
|
47
|
+
goog.require('shaka.polyfill.RandomUUID');
|
|
47
48
|
goog.require('shaka.polyfill.VTTCue');
|
|
48
49
|
goog.require('shaka.polyfill.VideoPlayPromise');
|
|
49
50
|
goog.require('shaka.polyfill.VideoPlaybackQuality');
|
|
@@ -70,6 +71,7 @@ goog.require('shaka.ui.AdPosition');
|
|
|
70
71
|
goog.require('shaka.ui.AirPlayButton');
|
|
71
72
|
goog.require('shaka.ui.BigPlayButton');
|
|
72
73
|
goog.require('shaka.ui.CastButton');
|
|
74
|
+
goog.require('shaka.ui.ContextMenu');
|
|
73
75
|
goog.require('shaka.ui.Element');
|
|
74
76
|
goog.require('shaka.ui.FastForwardButton');
|
|
75
77
|
goog.require('shaka.ui.FullscreenButton');
|
|
@@ -85,6 +87,7 @@ goog.require('shaka.ui.RewindButton');
|
|
|
85
87
|
goog.require('shaka.ui.SkipAdButton');
|
|
86
88
|
goog.require('shaka.ui.SmallPlayButton');
|
|
87
89
|
goog.require('shaka.ui.Spacer');
|
|
90
|
+
goog.require('shaka.ui.StatisticsButton');
|
|
88
91
|
goog.require('shaka.ui.TextSelection');
|
|
89
92
|
goog.require('shaka.ui.VolumeBar');
|
|
90
93
|
goog.require('shaka.util.Dom');
|
|
@@ -42,6 +42,8 @@ describe('DashParser ContentProtection', () => {
|
|
|
42
42
|
|
|
43
43
|
const playerInterface = {
|
|
44
44
|
networkingEngine: netEngine,
|
|
45
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
46
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
45
47
|
filter: (manifest) => Promise.resolve(),
|
|
46
48
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
47
49
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -39,6 +39,8 @@ describe('DashParser Live', () => {
|
|
|
39
39
|
parser.configure(shaka.util.PlayerConfiguration.createDefault().manifest);
|
|
40
40
|
playerInterface = {
|
|
41
41
|
networkingEngine: fakeNetEngine,
|
|
42
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
43
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
42
44
|
filter: (manifest) => Promise.resolve(),
|
|
43
45
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
44
46
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -364,7 +366,7 @@ describe('DashParser Live', () => {
|
|
|
364
366
|
});
|
|
365
367
|
}
|
|
366
368
|
|
|
367
|
-
it('can add Periods', async () => {
|
|
369
|
+
it('can add Periods with SegmentTemplate', async () => {
|
|
368
370
|
const template1 = [
|
|
369
371
|
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
370
372
|
' suggestedPresentationDelay="PT5S"',
|
|
@@ -440,6 +442,104 @@ describe('DashParser Live', () => {
|
|
|
440
442
|
// two segments (10-14s).
|
|
441
443
|
expect(stream.segmentIndex.find(9)).not.toBe(null);
|
|
442
444
|
expect(stream.segmentIndex.find(13)).not.toBe(null);
|
|
445
|
+
|
|
446
|
+
stream.closeSegmentIndex();
|
|
447
|
+
await stream.createSegmentIndex();
|
|
448
|
+
|
|
449
|
+
expect(stream.segmentIndex.find(9)).not.toBe(null);
|
|
450
|
+
expect(stream.segmentIndex.find(13)).not.toBe(null);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('can add Periods with SegmentList', async () => {
|
|
454
|
+
const list1 = [
|
|
455
|
+
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
456
|
+
' suggestedPresentationDelay="PT5S"',
|
|
457
|
+
' minimumUpdatePeriod="PT%(updateTime)dS">',
|
|
458
|
+
' <Period id="1">',
|
|
459
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
460
|
+
' <Representation id="1" bandwidth="500">',
|
|
461
|
+
' <BaseURL>http://example.com</BaseURL>',
|
|
462
|
+
' <SegmentList>',
|
|
463
|
+
' <SegmentURL media="s1.mp4" />',
|
|
464
|
+
' <SegmentURL media="s2.mp4" />',
|
|
465
|
+
' <SegmentURL media="s3.mp4" />',
|
|
466
|
+
' <SegmentTimeline>',
|
|
467
|
+
' <S d="10" t="0" r="2"/>',
|
|
468
|
+
' </SegmentTimeline>',
|
|
469
|
+
' </SegmentList>',
|
|
470
|
+
' </Representation>',
|
|
471
|
+
' </AdaptationSet>',
|
|
472
|
+
' </Period>',
|
|
473
|
+
'</MPD>',
|
|
474
|
+
].join('\n');
|
|
475
|
+
const list2 = [
|
|
476
|
+
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
477
|
+
' suggestedPresentationDelay="PT5S"',
|
|
478
|
+
' minimumUpdatePeriod="PT%(updateTime)dS">',
|
|
479
|
+
' <Period id="1" duration="PT40S">',
|
|
480
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
481
|
+
' <Representation id="1" bandwidth="500">',
|
|
482
|
+
' <BaseURL>http://example.com</BaseURL>',
|
|
483
|
+
' <SegmentList>',
|
|
484
|
+
' <SegmentURL media="s1.mp4" />',
|
|
485
|
+
' <SegmentURL media="s2.mp4" />',
|
|
486
|
+
' <SegmentURL media="s3.mp4" />',
|
|
487
|
+
' <SegmentURL media="s4.mp4" />',
|
|
488
|
+
' <SegmentTimeline>',
|
|
489
|
+
' <S d="10" t="0" r="3"/>',
|
|
490
|
+
' </SegmentTimeline>',
|
|
491
|
+
' </SegmentList>',
|
|
492
|
+
' </Representation>',
|
|
493
|
+
' </AdaptationSet>',
|
|
494
|
+
' </Period>',
|
|
495
|
+
' <Period id="2">',
|
|
496
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
497
|
+
' <Representation id="2" bandwidth="500">',
|
|
498
|
+
' <BaseURL>http://example.com</BaseURL>',
|
|
499
|
+
' <SegmentList>',
|
|
500
|
+
' <SegmentURL media="s1.mp4" />',
|
|
501
|
+
' <SegmentURL media="s2.mp4" />',
|
|
502
|
+
' <SegmentTimeline>',
|
|
503
|
+
' <S d="10" t="0" r="1"/>',
|
|
504
|
+
' </SegmentTimeline>',
|
|
505
|
+
' </SegmentList>',
|
|
506
|
+
' </Representation>',
|
|
507
|
+
' </AdaptationSet>',
|
|
508
|
+
' </Period>',
|
|
509
|
+
'</MPD>',
|
|
510
|
+
].join('\n');
|
|
511
|
+
const firstManifest = sprintf(list1, {updateTime: updateTime});
|
|
512
|
+
const secondManifest = sprintf(list2, {updateTime: updateTime});
|
|
513
|
+
|
|
514
|
+
fakeNetEngine.setResponseText('dummy://foo', firstManifest);
|
|
515
|
+
// First three segments should exist.
|
|
516
|
+
Date.now = () => 5;
|
|
517
|
+
|
|
518
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
519
|
+
const variant = manifest.variants[0];
|
|
520
|
+
const stream = variant.video;
|
|
521
|
+
await stream.createSegmentIndex();
|
|
522
|
+
|
|
523
|
+
// First three segments exist, but not the fourth.
|
|
524
|
+
expect(stream.segmentIndex.find(25)).not.toBe(null);
|
|
525
|
+
expect(stream.segmentIndex.find(45)).toBe(null);
|
|
526
|
+
|
|
527
|
+
fakeNetEngine.setResponseText('dummy://foo', secondManifest);
|
|
528
|
+
Date.now = () => 25;
|
|
529
|
+
|
|
530
|
+
await updateManifest();
|
|
531
|
+
|
|
532
|
+
// The update should have affected the same variant object we captured
|
|
533
|
+
// before. Now the entire first period should exist (0-40s), plus the next
|
|
534
|
+
// two segments of the second period(40-60s).
|
|
535
|
+
expect(stream.segmentIndex.find(25)).not.toBe(null);
|
|
536
|
+
expect(stream.segmentIndex.find(45)).not.toBe(null);
|
|
537
|
+
|
|
538
|
+
stream.closeSegmentIndex();
|
|
539
|
+
await stream.createSegmentIndex();
|
|
540
|
+
|
|
541
|
+
expect(stream.segmentIndex.find(25)).not.toBe(null);
|
|
542
|
+
expect(stream.segmentIndex.find(45)).not.toBe(null);
|
|
443
543
|
});
|
|
444
544
|
|
|
445
545
|
it('uses redirect URL for manifest BaseURL and updates', async () => {
|
|
@@ -1289,6 +1389,49 @@ describe('DashParser Live', () => {
|
|
|
1289
1389
|
.toHaveBeenCalledWith(
|
|
1290
1390
|
jasmine.objectContaining({id: '2', startTime: 10, endTime: 25}));
|
|
1291
1391
|
});
|
|
1392
|
+
|
|
1393
|
+
it('will not add timeline regions outside the DVR window', async () => {
|
|
1394
|
+
const manifest = [
|
|
1395
|
+
'<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S"',
|
|
1396
|
+
' availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
1397
|
+
' suggestedPresentationDelay="PT0S"',
|
|
1398
|
+
' timeShiftBufferDepth="PT30S">',
|
|
1399
|
+
' <Period id="1">',
|
|
1400
|
+
' <EventStream schemeIdUri="http://example.com" timescale="1">',
|
|
1401
|
+
' <Event id="0" presentationTime="0" duration="10"/>',
|
|
1402
|
+
' <Event id="1" presentationTime="10" duration="10"/>',
|
|
1403
|
+
' <Event id="2" presentationTime="20" duration="10"/>',
|
|
1404
|
+
' <Event id="3" presentationTime="30" duration="10"/>',
|
|
1405
|
+
' <Event id="4" presentationTime="40" duration="10"/>',
|
|
1406
|
+
' </EventStream>',
|
|
1407
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
1408
|
+
' <Representation bandwidth="1">',
|
|
1409
|
+
' <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
|
|
1410
|
+
' duration="2" />',
|
|
1411
|
+
' </Representation>',
|
|
1412
|
+
' </AdaptationSet>',
|
|
1413
|
+
' </Period>',
|
|
1414
|
+
'</MPD>',
|
|
1415
|
+
].join('\n');
|
|
1416
|
+
|
|
1417
|
+
// 45 seconds after the epoch, which is also 45 seconds after the
|
|
1418
|
+
// presentation started. Only the last 30 seconds are available, from
|
|
1419
|
+
// time 15 to 45.
|
|
1420
|
+
Date.now = () => 45 * 1000;
|
|
1421
|
+
|
|
1422
|
+
fakeNetEngine.setResponseText('dummy://foo', manifest);
|
|
1423
|
+
await parser.start('dummy://foo', playerInterface);
|
|
1424
|
+
|
|
1425
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(4);
|
|
1426
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1427
|
+
jasmine.objectContaining({id: '1'}));
|
|
1428
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1429
|
+
jasmine.objectContaining({id: '2'}));
|
|
1430
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1431
|
+
jasmine.objectContaining({id: '3'}));
|
|
1432
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1433
|
+
jasmine.objectContaining({id: '4'}));
|
|
1434
|
+
});
|
|
1292
1435
|
});
|
|
1293
1436
|
|
|
1294
1437
|
it('honors clockSyncUri for in-progress recordings', async () => {
|
|
@@ -57,6 +57,8 @@ describe('DashParser Manifest', () => {
|
|
|
57
57
|
onEventSpy = jasmine.createSpy('onEvent');
|
|
58
58
|
playerInterface = {
|
|
59
59
|
networkingEngine: fakeNetEngine,
|
|
60
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
61
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
60
62
|
filter: (manifest) => Promise.resolve(),
|
|
61
63
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
62
64
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -983,7 +985,8 @@ describe('DashParser Manifest', () => {
|
|
|
983
985
|
|
|
984
986
|
it('duplicate Representation ids with live', async () => {
|
|
985
987
|
const source = [
|
|
986
|
-
'<MPD minBufferTime="PT75S" type="dynamic"
|
|
988
|
+
'<MPD minBufferTime="PT75S" type="dynamic"',
|
|
989
|
+
' availabilityStartTime="1970-01-01T00:00:00Z">',
|
|
987
990
|
' <Period id="1" duration="PT30S">',
|
|
988
991
|
' <AdaptationSet mimeType="video/mp4">',
|
|
989
992
|
' <Representation id="1" bandwidth="1">',
|
|
@@ -1872,7 +1875,7 @@ describe('DashParser Manifest', () => {
|
|
|
1872
1875
|
expect(textStream.kind).toBe('caption');
|
|
1873
1876
|
});
|
|
1874
1877
|
|
|
1875
|
-
it('converts Roles element to "forced"', async () => {
|
|
1878
|
+
it('converts Roles element to "forced" (old role)', async () => {
|
|
1876
1879
|
const manifestText = [
|
|
1877
1880
|
'<MPD minBufferTime="PT75S">',
|
|
1878
1881
|
' <Period id="1" duration="PT30S">',
|
|
@@ -1904,6 +1907,38 @@ describe('DashParser Manifest', () => {
|
|
|
1904
1907
|
expect(textStream.forced).toBe(true);
|
|
1905
1908
|
});
|
|
1906
1909
|
|
|
1910
|
+
it('converts Roles element to "forced"', async () => {
|
|
1911
|
+
const manifestText = [
|
|
1912
|
+
'<MPD minBufferTime="PT75S">',
|
|
1913
|
+
' <Period id="1" duration="PT30S">',
|
|
1914
|
+
' <AdaptationSet id="1" contentType="text">',
|
|
1915
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1916
|
+
' value="captions" />',
|
|
1917
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1918
|
+
' value="forced-subtitle" />',
|
|
1919
|
+
' <Accessibility schemeIdUri="foobar" value="bar" />',
|
|
1920
|
+
' <Representation id="text-en" mimeType="text/webvtt">',
|
|
1921
|
+
' <BaseURL>t-en.vtt</BaseURL>',
|
|
1922
|
+
' </Representation>',
|
|
1923
|
+
' </AdaptationSet>',
|
|
1924
|
+
' <AdaptationSet id="1" mimeType="video/mp4">',
|
|
1925
|
+
' <Representation id="video-sd" width="640" height="480">',
|
|
1926
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
1927
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1928
|
+
' </Representation>',
|
|
1929
|
+
' </AdaptationSet>',
|
|
1930
|
+
' </Period>',
|
|
1931
|
+
'</MPD>',
|
|
1932
|
+
].join('\n');
|
|
1933
|
+
|
|
1934
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
1935
|
+
/** @type {shaka.extern.Manifest} */
|
|
1936
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1937
|
+
const textStream = manifest.textStreams[0];
|
|
1938
|
+
expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
|
|
1939
|
+
expect(textStream.forced).toBe(true);
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1907
1942
|
it('supports HDR signaling via profiles', async () => {
|
|
1908
1943
|
// (DASH-IF IOP v4.3 10.3.3.)
|
|
1909
1944
|
const hdrProfile =
|
|
@@ -35,6 +35,8 @@ describe('DashParser SegmentBase', () => {
|
|
|
35
35
|
|
|
36
36
|
playerInterface = {
|
|
37
37
|
networkingEngine: fakeNetEngine,
|
|
38
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
39
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
38
40
|
filter: (manifest) => Promise.resolve(),
|
|
39
41
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
40
42
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -344,6 +344,8 @@ describe('DashParser SegmentList', () => {
|
|
|
344
344
|
|
|
345
345
|
const playerInterface = {
|
|
346
346
|
networkingEngine: networkingEngine,
|
|
347
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
348
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
347
349
|
filter: () => {},
|
|
348
350
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
349
351
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -374,4 +376,3 @@ describe('DashParser SegmentList', () => {
|
|
|
374
376
|
expect(actual).toEqual(expected);
|
|
375
377
|
});
|
|
376
378
|
});
|
|
377
|
-
|
|
@@ -46,6 +46,8 @@ describe('DashParser SegmentTemplate', () => {
|
|
|
46
46
|
|
|
47
47
|
playerInterface = {
|
|
48
48
|
networkingEngine: fakeNetEngine,
|
|
49
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
50
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
49
51
|
filter: (manifest) => Promise.resolve(),
|
|
50
52
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
51
53
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -585,4 +587,3 @@ describe('DashParser SegmentTemplate', () => {
|
|
|
585
587
|
});
|
|
586
588
|
});
|
|
587
589
|
});
|
|
588
|
-
|
|
@@ -116,6 +116,8 @@ describe('HlsParser live', () => {
|
|
|
116
116
|
|
|
117
117
|
config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
118
118
|
playerInterface = {
|
|
119
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
120
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
119
121
|
filter: () => Promise.resolve(),
|
|
120
122
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
121
123
|
networkingEngine: fakeNetEngine,
|
|
@@ -94,6 +94,8 @@ describe('HlsParser', () => {
|
|
|
94
94
|
config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
95
95
|
onEventSpy = jasmine.createSpy('onEvent');
|
|
96
96
|
playerInterface = {
|
|
97
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
98
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
97
99
|
filter: () => Promise.resolve(),
|
|
98
100
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
99
101
|
networkingEngine: fakeNetEngine,
|
|
@@ -209,6 +211,80 @@ describe('HlsParser', () => {
|
|
|
209
211
|
expect(actual).toEqual(manifest);
|
|
210
212
|
});
|
|
211
213
|
|
|
214
|
+
it('parses manifest attributes with space', async () => {
|
|
215
|
+
const master = [
|
|
216
|
+
'#EXTM3U\n',
|
|
217
|
+
'#EXT-X-MEDIA:TYPE=AUDIO, GROUP-ID="aud1", LANGUAGE="eng", ',
|
|
218
|
+
'CHANNELS="16/JOC", URI="audio"\n',
|
|
219
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub1", LANGUAGE="eng", ',
|
|
220
|
+
'URI="text"\n',
|
|
221
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub2", LANGUAGE="es", ',
|
|
222
|
+
'URI="text2"\n',
|
|
223
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200, CODECS="avc1,mp4a", ',
|
|
224
|
+
'RESOLUTION=960x540, FRAME-RATE=60, AUDIO="aud1"\n',
|
|
225
|
+
'video\n',
|
|
226
|
+
].join('');
|
|
227
|
+
|
|
228
|
+
const media = [
|
|
229
|
+
'#EXTM3U\n',
|
|
230
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
231
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
232
|
+
'#EXTINF:5,\n',
|
|
233
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
234
|
+
'main.mp4',
|
|
235
|
+
].join('');
|
|
236
|
+
|
|
237
|
+
const textMedia = [
|
|
238
|
+
'#EXTM3U\n',
|
|
239
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
240
|
+
'#EXTINF:5,\n',
|
|
241
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
242
|
+
'main.vtt',
|
|
243
|
+
].join('');
|
|
244
|
+
|
|
245
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
246
|
+
manifest.anyTimeline();
|
|
247
|
+
manifest.addPartialVariant((variant) => {
|
|
248
|
+
variant.language = 'en';
|
|
249
|
+
variant.bandwidth = 200;
|
|
250
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
251
|
+
stream.frameRate = 60;
|
|
252
|
+
stream.mime('video/mp4', 'avc1');
|
|
253
|
+
stream.size(960, 540);
|
|
254
|
+
});
|
|
255
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
256
|
+
stream.language = 'en';
|
|
257
|
+
stream.channelsCount = 16;
|
|
258
|
+
stream.spatialAudio = true;
|
|
259
|
+
stream.mime('audio/mp4', 'mp4a');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
manifest.addPartialTextStream((stream) => {
|
|
263
|
+
stream.language = 'en';
|
|
264
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
265
|
+
stream.mime('text/vtt', '');
|
|
266
|
+
});
|
|
267
|
+
manifest.addPartialTextStream((stream) => {
|
|
268
|
+
stream.language = 'es';
|
|
269
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
270
|
+
stream.mime('text/vtt', '');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
fakeNetEngine
|
|
275
|
+
.setResponseText('test:/master', master)
|
|
276
|
+
.setResponseText('test:/audio', media)
|
|
277
|
+
.setResponseText('test:/video', media)
|
|
278
|
+
.setResponseText('test:/text', textMedia)
|
|
279
|
+
.setResponseText('test:/text2', textMedia)
|
|
280
|
+
.setResponseText('test:/main.vtt', vttText)
|
|
281
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
282
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
283
|
+
|
|
284
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
285
|
+
expect(actual).toEqual(manifest);
|
|
286
|
+
});
|
|
287
|
+
|
|
212
288
|
it('prioritize AVERAGE-BANDWIDTH to BANDWIDTH', async () => {
|
|
213
289
|
const master = [
|
|
214
290
|
'#EXTM3U\n',
|
|
@@ -31,7 +31,7 @@ describe('DrmEngine', () => {
|
|
|
31
31
|
navigator.requestMediaKeySystemAccess;
|
|
32
32
|
const originalLogError = shaka.log.error;
|
|
33
33
|
const originalBatchTime = shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME;
|
|
34
|
-
const originalDecodingInfo =
|
|
34
|
+
const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
|
|
35
35
|
|
|
36
36
|
/** @type {!jasmine.Spy} */
|
|
37
37
|
let decodingInfoSpy;
|
|
@@ -76,7 +76,7 @@ describe('DrmEngine', () => {
|
|
|
76
76
|
|
|
77
77
|
beforeEach(() => {
|
|
78
78
|
decodingInfoSpy = jasmine.createSpy('decodingInfo');
|
|
79
|
-
|
|
79
|
+
navigator.mediaCapabilities.decodingInfo =
|
|
80
80
|
shaka.test.Util.spyFunc(decodingInfoSpy);
|
|
81
81
|
|
|
82
82
|
logErrorSpy = jasmine.createSpy('shaka.log.error');
|
|
@@ -153,7 +153,7 @@ describe('DrmEngine', () => {
|
|
|
153
153
|
|
|
154
154
|
navigator.requestMediaKeySystemAccess =
|
|
155
155
|
originalRequestMediaKeySystemAccess;
|
|
156
|
-
|
|
156
|
+
navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
|
|
157
157
|
shaka.log.error = originalLogError;
|
|
158
158
|
});
|
|
159
159
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.require('shaka.media.QualityObserver');
|
|
8
|
+
goog.require('shaka.test.Util');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
describe('QualityObserver', () => {
|
|
12
|
+
/** @type {!shaka.media.QualityObserver} */
|
|
13
|
+
let observer;
|
|
14
|
+
|
|
15
|
+
/** @type {!jasmine.Spy} */
|
|
16
|
+
let onQualityChange;
|
|
17
|
+
|
|
18
|
+
const createQualityInfo = (contentType, bandwidth) => {
|
|
19
|
+
return {
|
|
20
|
+
bandwidth: bandwidth,
|
|
21
|
+
audioSamplingRate: 444000,
|
|
22
|
+
codecs: 'my codec',
|
|
23
|
+
contentType: contentType,
|
|
24
|
+
frameRate: 30,
|
|
25
|
+
height: 720,
|
|
26
|
+
mimeType: 'mime type',
|
|
27
|
+
channelsCount: 2,
|
|
28
|
+
pixelAspectRatio: '1:1',
|
|
29
|
+
width: 1280,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
let emptyBuffer = true;
|
|
33
|
+
let bufferStart = 0;
|
|
34
|
+
let bufferEnd = 0;
|
|
35
|
+
|
|
36
|
+
const quality1 = createQualityInfo('video', 1);
|
|
37
|
+
const quality2 = createQualityInfo('video', 2);
|
|
38
|
+
|
|
39
|
+
const getBufferedInfo = () => {
|
|
40
|
+
if (emptyBuffer) {
|
|
41
|
+
return {
|
|
42
|
+
video: [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
video: [{start: bufferStart, end: bufferEnd}],
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
onQualityChange = jasmine.createSpy('onQualityChange');
|
|
52
|
+
observer = new shaka.media.QualityObserver(getBufferedInfo);
|
|
53
|
+
observer.addEventListener('qualitychange', (event) => {
|
|
54
|
+
shaka.test.Util.spyFunc(onQualityChange)(
|
|
55
|
+
event['quality'], event['position']);
|
|
56
|
+
});
|
|
57
|
+
emptyBuffer = true;
|
|
58
|
+
bufferStart = 0;
|
|
59
|
+
bufferEnd = 0;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does not call onQualityChange when there are no quality changes', () => {
|
|
63
|
+
observer.poll(10, false);
|
|
64
|
+
expect(onQualityChange).not.toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('calls onQualityChange when position is after 1st quality change', () => {
|
|
68
|
+
observer.addMediaQualityChange(quality1, 10);
|
|
69
|
+
emptyBuffer = false;
|
|
70
|
+
bufferStart = 10;
|
|
71
|
+
bufferEnd = 20;
|
|
72
|
+
observer.poll(10, false);
|
|
73
|
+
expect(onQualityChange).toHaveBeenCalledWith(quality1, 10);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('does not call onQualityChange when pos advances with no change', () => {
|
|
77
|
+
observer.addMediaQualityChange(quality1, 10);
|
|
78
|
+
emptyBuffer = false;
|
|
79
|
+
bufferStart = 10;
|
|
80
|
+
bufferEnd = 20;
|
|
81
|
+
observer.poll(10, false);
|
|
82
|
+
expect(onQualityChange).toHaveBeenCalledWith(quality1, 10);
|
|
83
|
+
observer.poll(11, false);
|
|
84
|
+
expect(onQualityChange).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not call onQualityChange on seek to unbuffered position', () => {
|
|
88
|
+
observer.addMediaQualityChange(quality1, 10);
|
|
89
|
+
emptyBuffer = false;
|
|
90
|
+
bufferStart = 10;
|
|
91
|
+
bufferEnd = 20;
|
|
92
|
+
observer.poll(15, false);
|
|
93
|
+
expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 15]);
|
|
94
|
+
observer.addMediaQualityChange(quality2, 20);
|
|
95
|
+
observer.poll(25, true);
|
|
96
|
+
expect(onQualityChange).not.toHaveBeenCalledOnceMore();
|
|
97
|
+
bufferEnd = 30;
|
|
98
|
+
observer.poll(26, false);
|
|
99
|
+
expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 26]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('calls onQualityChange when position advances over 2nd quality change',
|
|
103
|
+
() => {
|
|
104
|
+
observer.addMediaQualityChange(quality1, 10);
|
|
105
|
+
emptyBuffer = false;
|
|
106
|
+
bufferStart = 10;
|
|
107
|
+
bufferEnd = 20;
|
|
108
|
+
observer.poll(10, false);
|
|
109
|
+
expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 10]);
|
|
110
|
+
observer.addMediaQualityChange(quality2, 20);
|
|
111
|
+
bufferStart = 10;
|
|
112
|
+
bufferEnd = 30;
|
|
113
|
+
observer.poll(20, false);
|
|
114
|
+
expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 20]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('calls onQualityChange when position moves back over a quality chanage',
|
|
118
|
+
() => {
|
|
119
|
+
observer.addMediaQualityChange(quality1, 10);
|
|
120
|
+
emptyBuffer = false;
|
|
121
|
+
bufferStart = 10;
|
|
122
|
+
bufferEnd = 20;
|
|
123
|
+
observer.addMediaQualityChange(quality2, 20);
|
|
124
|
+
bufferStart = 10;
|
|
125
|
+
bufferEnd = 30;
|
|
126
|
+
observer.poll(25, false);
|
|
127
|
+
expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 25]);
|
|
128
|
+
observer.poll(15, false);
|
|
129
|
+
expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 15]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('uses last applied quality when there are two at the same position',
|
|
133
|
+
() => {
|
|
134
|
+
observer.addMediaQualityChange(quality1, 10);
|
|
135
|
+
observer.addMediaQualityChange(quality2, 10);
|
|
136
|
+
emptyBuffer = false;
|
|
137
|
+
bufferStart = 10;
|
|
138
|
+
bufferEnd = 20;
|
|
139
|
+
observer.poll(15, false);
|
|
140
|
+
expect(onQualityChange).toHaveBeenCalledWith(quality2, 15);
|
|
141
|
+
});
|
|
142
|
+
});
|