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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.require('shaka.util.CmcdManager');
|
|
8
|
+
goog.require('shaka.util.ObjectUtils');
|
|
9
|
+
|
|
10
|
+
describe('CmcdManager', () => {
|
|
11
|
+
const CmcdManager = shaka.util.CmcdManager;
|
|
12
|
+
const uuidRegex =
|
|
13
|
+
'[A-F\\d]{8}-[A-F\\d]{4}-4[A-F\\d]{3}-[89AB][A-F\\d]{3}-[A-F\\d]{12}';
|
|
14
|
+
const data = {
|
|
15
|
+
'sid': 'c936730c-031e-4a73-976f-92bc34039c60',
|
|
16
|
+
'cid': 'xyz',
|
|
17
|
+
'su': false,
|
|
18
|
+
'nor': '../testing/3.m4v',
|
|
19
|
+
'nrr': '0-99',
|
|
20
|
+
'd': 6066.66,
|
|
21
|
+
'mtp': 10049,
|
|
22
|
+
'bs': true,
|
|
23
|
+
'br': 52317,
|
|
24
|
+
'v': 1,
|
|
25
|
+
'pr': 1,
|
|
26
|
+
'com.test-hello': 'world',
|
|
27
|
+
'com.test-testing': 1234,
|
|
28
|
+
'com.test-exists': true,
|
|
29
|
+
'com.test-notExists': false,
|
|
30
|
+
'com.test-token': Symbol('s'),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe('Query serialization', () => {
|
|
34
|
+
it('produces correctly serialized data', () => {
|
|
35
|
+
const query = CmcdManager.toQuery(data);
|
|
36
|
+
const result = 'CMCD=br%3D52317%2Cbs%2Ccid%3D%22xyz%22%2C' +
|
|
37
|
+
'com.test-exists%2Ccom.test-hello%3D%22world%22%2C' +
|
|
38
|
+
'com.test-testing%3D1234%2Ccom.test-token%3Ds%2C' +
|
|
39
|
+
'd%3D6067%2Cmtp%3D10000%2C' +
|
|
40
|
+
'nor%3D%22..%252Ftesting%252F3.m4v%22%2C' +
|
|
41
|
+
'nrr%3D%220-99%22%2C' +
|
|
42
|
+
'sid%3D%22c936730c-031e-4a73-976f-92bc34039c60%22';
|
|
43
|
+
expect(query).toBe(result);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('Header serialization', () => {
|
|
48
|
+
it('produces all header shards', () => {
|
|
49
|
+
const header = CmcdManager.toHeaders(data);
|
|
50
|
+
expect(header).toEqual({
|
|
51
|
+
'CMCD-Object': 'br=52317,d=6067',
|
|
52
|
+
'CMCD-Request': 'com.test-exists,com.test-hello="world",' +
|
|
53
|
+
'com.test-testing=1234,com.test-token=s,mtp=10000,' +
|
|
54
|
+
'nor="..%2Ftesting%2F3.m4v",nrr="0-99"',
|
|
55
|
+
'CMCD-Session': 'cid="xyz",sid="c936730c-031e-4a73-976f-92bc34039c60"',
|
|
56
|
+
'CMCD-Status': 'bs',
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('ignores empty shards', () => {
|
|
61
|
+
expect(CmcdManager.toHeaders({br: 200})).toEqual({
|
|
62
|
+
'CMCD-Object': 'br=200',
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('CmcdManager instance', () => {
|
|
68
|
+
const ObjectUtils = shaka.util.ObjectUtils;
|
|
69
|
+
|
|
70
|
+
const playerInterface = {
|
|
71
|
+
isLive: () => false,
|
|
72
|
+
getBandwidthEstimate: () => 10000000,
|
|
73
|
+
getBufferedInfo: () => ({
|
|
74
|
+
video: [
|
|
75
|
+
{start: 0, end: 5},
|
|
76
|
+
{start: 6, end: 31.234},
|
|
77
|
+
{start: 35, end: 40},
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
80
|
+
getManifest: () => /** @type {shaka.extern.Manifest} */({
|
|
81
|
+
variants: [
|
|
82
|
+
{video: {bandwidth: 50000}},
|
|
83
|
+
{video: {bandwidth: 5000000}},
|
|
84
|
+
],
|
|
85
|
+
}),
|
|
86
|
+
getPlaybackRate: () => 1,
|
|
87
|
+
getCurrentTime: () => 10,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const sid = '2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3';
|
|
91
|
+
const config = {
|
|
92
|
+
enabled: false,
|
|
93
|
+
sessionId: '',
|
|
94
|
+
contentId: 'testing',
|
|
95
|
+
useHeaders: false,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** @type shaka.util.CmcdManager */
|
|
99
|
+
let cmcdManager = new CmcdManager(playerInterface, config);
|
|
100
|
+
|
|
101
|
+
const request = {
|
|
102
|
+
uris: ['https://test.com/test.mpd'],
|
|
103
|
+
method: 'GET',
|
|
104
|
+
body: null,
|
|
105
|
+
headers: {
|
|
106
|
+
testing: '1234',
|
|
107
|
+
},
|
|
108
|
+
allowCrossSiteCredentials: false,
|
|
109
|
+
retryParameters: /** @type {shaka.extern.RetryParameters} */({}),
|
|
110
|
+
licenseRequestType: null,
|
|
111
|
+
sessionId: null,
|
|
112
|
+
streamDataCallback: null,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const manifestInfo = {
|
|
116
|
+
format: shaka.util.CmcdManager.StreamingFormat.DASH,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const segmentInfo = {
|
|
120
|
+
type: 'video',
|
|
121
|
+
init: false,
|
|
122
|
+
duration: 3.33,
|
|
123
|
+
mimeType: 'application/mp4',
|
|
124
|
+
codecs: 'avc1.42001e',
|
|
125
|
+
bandwidth: 5234167,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
describe('configuration', () => {
|
|
129
|
+
it('does not modify requests when disabled', () => {
|
|
130
|
+
const r = ObjectUtils.cloneObject(request);
|
|
131
|
+
|
|
132
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
133
|
+
expect(r.uris[0]).toBe(request.uris[0]);
|
|
134
|
+
|
|
135
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
136
|
+
expect(r.uris[0]).toBe(request.uris[0]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('generates a session id if not provided', () => {
|
|
140
|
+
config.enabled = true;
|
|
141
|
+
cmcdManager = new CmcdManager(playerInterface, config);
|
|
142
|
+
|
|
143
|
+
const r = ObjectUtils.cloneObject(request);
|
|
144
|
+
|
|
145
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
146
|
+
const regex = new RegExp(`sid%3D%22${uuidRegex}%22`, 'i');
|
|
147
|
+
expect(regex.test(r.uris[0])).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('query mode', () => {
|
|
152
|
+
beforeAll(() => {
|
|
153
|
+
config.sessionId = sid;
|
|
154
|
+
cmcdManager = new CmcdManager(playerInterface, config);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('modifies manifest request uris', () => {
|
|
158
|
+
const r = ObjectUtils.cloneObject(request);
|
|
159
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
160
|
+
const uri = 'https://test.com/test.mpd?CMCD=cid%3D%22testing%22%2C' +
|
|
161
|
+
'mtp%3D10000%2Cot%3Dm%2Csf%3Dd%2C' +
|
|
162
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
163
|
+
expect(r.uris[0]).toBe(uri);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('modifies segment request uris', () => {
|
|
167
|
+
const r = ObjectUtils.cloneObject(request);
|
|
168
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
169
|
+
const uri = 'https://test.com/test.mpd?CMCD=bl%3D21200%2Cbr%3D5234%2Ccid%3D%22' +
|
|
170
|
+
'testing%22%2Cd%3D3330%2Cmtp%3D10000%2Cot%3Dv%2Csf%3Dd%2C' +
|
|
171
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Cst%3Dv%2Csu%2C' +
|
|
172
|
+
'tb%3D5000';
|
|
173
|
+
expect(r.uris[0]).toBe(uri);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('modifies text request uris', () => {
|
|
177
|
+
const r = ObjectUtils.cloneObject(request);
|
|
178
|
+
cmcdManager.applyTextData(r);
|
|
179
|
+
const uri = 'https://test.com/test.mpd?CMCD=cid%3D%22' +
|
|
180
|
+
'testing%22%2Cmtp%3D10000%2Cot%3Dc%2Csf%3Dd%2C' +
|
|
181
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
182
|
+
expect(r.uris[0]).toBe(uri);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('header mode', () => {
|
|
187
|
+
beforeAll(() => {
|
|
188
|
+
config.useHeaders = true;
|
|
189
|
+
cmcdManager = new CmcdManager(playerInterface, config);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('modifies manifest request headers', () => {
|
|
193
|
+
const r = ObjectUtils.cloneObject(request);
|
|
194
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
195
|
+
expect(r.headers).toEqual({
|
|
196
|
+
'testing': '1234',
|
|
197
|
+
'CMCD-Object': 'ot=m',
|
|
198
|
+
'CMCD-Request': 'mtp=10000,su',
|
|
199
|
+
'CMCD-Session': 'cid="testing",sf=d,' +
|
|
200
|
+
'sid="2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3"',
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('modifies segment request headers', () => {
|
|
205
|
+
const r = ObjectUtils.cloneObject(request);
|
|
206
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
207
|
+
expect(r.headers).toEqual({
|
|
208
|
+
'testing': '1234',
|
|
209
|
+
'CMCD-Object': 'br=5234,d=3330,ot=v,tb=5000',
|
|
210
|
+
'CMCD-Request': 'bl=21200,mtp=10000,su',
|
|
211
|
+
'CMCD-Session': 'cid="testing",sf=d,' +
|
|
212
|
+
'sid="2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3",st=v',
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('src= mode', () => {
|
|
218
|
+
it('modifies media stream uris', () => {
|
|
219
|
+
const r = cmcdManager
|
|
220
|
+
.appendSrcData('https://test.com/test.mp4', 'video/mp4');
|
|
221
|
+
const uri = 'https://test.com/test.mp4?CMCD=cid%3D%22testing%22%2C' +
|
|
222
|
+
'mtp%3D10000%2Cot%3Dav%2Csf%3Dd%2C' +
|
|
223
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
224
|
+
expect(r).toBe(uri);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('modifies manifest stream uris', () => {
|
|
228
|
+
const r = cmcdManager
|
|
229
|
+
.appendSrcData('https://test.com/test.m3u8', 'application/x-mpegurl');
|
|
230
|
+
const uri = 'https://test.com/test.m3u8?CMCD=cid%3D%22testing%22%2C' +
|
|
231
|
+
'mtp%3D10000%2Cot%3Dm%2Csf%3Dd%2C' +
|
|
232
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
233
|
+
expect(r).toBe(uri);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('modifies text track uris', () => {
|
|
237
|
+
const r = cmcdManager.appendTextTrackData('https://test.com/test.vtt');
|
|
238
|
+
const uri = 'https://test.com/test.vtt?CMCD=cid%3D%22testing%22%2C' +
|
|
239
|
+
'mtp%3D10000%2Cot%3Dc%2Csf%3Dd%2C' +
|
|
240
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
241
|
+
expect(r).toBe(uri);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('adheres to the spec', () => {
|
|
246
|
+
beforeAll(() => {
|
|
247
|
+
cmcdManager.setBuffering(false);
|
|
248
|
+
cmcdManager.setBuffering(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('sends bs only once', () => {
|
|
252
|
+
let r = ObjectUtils.cloneObject(request);
|
|
253
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
254
|
+
expect(r.headers['CMCD-Status']).toContain('bs');
|
|
255
|
+
|
|
256
|
+
r = ObjectUtils.cloneObject(request);
|
|
257
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
258
|
+
expect(r.headers['CMCD-Status']).not.toContain('bs');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('sends su until buffering is complete', () => {
|
|
262
|
+
let r = ObjectUtils.cloneObject(request);
|
|
263
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
264
|
+
expect(r.headers['CMCD-Request']).toContain(',su');
|
|
265
|
+
|
|
266
|
+
r = ObjectUtils.cloneObject(request);
|
|
267
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
268
|
+
expect(r.headers['CMCD-Request']).toContain(',su');
|
|
269
|
+
|
|
270
|
+
cmcdManager.setBuffering(false);
|
|
271
|
+
r = ObjectUtils.cloneObject(request);
|
|
272
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
273
|
+
expect(r.headers['CMCD-Request']).not.toContain(',su');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
package/ui/airplay_button.js
CHANGED
|
@@ -36,6 +36,7 @@ shaka.ui.AirPlayButton = class extends shaka.ui.Element {
|
|
|
36
36
|
/** @private {!HTMLButtonElement} */
|
|
37
37
|
this.airplayButton_ = shaka.util.Dom.createButton();
|
|
38
38
|
this.airplayButton_.classList.add('shaka-airplay-button');
|
|
39
|
+
this.airplayButton_.classList.add('shaka-tooltip');
|
|
39
40
|
this.airplayButton_.ariaPressed = 'false';
|
|
40
41
|
|
|
41
42
|
/** @private {!HTMLElement} */
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
goog.provide('shaka.ui.AudioLanguageSelection');
|
|
9
9
|
|
|
10
|
+
goog.require('shaka.ui.Controls');
|
|
10
11
|
goog.require('shaka.ui.Enums');
|
|
11
12
|
goog.require('shaka.ui.LanguageUtils');
|
|
12
13
|
goog.require('shaka.ui.Locales');
|
|
@@ -31,6 +32,7 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
|
|
|
31
32
|
super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.LANGUAGE);
|
|
32
33
|
|
|
33
34
|
this.button.classList.add('shaka-language-button');
|
|
35
|
+
this.button.classList.add('shaka-tooltip-status');
|
|
34
36
|
this.menu.classList.add('shaka-audio-languages');
|
|
35
37
|
|
|
36
38
|
this.eventManager.listen(
|
|
@@ -71,6 +73,8 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
|
|
|
71
73
|
|
|
72
74
|
this.controls.dispatchEvent(
|
|
73
75
|
new shaka.util.FakeEvent('languageselectionupdated'));
|
|
76
|
+
|
|
77
|
+
this.button.setAttribute('shaka-status', this.currentSelection.innerText);
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
/** @private */
|
|
@@ -118,3 +122,6 @@ shaka.ui.AudioLanguageSelection.Factory = class {
|
|
|
118
122
|
|
|
119
123
|
shaka.ui.OverflowMenu.registerElement(
|
|
120
124
|
'language', new shaka.ui.AudioLanguageSelection.Factory());
|
|
125
|
+
|
|
126
|
+
shaka.ui.Controls.registerElement(
|
|
127
|
+
'language', new shaka.ui.AudioLanguageSelection.Factory());
|
package/ui/cast_button.js
CHANGED
|
@@ -41,6 +41,7 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
|
|
|
41
41
|
/** @private {!HTMLButtonElement} */
|
|
42
42
|
this.castButton_ = shaka.util.Dom.createButton();
|
|
43
43
|
this.castButton_.classList.add('shaka-cast-button');
|
|
44
|
+
this.castButton_.classList.add('shaka-tooltip');
|
|
44
45
|
this.castButton_.ariaPressed = 'false';
|
|
45
46
|
|
|
46
47
|
/** @private {!HTMLElement} */
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
goog.provide('shaka.ui.ContextMenu');
|
|
9
|
+
|
|
10
|
+
goog.require('goog.asserts');
|
|
11
|
+
goog.require('shaka.log');
|
|
12
|
+
goog.require('shaka.ui.Element');
|
|
13
|
+
goog.require('shaka.ui.Utils');
|
|
14
|
+
goog.require('shaka.util.Dom');
|
|
15
|
+
goog.requireType('shaka.ui.Controls');
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @extends {shaka.ui.Element}
|
|
20
|
+
* @final
|
|
21
|
+
* @export
|
|
22
|
+
*/
|
|
23
|
+
shaka.ui.ContextMenu = class extends shaka.ui.Element {
|
|
24
|
+
/**
|
|
25
|
+
* @param {!HTMLElement} parent
|
|
26
|
+
* @param {!shaka.ui.Controls} controls
|
|
27
|
+
*/
|
|
28
|
+
constructor(parent, controls) {
|
|
29
|
+
super(parent, controls);
|
|
30
|
+
|
|
31
|
+
/** @private {!shaka.extern.UIConfiguration} */
|
|
32
|
+
this.config_ = this.controls.getConfig();
|
|
33
|
+
|
|
34
|
+
/** @private {HTMLElement} */
|
|
35
|
+
this.controlsContainer_ = this.controls.getControlsContainer();
|
|
36
|
+
|
|
37
|
+
/** @private {!Array.<shaka.extern.IUIElement>} */
|
|
38
|
+
this.children_ = [];
|
|
39
|
+
|
|
40
|
+
/** @private {!HTMLElement} */
|
|
41
|
+
this.contextMenu_ = shaka.util.Dom.createHTMLElement('div');
|
|
42
|
+
this.contextMenu_.classList.add('shaka-no-propagation');
|
|
43
|
+
this.contextMenu_.classList.add('shaka-context-menu');
|
|
44
|
+
this.contextMenu_.classList.add('shaka-hidden');
|
|
45
|
+
|
|
46
|
+
this.controlsContainer_.appendChild(this.contextMenu_);
|
|
47
|
+
|
|
48
|
+
this.eventManager.listen(this.controlsContainer_, 'contextmenu', (e) => {
|
|
49
|
+
if (this.contextMenu_.classList.contains('shaka-hidden')) {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
|
|
52
|
+
const controlsLocation =
|
|
53
|
+
this.controlsContainer_.getBoundingClientRect();
|
|
54
|
+
this.contextMenu_.style.left = `${e.clientX - controlsLocation.left}px`;
|
|
55
|
+
this.contextMenu_.style.top = `${e.clientY - controlsLocation.top}px`;
|
|
56
|
+
|
|
57
|
+
shaka.ui.Utils.setDisplay(this.contextMenu_, true);
|
|
58
|
+
} else {
|
|
59
|
+
shaka.ui.Utils.setDisplay(this.contextMenu_, false);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this.eventManager.listen(window, 'click', () => {
|
|
64
|
+
shaka.ui.Utils.setDisplay(this.contextMenu_, false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.createChildren_();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @override */
|
|
71
|
+
release() {
|
|
72
|
+
this.controlsContainer_ = null;
|
|
73
|
+
|
|
74
|
+
for (const element of this.children_) {
|
|
75
|
+
element.release();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.children_ = [];
|
|
79
|
+
super.release();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {string} name
|
|
84
|
+
* @param {!shaka.extern.IUIElement.Factory} factory
|
|
85
|
+
* @export
|
|
86
|
+
*/
|
|
87
|
+
static registerElement(name, factory) {
|
|
88
|
+
shaka.ui.ContextMenu.elementNamesToFactories_.set(name, factory);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
createChildren_() {
|
|
95
|
+
for (const name of this.config_.contextMenuElements) {
|
|
96
|
+
const factory =
|
|
97
|
+
shaka.ui.ContextMenu.elementNamesToFactories_.get(name);
|
|
98
|
+
if (factory) {
|
|
99
|
+
goog.asserts.assert(this.controls, 'Controls should not be null!');
|
|
100
|
+
this.children_.push(factory.create(this.contextMenu_, this.controls));
|
|
101
|
+
} else {
|
|
102
|
+
shaka.log.alwaysWarn('Unrecognized context menu element:', name);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** @private {!Map.<string, !shaka.extern.IUIElement.Factory>} */
|
|
109
|
+
shaka.ui.ContextMenu.elementNamesToFactories_ = new Map();
|
package/ui/controls.js
CHANGED
|
@@ -16,6 +16,7 @@ goog.require('shaka.log');
|
|
|
16
16
|
goog.require('shaka.ui.AdCounter');
|
|
17
17
|
goog.require('shaka.ui.AdPosition');
|
|
18
18
|
goog.require('shaka.ui.BigPlayButton');
|
|
19
|
+
goog.require('shaka.ui.ContextMenu');
|
|
19
20
|
goog.require('shaka.ui.Locales');
|
|
20
21
|
goog.require('shaka.ui.Localization');
|
|
21
22
|
goog.require('shaka.ui.SeekBar');
|
|
@@ -323,6 +324,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
323
324
|
this.playButton_ = null;
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
if (this.contextMenu_) {
|
|
328
|
+
this.contextMenu_ = null;
|
|
329
|
+
}
|
|
330
|
+
|
|
326
331
|
if (this.controlsContainer_) {
|
|
327
332
|
shaka.util.Dom.removeAllChildren(this.controlsContainer_);
|
|
328
333
|
this.releaseChildElements_();
|
|
@@ -650,6 +655,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
650
655
|
this.addPlayButton_();
|
|
651
656
|
}
|
|
652
657
|
|
|
658
|
+
if (this.config_.customContextMenu) {
|
|
659
|
+
this.addContextMenu_();
|
|
660
|
+
}
|
|
661
|
+
|
|
653
662
|
if (!this.spinnerContainer_) {
|
|
654
663
|
this.addBufferingSpinner_();
|
|
655
664
|
}
|
|
@@ -708,6 +717,14 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
708
717
|
this.elements_.push(this.playButton_);
|
|
709
718
|
}
|
|
710
719
|
|
|
720
|
+
/** @private */
|
|
721
|
+
addContextMenu_() {
|
|
722
|
+
/** @private {shaka.ui.ContextMenu} */
|
|
723
|
+
this.contextMenu_ =
|
|
724
|
+
new shaka.ui.ContextMenu(this.controlsButtonPanel_, this);
|
|
725
|
+
this.elements_.push(this.contextMenu_);
|
|
726
|
+
}
|
|
727
|
+
|
|
711
728
|
/** @private */
|
|
712
729
|
addScrimContainer_() {
|
|
713
730
|
// This is the container that gets styled by CSS to have the
|
|
@@ -722,7 +739,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
722
739
|
/** @private {!HTMLElement} */
|
|
723
740
|
this.adPanel_ = shaka.util.Dom.createHTMLElement('div');
|
|
724
741
|
this.adPanel_.classList.add('shaka-ad-controls');
|
|
725
|
-
|
|
742
|
+
const showAdPanel = this.ad_ != null && this.ad_.isLinear();
|
|
743
|
+
shaka.ui.Utils.setDisplay(this.adPanel_, showAdPanel);
|
|
726
744
|
this.bottomControls_.appendChild(this.adPanel_);
|
|
727
745
|
|
|
728
746
|
const adPosition = new shaka.ui.AdPosition(this.adPanel_, this);
|
|
@@ -779,8 +797,12 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
779
797
|
// on the page. The click event listener on window ensures that.
|
|
780
798
|
// However, clicks on the bottom controls don't propagate to the container,
|
|
781
799
|
// so we have to explicitly hide the menus onclick here.
|
|
782
|
-
this.eventManager_.listen(this.bottomControls_, 'click', () => {
|
|
783
|
-
this
|
|
800
|
+
this.eventManager_.listen(this.bottomControls_, 'click', (e) => {
|
|
801
|
+
// We explicitly deny this measure when clicking on buttons that
|
|
802
|
+
// open submenus in the control panel.
|
|
803
|
+
if (!e.target['closest']('.shaka-overflow-button')) {
|
|
804
|
+
this.hideSettingsMenus();
|
|
805
|
+
}
|
|
784
806
|
});
|
|
785
807
|
|
|
786
808
|
this.addAdControls_();
|
|
@@ -790,6 +812,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
790
812
|
this.controlsButtonPanel_.classList.add('shaka-controls-button-panel');
|
|
791
813
|
this.controlsButtonPanel_.classList.add(
|
|
792
814
|
'shaka-show-controls-on-mouse-over');
|
|
815
|
+
if (this.config_.enableTooltips) {
|
|
816
|
+
this.controlsButtonPanel_.classList.add('shaka-tooltips-on');
|
|
817
|
+
}
|
|
793
818
|
this.bottomControls_.appendChild(this.controlsButtonPanel_);
|
|
794
819
|
|
|
795
820
|
// Create the elements specified by controlPanelElements
|
|
@@ -859,7 +884,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
859
884
|
|
|
860
885
|
|
|
861
886
|
/**
|
|
862
|
-
* Adds a container for
|
|
887
|
+
* Adds a container for client side ad UI with IMA SDK.
|
|
863
888
|
*
|
|
864
889
|
* @private
|
|
865
890
|
*/
|
|
@@ -868,6 +893,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
868
893
|
this.clientAdContainer_ = shaka.util.Dom.createHTMLElement('div');
|
|
869
894
|
this.clientAdContainer_.classList.add('shaka-client-side-ad-container');
|
|
870
895
|
shaka.ui.Utils.setDisplay(this.clientAdContainer_, false);
|
|
896
|
+
this.eventManager_.listen(this.clientAdContainer_, 'click', () => {
|
|
897
|
+
this.onContainerClick_();
|
|
898
|
+
});
|
|
871
899
|
this.videoContainer_.appendChild(this.clientAdContainer_);
|
|
872
900
|
}
|
|
873
901
|
|
|
@@ -894,6 +922,12 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
894
922
|
// Listen for click events to dismiss the settings menus.
|
|
895
923
|
this.eventManager_.listen(window, 'click', () => this.hideSettingsMenus());
|
|
896
924
|
|
|
925
|
+
// Avoid having multiple submenus open at the same time.
|
|
926
|
+
this.eventManager_.listen(
|
|
927
|
+
this, 'submenuopen', () => {
|
|
928
|
+
this.hideSettingsMenus();
|
|
929
|
+
});
|
|
930
|
+
|
|
897
931
|
this.eventManager_.listen(this.video_, 'play', () => {
|
|
898
932
|
this.onPlayStateChange_();
|
|
899
933
|
});
|
|
@@ -1100,7 +1134,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
1100
1134
|
// recent mouse movement, we're in keyboard navigation, or one of a special
|
|
1101
1135
|
// class of elements is hovered.
|
|
1102
1136
|
if (adIsPaused ||
|
|
1103
|
-
(!this.ad_ && videoIsPaused) ||
|
|
1137
|
+
((!this.ad_ || !this.ad_.isLinear()) && videoIsPaused) ||
|
|
1104
1138
|
this.recentMouseMovement_ ||
|
|
1105
1139
|
keyboardNavigationMode ||
|
|
1106
1140
|
this.isHovered_()) {
|
|
@@ -1144,14 +1178,14 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
1144
1178
|
|
|
1145
1179
|
if (this.anySettingsMenusAreOpen()) {
|
|
1146
1180
|
this.hideSettingsMenusTimer_.tickNow();
|
|
1147
|
-
} else {
|
|
1181
|
+
} else if (this.config_.singleClickForPlayAndPause) {
|
|
1148
1182
|
this.onPlayPauseClick_();
|
|
1149
1183
|
}
|
|
1150
1184
|
}
|
|
1151
1185
|
|
|
1152
1186
|
/** @private */
|
|
1153
1187
|
onPlayPauseClick_() {
|
|
1154
|
-
if (this.ad_) {
|
|
1188
|
+
if (this.ad_ && this.ad_.isLinear()) {
|
|
1155
1189
|
this.playPauseAd();
|
|
1156
1190
|
} else {
|
|
1157
1191
|
this.playPausePresentation();
|
package/ui/controls.less
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
@import "less/other_elements.less";
|
|
13
13
|
@import "less/overflow_menu.less";
|
|
14
14
|
@import "less/ad_controls.less";
|
|
15
|
+
@import "less/tooltip.less";
|
|
15
16
|
@import (css, inline) "https://fonts.googleapis.com/css?family=Roboto";
|
|
16
17
|
// NOTE: Working around google/material-design-icons#958
|
|
17
18
|
@import (css, inline) "https://fonts.googleapis.com/icon?family=Material+Icons+Round";
|
package/ui/enums.js
CHANGED
|
@@ -16,7 +16,9 @@ goog.provide('shaka.ui.Enums');
|
|
|
16
16
|
shaka.ui.Enums.MaterialDesignIcons = {
|
|
17
17
|
'FULLSCREEN': 'fullscreen',
|
|
18
18
|
'EXIT_FULLSCREEN': 'fullscreen_exit',
|
|
19
|
+
'CLOSE': 'close',
|
|
19
20
|
'CLOSED_CAPTIONS': 'closed_caption',
|
|
21
|
+
'CLOSED_CAPTIONS_OFF': 'closed_caption_disabled',
|
|
20
22
|
'CHECKMARK': 'done',
|
|
21
23
|
'LANGUAGE': 'language',
|
|
22
24
|
'PIP': 'picture_in_picture_alt',
|
|
@@ -40,4 +42,6 @@ shaka.ui.Enums.MaterialDesignIcons = {
|
|
|
40
42
|
'UNLOOP': 'repeat_on',
|
|
41
43
|
'AIRPLAY': 'airplay',
|
|
42
44
|
'REPLAY': 'replay',
|
|
45
|
+
'STATISTICS_ON': 'insert_chart_outlined',
|
|
46
|
+
'STATISTICS_OFF': 'insert_chart',
|
|
43
47
|
};
|
package/ui/externs/ui.js
CHANGED
|
@@ -63,8 +63,14 @@ shaka.extern.UIVolumeBarColors;
|
|
|
63
63
|
* @typedef {{
|
|
64
64
|
* controlPanelElements: !Array.<string>,
|
|
65
65
|
* overflowMenuButtons: !Array.<string>,
|
|
66
|
+
* contextMenuElements: !Array.<string>,
|
|
67
|
+
* statisticsList: !Array.<string>,
|
|
68
|
+
* playbackRates: !Array.<number>,
|
|
69
|
+
* fastForwardRates: !Array.<number>,
|
|
70
|
+
* rewindRates: !Array.<number>,
|
|
66
71
|
* addSeekBar: boolean,
|
|
67
72
|
* addBigPlayButton: boolean,
|
|
73
|
+
* customContextMenu: boolean,
|
|
68
74
|
* castReceiverAppId: string,
|
|
69
75
|
* clearBufferOnQualityChange: boolean,
|
|
70
76
|
* showUnbufferedStart: boolean,
|
|
@@ -73,20 +79,34 @@ shaka.extern.UIVolumeBarColors;
|
|
|
73
79
|
* trackLabelFormat: shaka.ui.Overlay.TrackLabelFormat,
|
|
74
80
|
* fadeDelay: number,
|
|
75
81
|
* doubleClickForFullscreen: boolean,
|
|
82
|
+
* singleClickForPlayAndPause: boolean,
|
|
76
83
|
* enableKeyboardPlaybackControls: boolean,
|
|
77
84
|
* enableFullscreenOnRotation: boolean,
|
|
78
|
-
* forceLandscapeOnFullscreen: boolean
|
|
85
|
+
* forceLandscapeOnFullscreen: boolean,
|
|
86
|
+
* enableTooltips: boolean
|
|
79
87
|
* }}
|
|
80
88
|
*
|
|
81
89
|
* @property {!Array.<string>} controlPanelElements
|
|
82
90
|
* The ordered list of control panel elements of the UI.
|
|
83
91
|
* @property {!Array.<string>} overflowMenuButtons
|
|
84
92
|
* The ordered list of the overflow menu buttons.
|
|
93
|
+
* @property {!Array.<string>} contextMenuElements
|
|
94
|
+
* The ordered list of buttons in the context menu.
|
|
95
|
+
* @property {!Array.<string>} statisticsList
|
|
96
|
+
* The ordered list of statistics present in the statistics container.
|
|
97
|
+
* @property {!Array.<number>} playbackRates
|
|
98
|
+
* The ordered list of rates for playback selection.
|
|
99
|
+
* @property {!Array.<number>} fastForwardRates
|
|
100
|
+
* The ordered list of rates for fast forward selection.
|
|
101
|
+
* @property {!Array.<number>} rewindRates
|
|
102
|
+
* The ordered list of rates for rewind selection.
|
|
85
103
|
* @property {boolean} addSeekBar
|
|
86
104
|
* Whether or not a seek bar should be part of the UI.
|
|
87
105
|
* @property {boolean} addBigPlayButton
|
|
88
106
|
* Whether or not a big play button in the center of the video
|
|
89
107
|
* should be part of the UI.
|
|
108
|
+
* @property {boolean} customContextMenu
|
|
109
|
+
* Whether or not a custom context menu replaces the default.
|
|
90
110
|
* @property {string} castReceiverAppId
|
|
91
111
|
* Receiver app id to use for the Chromecast support.
|
|
92
112
|
* @property {boolean} clearBufferOnQualityChange
|
|
@@ -135,6 +155,9 @@ shaka.extern.UIVolumeBarColors;
|
|
|
135
155
|
* Whether or not double-clicking on the UI should cause it to enter
|
|
136
156
|
* fullscreen.
|
|
137
157
|
* Defaults to true.
|
|
158
|
+
* @property {boolean} singleClickForPlayAndPause
|
|
159
|
+
* Whether or not clicking on the video should cause it to play or pause.
|
|
160
|
+
* Defaults to true.
|
|
138
161
|
* @property {boolean} enableKeyboardPlaybackControls
|
|
139
162
|
* Whether or not playback controls via keyboard is enabled, such as seek
|
|
140
163
|
* forward, seek backward, jump to the beginning/end of the video.
|
|
@@ -147,6 +170,10 @@ shaka.extern.UIVolumeBarColors;
|
|
|
147
170
|
* enters fullscreen. Note that this behavior is based on an experimental
|
|
148
171
|
* browser API, and may not work on all platforms.
|
|
149
172
|
* Defaults to true.
|
|
173
|
+
* @property {boolean} enableTooltips
|
|
174
|
+
* Whether or not buttons in the control panel display tooltips that contain
|
|
175
|
+
* information about their function.
|
|
176
|
+
* Defaults to false.
|
|
150
177
|
* @exportDoc
|
|
151
178
|
*/
|
|
152
179
|
shaka.extern.UIConfiguration;
|