shaka-player 3.2.2 → 3.3.0
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/AUTHORS +6 -5
- package/CHANGELOG.md +247 -0
- package/CONTRIBUTORS +4 -2
- 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 +18 -11
- package/dist/locales.js +28 -26
- 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 +205 -15
- package/dist/shaka-player.compiled.debug.d.ts +205 -15
- package/dist/shaka-player.compiled.debug.externs.js +256 -6
- package/dist/shaka-player.compiled.debug.js +406 -354
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +256 -6
- package/dist/shaka-player.compiled.js +826 -800
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +221 -16
- package/dist/shaka-player.ui.debug.d.ts +221 -16
- package/dist/shaka-player.ui.debug.externs.js +283 -6
- package/dist/shaka-player.ui.debug.js +547 -476
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +283 -6
- package/dist/shaka-player.ui.js +1019 -980
- 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/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 +130 -5
- package/externs/shaka/text.js +14 -0
- package/externs/webcrypto.js +18 -0
- 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/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_utils.js +1 -0
- package/lib/dash/dash_parser.js +48 -14
- 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 +86 -23
- package/lib/media/quality_observer.js +291 -0
- package/lib/media/segment_index.js +6 -0
- package/lib/media/segment_reference.js +18 -3
- package/lib/media/streaming_engine.js +42 -4
- 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 +81 -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 +393 -284
- package/lib/offline/stored_content_utils.js +2 -0
- package/lib/player.js +206 -7
- package/lib/polyfill/random_uuid.js +52 -0
- package/lib/text/cue.js +12 -0
- package/lib/text/ttml_text_parser.js +25 -2
- package/lib/text/ui_text_displayer.js +2 -2
- package/lib/util/cmcd_manager.js +826 -0
- package/lib/util/error.js +6 -0
- 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/package.json +1 -1
- 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 +101 -1
- package/test/dash/dash_parser_manifest_unit.js +35 -1
- 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 +2 -0
- package/test/media/quality_observer_unit.js +139 -0
- package/test/media/segment_reference_unit.js +16 -1
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +2 -0
- package/test/net/http_plugin_unit.js +20 -15
- package/test/offline/storage_integration.js +108 -4
- package/test/player_integration.js +99 -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/text/ttml_text_parser_unit.js +47 -0
- package/test/ui/ui_unit.js +250 -0
- package/test/util/cmcd_manager_unit.js +277 -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 +41 -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/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 +11 -4
- package/ui/ui.js +30 -0
- package/ui/volume_bar.js +1 -1
|
@@ -87,6 +87,41 @@ describe('Player', () => {
|
|
|
87
87
|
});
|
|
88
88
|
}); // describe('attach')
|
|
89
89
|
|
|
90
|
+
describe('updateStartTime() in manifestparsed event handler', () => {
|
|
91
|
+
it('does not get segments prior to startTime', async () => {
|
|
92
|
+
player.addEventListener('manifestparsed', () => {
|
|
93
|
+
player.updateStartTime(24);
|
|
94
|
+
});
|
|
95
|
+
const results = {
|
|
96
|
+
requestedVideoSegment0: false,
|
|
97
|
+
requestedVideoSegment1: false,
|
|
98
|
+
requestedVideoSegment2: false,
|
|
99
|
+
};
|
|
100
|
+
const expected = {
|
|
101
|
+
requestedVideoSegment0: false,
|
|
102
|
+
requestedVideoSegment1: false,
|
|
103
|
+
requestedVideoSegment2: true,
|
|
104
|
+
};
|
|
105
|
+
player.getNetworkingEngine().registerRequestFilter(
|
|
106
|
+
(type, request) => {
|
|
107
|
+
if (request.uris[0] == 'test:sintel/video/0') {
|
|
108
|
+
results.requestedVideoSegment0 = true;
|
|
109
|
+
}
|
|
110
|
+
if (request.uris[0] == 'test:sintel/video/1') {
|
|
111
|
+
results.requestedVideoSegment1 = true;
|
|
112
|
+
}
|
|
113
|
+
if (request.uris[0] == 'test:sintel/video/2') {
|
|
114
|
+
results.requestedVideoSegment2 = true;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
await player.load('test:sintel_compiled');
|
|
118
|
+
video.play();
|
|
119
|
+
await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 25, 30);
|
|
120
|
+
expect(results).toEqual(expected);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
|
|
90
125
|
describe('getStats', () => {
|
|
91
126
|
it('gives stats about current stream', async () => {
|
|
92
127
|
// This is tested more in player_unit.js. This is here to test the public
|
|
@@ -652,6 +687,70 @@ describe('Player', () => {
|
|
|
652
687
|
});
|
|
653
688
|
});
|
|
654
689
|
|
|
690
|
+
describe('mediaQualityChanges', () => {
|
|
691
|
+
/** @type {!jasmine.Spy} */
|
|
692
|
+
let onQualityChange;
|
|
693
|
+
/** @type {!shaka.test.Waiter} */
|
|
694
|
+
let waiter;
|
|
695
|
+
|
|
696
|
+
const qualityChange1 = jasmine.objectContaining({
|
|
697
|
+
mediaQuality: jasmine.objectContaining({
|
|
698
|
+
bandwidth: 1,
|
|
699
|
+
codecs: 'mp4a.40.2',
|
|
700
|
+
contentType: 'audio',
|
|
701
|
+
mimeType: 'audio/mp4',
|
|
702
|
+
}),
|
|
703
|
+
type: 'mediaqualitychanged',
|
|
704
|
+
position: jasmine.any(Number),
|
|
705
|
+
});
|
|
706
|
+
const qualityChange2 = jasmine.objectContaining({
|
|
707
|
+
mediaQuality: jasmine.objectContaining({
|
|
708
|
+
bandwidth: 1,
|
|
709
|
+
codecs: 'avc1.42c01e',
|
|
710
|
+
contentType: 'video',
|
|
711
|
+
mimeType: 'video/mp4',
|
|
712
|
+
}),
|
|
713
|
+
type: 'mediaqualitychanged',
|
|
714
|
+
position: jasmine.any(Number),
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
beforeEach(() => {
|
|
718
|
+
onQualityChange = jasmine.createSpy('onQualityChange');
|
|
719
|
+
// const spyFunc = Util.spyFunc(onQualityChange);
|
|
720
|
+
player.addEventListener('mediaqualitychanged',
|
|
721
|
+
Util.spyFunc(onQualityChange));
|
|
722
|
+
waiter = new shaka.test.Waiter(eventManager)
|
|
723
|
+
.timeoutAfter(10)
|
|
724
|
+
.failOnTimeout(true);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it('emits audio/video quality changes at start when enabled', async () => {
|
|
728
|
+
player.configure('streaming.observeQualityChanges', true);
|
|
729
|
+
|
|
730
|
+
await player.load('test:sintel_compiled');
|
|
731
|
+
video.play();
|
|
732
|
+
|
|
733
|
+
// Wait for the video to start playback. If it takes longer than 10
|
|
734
|
+
// seconds, fail the test.
|
|
735
|
+
await waiter.waitForMovementOrFailOnTimeout(video, 10);
|
|
736
|
+
expect(onQualityChange).toHaveBeenCalledTimes(2);
|
|
737
|
+
expect(onQualityChange).toHaveBeenCalledWith(qualityChange1);
|
|
738
|
+
expect(onQualityChange).toHaveBeenCalledWith(qualityChange2);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it('does not emit quality changes at start when disabled', async () => {
|
|
742
|
+
player.configure('streaming.observeQualityChanges', false);
|
|
743
|
+
|
|
744
|
+
await player.load('test:sintel_compiled');
|
|
745
|
+
video.play();
|
|
746
|
+
|
|
747
|
+
// Wait for the video to start playback. If it takes longer than 10
|
|
748
|
+
// seconds, fail the test.
|
|
749
|
+
await waiter.waitForMovementOrFailOnTimeout(video, 10);
|
|
750
|
+
expect(onQualityChange).not.toHaveBeenCalled();
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
655
754
|
describe('seek range', () => {
|
|
656
755
|
// Regression test for Issue #3675.
|
|
657
756
|
it('has a reasonable seek range after going from live to VOD', async () => {
|
|
@@ -46,6 +46,8 @@ shaka.test.Dash = class {
|
|
|
46
46
|
|
|
47
47
|
const playerInterface = {
|
|
48
48
|
networkingEngine: networkingEngine,
|
|
49
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
50
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
49
51
|
filter: () => {},
|
|
50
52
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
51
53
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -78,6 +80,8 @@ shaka.test.Dash = class {
|
|
|
78
80
|
|
|
79
81
|
const playerInterface = {
|
|
80
82
|
networkingEngine: networkingEngine,
|
|
83
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
84
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
81
85
|
filter: () => {},
|
|
82
86
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
83
87
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -17,7 +17,7 @@ goog.require('shaka.test.Util');
|
|
|
17
17
|
*/
|
|
18
18
|
shaka.test.FakeDrmEngine = class {
|
|
19
19
|
constructor() {
|
|
20
|
-
/** @private {!Array.<
|
|
20
|
+
/** @private {!Array.<string>} */
|
|
21
21
|
this.offlineSessions_ = [];
|
|
22
22
|
/** @private {?shaka.extern.DrmInfo} */
|
|
23
23
|
this.drmInfo_ = null;
|
|
@@ -45,6 +45,13 @@ shaka.test.FakeDrmEngine = class {
|
|
|
45
45
|
// be returned.
|
|
46
46
|
this.getDrmInfo.and.callFake(() => this.drmInfo_);
|
|
47
47
|
|
|
48
|
+
/** @type {!jasmine.Spy} */
|
|
49
|
+
this.newInitData = jasmine.createSpy('newInitData');
|
|
50
|
+
this.newInitData.and.callFake((initDataType, initData) => {
|
|
51
|
+
const num = 1 + this.offlineSessions_.length;
|
|
52
|
+
this.offlineSessions_.push('session-' + num);
|
|
53
|
+
});
|
|
54
|
+
|
|
48
55
|
/** @type {!jasmine.Spy} */
|
|
49
56
|
this.getExpiration = jasmine.createSpy('getExpiration');
|
|
50
57
|
this.getExpiration.and.returnValue(Infinity);
|
|
@@ -90,7 +97,7 @@ shaka.test.FakeDrmEngine = class {
|
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
/**
|
|
93
|
-
* @param {!Array.<
|
|
100
|
+
* @param {!Array.<string>} sessions
|
|
94
101
|
*/
|
|
95
102
|
setSessionIds(sessions) {
|
|
96
103
|
// Copy the values to break the reference to the input value.
|
|
@@ -653,15 +653,16 @@ shaka.test.ManifestGenerator.Stream = class {
|
|
|
653
653
|
* @param {!Array.<string>} uris
|
|
654
654
|
* @param {number} startByte
|
|
655
655
|
* @param {?number} endByte
|
|
656
|
+
* @param {null|shaka.extern.MediaQualityInfo=} mediaQuality
|
|
656
657
|
*/
|
|
657
|
-
setInitSegmentReference(uris, startByte, endByte) {
|
|
658
|
+
setInitSegmentReference(uris, startByte, endByte, mediaQuality) {
|
|
658
659
|
goog.asserts.assert(this.manifest_,
|
|
659
660
|
'A top-level generated Manifest is required to use this method!');
|
|
660
661
|
|
|
661
662
|
const getUris = () => uris;
|
|
662
663
|
this.initSegmentReference_ =
|
|
663
664
|
new this.manifest_.shaka_.media.InitSegmentReference(
|
|
664
|
-
getUris, startByte, endByte);
|
|
665
|
+
getUris, startByte, endByte, mediaQuality);
|
|
665
666
|
}
|
|
666
667
|
|
|
667
668
|
/**
|
|
@@ -206,10 +206,23 @@ shaka.test.TestScheme = class {
|
|
|
206
206
|
* @param {string} name
|
|
207
207
|
*/
|
|
208
208
|
function addStreamInfo(stream, variant, data, contentType, name) {
|
|
209
|
+
const mediaQualityInfo = {
|
|
210
|
+
bandwidth: 1,
|
|
211
|
+
codecs: data[contentType].codecs || 'unknown',
|
|
212
|
+
contentType: contentType,
|
|
213
|
+
mimeType: data[contentType].mimeType,
|
|
214
|
+
audioSamplingRate: null,
|
|
215
|
+
frameRate: null,
|
|
216
|
+
height: null,
|
|
217
|
+
channelsCount: null,
|
|
218
|
+
pixelAspectRatio: null,
|
|
219
|
+
width: null,
|
|
220
|
+
};
|
|
209
221
|
stream.mimeType = data[contentType].mimeType;
|
|
210
222
|
stream.codecs = data[contentType].codecs;
|
|
211
223
|
stream.setInitSegmentReference(
|
|
212
|
-
['test:' + name + '/' + contentType + '/init'], 0, null
|
|
224
|
+
['test:' + name + '/' + contentType + '/init'], 0, null,
|
|
225
|
+
mediaQualityInfo);
|
|
213
226
|
stream.useSegmentTemplate(
|
|
214
227
|
'test:' + name + '/' + contentType + '/%d',
|
|
215
228
|
data[contentType].segmentDuration);
|
|
@@ -1266,6 +1266,8 @@ describe('TtmlTextParser', () => {
|
|
|
1266
1266
|
fontStyle: Cue.fontStyle.ITALIC,
|
|
1267
1267
|
lineHeight: '20px',
|
|
1268
1268
|
fontSize: '10em',
|
|
1269
|
+
textStrokeColor: 'blue',
|
|
1270
|
+
textStrokeWidth: '3px',
|
|
1269
1271
|
},
|
|
1270
1272
|
{
|
|
1271
1273
|
startTime: 2,
|
|
@@ -1277,6 +1279,7 @@ describe('TtmlTextParser', () => {
|
|
|
1277
1279
|
'<tt xmlns:tts="http://www.w3.org/ns/ttml#styling">' +
|
|
1278
1280
|
'<styling>' +
|
|
1279
1281
|
'<style xml:id="s1" tts:color="red" ' +
|
|
1282
|
+
'tts:textOutline="blue 3px" ' +
|
|
1280
1283
|
'tts:backgroundColor="blue" ' +
|
|
1281
1284
|
'tts:fontWeight="bold" ' +
|
|
1282
1285
|
'tts:fontFamily="Times New Roman" ' +
|
|
@@ -1295,6 +1298,50 @@ describe('TtmlTextParser', () => {
|
|
|
1295
1298
|
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
|
|
1296
1299
|
});
|
|
1297
1300
|
|
|
1301
|
+
it('uses text color if tts:textOutline does not specify color', () => {
|
|
1302
|
+
verifyHelper(
|
|
1303
|
+
[
|
|
1304
|
+
{
|
|
1305
|
+
startTime: 1,
|
|
1306
|
+
endTime: 2,
|
|
1307
|
+
payload: 'Test',
|
|
1308
|
+
color: 'red',
|
|
1309
|
+
textStrokeColor: 'red',
|
|
1310
|
+
textStrokeWidth: '3px',
|
|
1311
|
+
},
|
|
1312
|
+
],
|
|
1313
|
+
'<tt xmlns:tts="http://www.w3.org/ns/ttml#styling">' +
|
|
1314
|
+
'<styling>' +
|
|
1315
|
+
'<style xml:id="s1" tts:color="red" ' +
|
|
1316
|
+
'tts:textOutline="3px" />' +
|
|
1317
|
+
'</styling>' +
|
|
1318
|
+
'<body region="subtitleArea"><div>' +
|
|
1319
|
+
'<p begin="00:01.00" end="00:02.00" style="s1">Test</p>' +
|
|
1320
|
+
'</div></body></tt>',
|
|
1321
|
+
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
it('does not add an outline if tts:textOutline only contains color', () => {
|
|
1325
|
+
verifyHelper(
|
|
1326
|
+
[
|
|
1327
|
+
{
|
|
1328
|
+
startTime: 1,
|
|
1329
|
+
endTime: 2,
|
|
1330
|
+
payload: 'Test',
|
|
1331
|
+
color: 'red',
|
|
1332
|
+
},
|
|
1333
|
+
],
|
|
1334
|
+
'<tt xmlns:tts="http://www.w3.org/ns/ttml#styling">' +
|
|
1335
|
+
'<styling>' +
|
|
1336
|
+
'<style xml:id="s1" tts:color="red" ' +
|
|
1337
|
+
'tts:textOutline="blue" />' +
|
|
1338
|
+
'</styling>' +
|
|
1339
|
+
'<body region="subtitleArea"><div>' +
|
|
1340
|
+
'<p begin="00:01.00" end="00:02.00" style="s1">Test</p>' +
|
|
1341
|
+
'</div></body></tt>',
|
|
1342
|
+
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1298
1345
|
// Regression test for #2623
|
|
1299
1346
|
it('does not apply background colors to containers', () => {
|
|
1300
1347
|
verifyHelper(
|
package/test/ui/ui_unit.js
CHANGED
|
@@ -409,6 +409,80 @@ describe('UI', () => {
|
|
|
409
409
|
});
|
|
410
410
|
});
|
|
411
411
|
|
|
412
|
+
describe('control panel buttons with submenus', () => {
|
|
413
|
+
/** @type {!HTMLElement} */
|
|
414
|
+
let resolutionMenu;
|
|
415
|
+
/** @type {!Element} */
|
|
416
|
+
let resolutionMenuButton;
|
|
417
|
+
/** @type {!HTMLElement} */
|
|
418
|
+
let languageMenu;
|
|
419
|
+
/** @type {!Element} */
|
|
420
|
+
let languageMenuButton;
|
|
421
|
+
|
|
422
|
+
beforeEach(() => {
|
|
423
|
+
const config = {
|
|
424
|
+
controlPanelElements: [
|
|
425
|
+
'quality',
|
|
426
|
+
'language',
|
|
427
|
+
],
|
|
428
|
+
};
|
|
429
|
+
const ui = UiUtils.createUIThroughAPI(videoContainer, video, config);
|
|
430
|
+
player = ui.getControls().getLocalPlayer();
|
|
431
|
+
|
|
432
|
+
const resolutionsMenus =
|
|
433
|
+
videoContainer.getElementsByClassName('shaka-resolutions');
|
|
434
|
+
expect(resolutionsMenus.length).toBe(1);
|
|
435
|
+
resolutionMenu = /** @type {!HTMLElement} */ (resolutionsMenus[0]);
|
|
436
|
+
|
|
437
|
+
const resolutionMenuButtons =
|
|
438
|
+
videoContainer.getElementsByClassName('shaka-resolution-button');
|
|
439
|
+
expect(resolutionMenuButtons.length).toBe(1);
|
|
440
|
+
resolutionMenuButton = resolutionMenuButtons[0];
|
|
441
|
+
|
|
442
|
+
const languageMenus =
|
|
443
|
+
videoContainer.getElementsByClassName('shaka-audio-languages');
|
|
444
|
+
expect(languageMenus.length).toBe(1);
|
|
445
|
+
languageMenu = /** @type {!HTMLElement} */ (languageMenus[0]);
|
|
446
|
+
|
|
447
|
+
const languageMenuButtons =
|
|
448
|
+
videoContainer.getElementsByClassName('shaka-language-button');
|
|
449
|
+
expect(languageMenuButtons.length).toBe(1);
|
|
450
|
+
languageMenuButton = languageMenuButtons[0];
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('menus are initially hidden', () => {
|
|
454
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
455
|
+
expect(languageMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('a menu becomes visible if the button is clicked', () => {
|
|
459
|
+
resolutionMenuButton.click();
|
|
460
|
+
|
|
461
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(false);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('a menu becomes hidden if the "close" button is clicked', () => {
|
|
465
|
+
resolutionMenuButton.click();
|
|
466
|
+
|
|
467
|
+
const backToOverflowButtons =
|
|
468
|
+
videoContainer.getElementsByClassName('shaka-back-to-overflow-button');
|
|
469
|
+
expect(backToOverflowButtons.length).toBe(2);
|
|
470
|
+
const backToOverflowButton =
|
|
471
|
+
/** @type {!HTMLElement} */ (backToOverflowButtons[0]);
|
|
472
|
+
backToOverflowButton.click();
|
|
473
|
+
|
|
474
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('a menu becomes hidden if another one is opened', () => {
|
|
478
|
+
resolutionMenuButton.click();
|
|
479
|
+
languageMenuButton.click();
|
|
480
|
+
|
|
481
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
482
|
+
expect(languageMenu.classList.contains('shaka-hidden')).toBe(false);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
412
486
|
describe('resolutions menu', () => {
|
|
413
487
|
/** @type {!HTMLElement} */
|
|
414
488
|
let resolutionsMenu;
|
|
@@ -611,6 +685,182 @@ describe('UI', () => {
|
|
|
611
685
|
goog.asserts.assert(found, 'Unable to find resolution menu');
|
|
612
686
|
}
|
|
613
687
|
});
|
|
688
|
+
|
|
689
|
+
describe('custom context menu', () => {
|
|
690
|
+
/** @type {!HTMLElement} */
|
|
691
|
+
let controlsContainer;
|
|
692
|
+
/** @type {!HTMLElement} */
|
|
693
|
+
let contextMenu;
|
|
694
|
+
|
|
695
|
+
beforeEach(() => {
|
|
696
|
+
const config = {
|
|
697
|
+
customContextMenu: true,
|
|
698
|
+
contextMenuElements: [
|
|
699
|
+
'fakeElement',
|
|
700
|
+
'statistics',
|
|
701
|
+
'fakeElement',
|
|
702
|
+
],
|
|
703
|
+
};
|
|
704
|
+
const ui = UiUtils.createUIThroughAPI(videoContainer, video, config);
|
|
705
|
+
|
|
706
|
+
controlsContainer = ui.getControls().getControlsContainer();
|
|
707
|
+
|
|
708
|
+
const contextMenus =
|
|
709
|
+
videoContainer.getElementsByClassName('shaka-context-menu');
|
|
710
|
+
expect(contextMenus.length).toBe(1);
|
|
711
|
+
contextMenu = /** @type {!HTMLElement} */
|
|
712
|
+
(contextMenus[0]);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('responds to contextmenu event', () => {
|
|
716
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
717
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
718
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(false);
|
|
719
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
720
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
721
|
+
});
|
|
722
|
+
it('hides on click event', () => {
|
|
723
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
724
|
+
UiUtils.simulateEvent(controlsContainer, 'click');
|
|
725
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
726
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
727
|
+
UiUtils.simulateEvent(window, 'click');
|
|
728
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
729
|
+
});
|
|
730
|
+
it('builds internal elements', () => {
|
|
731
|
+
expect(contextMenu.childNodes.length).toBe(1);
|
|
732
|
+
|
|
733
|
+
expect(contextMenu.childNodes[0]['className'])
|
|
734
|
+
.toBe('shaka-statistics-button');
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
describe('statistics context menu', () => {
|
|
739
|
+
/** @type {!HTMLElement} */
|
|
740
|
+
let statisticsButton;
|
|
741
|
+
/** @type {!HTMLElement} */
|
|
742
|
+
let statisticsContainer;
|
|
743
|
+
|
|
744
|
+
beforeEach(() => {
|
|
745
|
+
const config = {
|
|
746
|
+
customContextMenu: true,
|
|
747
|
+
contextMenuElements: [
|
|
748
|
+
'statistics',
|
|
749
|
+
],
|
|
750
|
+
statisticsList: Object.keys(new shaka.util.Stats().getBlob()),
|
|
751
|
+
};
|
|
752
|
+
const ui = UiUtils.createUIThroughAPI(videoContainer, video, config);
|
|
753
|
+
player = ui.getControls().getLocalPlayer();
|
|
754
|
+
|
|
755
|
+
const statisticsButtons =
|
|
756
|
+
videoContainer.getElementsByClassName('shaka-statistics-button');
|
|
757
|
+
expect(statisticsButtons.length).toBe(1);
|
|
758
|
+
statisticsButton = /** @type {!HTMLElement} */
|
|
759
|
+
(statisticsButtons[0]);
|
|
760
|
+
|
|
761
|
+
const statisticsContainers =
|
|
762
|
+
videoContainer.getElementsByClassName('shaka-statistics-container');
|
|
763
|
+
expect(statisticsContainers.length).toBe(1);
|
|
764
|
+
statisticsContainer = /** @type {!HTMLElement} */
|
|
765
|
+
(statisticsContainers[0]);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('appears and disappears on toggle', () => {
|
|
769
|
+
expect(statisticsContainer.classList.contains('shaka-hidden'))
|
|
770
|
+
.toBe(true);
|
|
771
|
+
|
|
772
|
+
statisticsButton.click();
|
|
773
|
+
expect(statisticsContainer.classList.contains('shaka-hidden'))
|
|
774
|
+
.toBe(false);
|
|
775
|
+
|
|
776
|
+
statisticsButton.click();
|
|
777
|
+
expect(statisticsContainer.classList.contains('shaka-hidden'))
|
|
778
|
+
.toBe(true);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it('displays all the available statistics', () => {
|
|
782
|
+
const skippedStats = ['stateHistory', 'switchHistory'];
|
|
783
|
+
const nodes = statisticsContainer.childNodes;
|
|
784
|
+
let nodeIndex = 0;
|
|
785
|
+
|
|
786
|
+
for (const statistic in new shaka.util.Stats().getBlob()) {
|
|
787
|
+
if (!skippedStats.includes(statistic)) {
|
|
788
|
+
// Text content of label (without ':') is a valid statistic
|
|
789
|
+
const label = nodes[nodeIndex].childNodes[0].textContent;
|
|
790
|
+
expect(label.replace(':', '')).toBe(statistic);
|
|
791
|
+
|
|
792
|
+
// Value has been parsed and it is not the default 'NaN'
|
|
793
|
+
const value = nodes[nodeIndex].childNodes[1].textContent;
|
|
794
|
+
expect(value).not.toBe('NaN');
|
|
795
|
+
|
|
796
|
+
nodeIndex += 1;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
it('is updated periodically', async () => {
|
|
801
|
+
function getStatsFromContainer() {
|
|
802
|
+
const nodes = statisticsContainer.childNodes;
|
|
803
|
+
width = nodes[0].childNodes[1].textContent.replace(' (px)', '');
|
|
804
|
+
height = nodes[1].childNodes[1].textContent.replace(' (px)', '');
|
|
805
|
+
bufferingTime =
|
|
806
|
+
nodes[13].childNodes[1].textContent.replace(' (s)', '');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** @type {!string} */
|
|
810
|
+
let width;
|
|
811
|
+
/** @type {!string} */
|
|
812
|
+
let height;
|
|
813
|
+
/** @type {!string} */
|
|
814
|
+
let bufferingTime;
|
|
815
|
+
/** @type {!string} */
|
|
816
|
+
let lastBufferingTime;
|
|
817
|
+
|
|
818
|
+
const manifest =
|
|
819
|
+
shaka.test.ManifestGenerator.generate((manifest) => {
|
|
820
|
+
manifest.addVariant(/* id= */ 0, (variant) => {
|
|
821
|
+
variant.addVideo(1, (stream) => {
|
|
822
|
+
stream.size(1920, 1080);
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
828
|
+
fakeMimeType, () => new shaka.test.FakeManifestParser(manifest));
|
|
829
|
+
|
|
830
|
+
await player.load(
|
|
831
|
+
/* uri= */ 'fake', /* startTime= */ 0, fakeMimeType);
|
|
832
|
+
|
|
833
|
+
// Placeholder statistics are available before toggle
|
|
834
|
+
getStatsFromContainer();
|
|
835
|
+
expect(width).toBe('NaN');
|
|
836
|
+
expect(height).toBe('NaN');
|
|
837
|
+
expect(bufferingTime).toBe('NaN');
|
|
838
|
+
|
|
839
|
+
// Statistics are displayed on toggle
|
|
840
|
+
statisticsButton.click();
|
|
841
|
+
await Util.delay(0.2);
|
|
842
|
+
|
|
843
|
+
getStatsFromContainer();
|
|
844
|
+
expect(width).toBe('1920');
|
|
845
|
+
expect(height).toBe('1080');
|
|
846
|
+
expect(bufferingTime).toBeGreaterThanOrEqual(0.1);
|
|
847
|
+
|
|
848
|
+
// Statistics are updated over time
|
|
849
|
+
lastBufferingTime = bufferingTime;
|
|
850
|
+
await Util.delay(0.2);
|
|
851
|
+
|
|
852
|
+
getStatsFromContainer();
|
|
853
|
+
expect(bufferingTime).toBeGreaterThan(lastBufferingTime);
|
|
854
|
+
|
|
855
|
+
// Statistics stop updating when the container is hidden
|
|
856
|
+
statisticsButton.click();
|
|
857
|
+
lastBufferingTime = bufferingTime;
|
|
858
|
+
await Util.delay(0.2);
|
|
859
|
+
|
|
860
|
+
getStatsFromContainer();
|
|
861
|
+
expect(bufferingTime).toBe(lastBufferingTime);
|
|
862
|
+
});
|
|
863
|
+
});
|
|
614
864
|
});
|
|
615
865
|
|
|
616
866
|
|