/
platform.js
362 lines (321 loc) · 9.42 KB
/
platform.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.util.Platform');
goog.require('shaka.util.Timer');
/**
* A wrapper for platform-specific functions.
*
* @final
*/
shaka.util.Platform = class {
/**
* Check if the current platform supports media source. We assume that if
* the current platform supports media source, then we can use media source
* as per its design.
*
* @return {boolean}
*/
static supportsMediaSource() {
// Browsers that lack a media source implementation will have no reference
// to |window.MediaSource|. Platforms that we see having problematic media
// source implementations will have this reference removed via a polyfill.
if (!window.MediaSource) {
return false;
}
// Some very old MediaSource implementations didn't have isTypeSupported.
if (!MediaSource.isTypeSupported) {
return false;
}
return true;
}
/**
* Returns true if the media type is supported natively by the platform.
*
* @param {string} mimeType
* @return {boolean}
*/
static supportsMediaType(mimeType) {
const video = shaka.util.Platform.anyMediaElement();
return video.canPlayType(mimeType) != '';
}
/**
* Check if the current platform is MS Edge.
*
* @return {boolean}
*/
static isEdge() {
// Legacy Edge contains "Edge/version".
// Chromium-based Edge contains "Edg/version" (no "e").
if (navigator.userAgent.match(/Edge?\//)) {
return true;
}
return false;
}
/**
* Check if the current platform is Legacy Edge.
*
* @return {boolean}
*/
static isLegacyEdge() {
// Legacy Edge contains "Edge/version".
// Chromium-based Edge contains "Edg/version" (no "e").
if (navigator.userAgent.match(/Edge\//)) {
return true;
}
return false;
}
/**
* Check if the current platform is MS IE.
*
* @return {boolean}
*/
static isIE() {
return shaka.util.Platform.userAgentContains_('Trident/');
}
/**
* Check if the current platform is an Xbox One.
*
* @return {boolean}
*/
static isXboxOne() {
return shaka.util.Platform.userAgentContains_('Xbox One');
}
/**
* Check if the current platform is a Tizen TV.
*
* @return {boolean}
*/
static isTizen() {
return shaka.util.Platform.userAgentContains_('Tizen');
}
/**
* Check if the current platform is a Tizen 4 TV.
*
* @return {boolean}
*/
static isTizen4() {
return shaka.util.Platform.userAgentContains_('Tizen 4');
}
/**
* Check if the current platform is a Tizen 3 TV.
*
* @return {boolean}
*/
static isTizen3() {
return shaka.util.Platform.userAgentContains_('Tizen 3');
}
/**
* Check if the current platform is a Tizen 2 TV.
*
* @return {boolean}
*/
static isTizen2() {
return shaka.util.Platform.userAgentContains_('Tizen 2');
}
/**
* Check if the current platform is a WebOS.
*
* @return {boolean}
*/
static isWebOS() {
return shaka.util.Platform.userAgentContains_('Web0S');
}
/**
* Check if the current platform is a WebOS 3.
*
* @return {boolean}
*/
static isWebOS3() {
// See: http://webostv.developer.lge.com/discover/specifications/web-engine/
return shaka.util.Platform.userAgentContains_('Web0S') &&
shaka.util.Platform.userAgentContains_(
'Chrome/38.0.2125.122 Safari/537.36');
}
/**
* Check if the current platform is a Google Chromecast.
*
* @return {boolean}
*/
static isChromecast() {
return shaka.util.Platform.userAgentContains_('CrKey');
}
/**
* Check if the current platform is Google Chrome.
*
* @return {boolean}
*/
static isChrome() {
// The Edge user agent will also contain the "Chrome" keyword, so we need
// to make sure this is not Edge.
return shaka.util.Platform.userAgentContains_('Chrome') &&
!shaka.util.Platform.isEdge();
}
/**
* Check if the current platform is from Apple.
*
* Returns true on all iOS browsers and on desktop Safari.
*
* Returns false for non-Safari browsers on macOS, which are independent of
* Apple.
*
* @return {boolean}
*/
static isApple() {
return !!navigator.vendor && navigator.vendor.includes('Apple')
&& !shaka.util.Platform.isTizen();
}
/**
* Check if the current platform is Playstation 5.
*
* Returns true on Playstation 5 browsers.
*
* Returns false for Playstation 5 browsers
*
* @return {boolean}
*/
static isPS5() {
return shaka.util.Platform.userAgentContains_('PlayStation 5');
}
/**
* Returns a major version number for Safari, or Safari-based iOS browsers.
*
* For example:
* - Safari 13.0.4 on macOS returns 13.
* - Safari on iOS 13.3.1 returns 13.
* - Chrome on iOS 13.3.1 returns 13 (since this is based on Safari/WebKit).
* - Chrome on macOS returns null (since this is independent of Apple).
*
* Returns null on Firefox on iOS, where this version information is not
* available.
*
* @return {?number} A major version number or null if not iOS.
*/
static safariVersion() {
// All iOS browsers and desktop Safari will return true for isApple().
if (!shaka.util.Platform.isApple()) {
return null;
}
// This works for iOS Safari and desktop Safari, which contain something
// like "Version/13.0" indicating the major Safari or iOS version.
let match = navigator.userAgent.match(/Version\/(\d+)/);
if (match) {
return parseInt(match[1], /* base= */ 10);
}
// This works for all other browsers on iOS, which contain something like
// "OS 13_3" indicating the major & minor iOS version.
match = navigator.userAgent.match(/OS (\d+)(?:_\d+)?/);
if (match) {
return parseInt(match[1], /* base= */ 10);
}
return null;
}
/**
* Check if the current platform is Apple Safari
* or Safari-based iOS browsers.
*
* @return {boolean}
*/
static isSafari() {
return !!shaka.util.Platform.safariVersion();
}
/**
* Guesses if the platform is a mobile one (iOS or Android).
*
* @return {boolean}
*/
static isMobile() {
if (/(?:iPhone|iPad|iPod|Android)/.test(navigator.userAgent)) {
// This is Android, iOS, or iPad < 13.
return true;
}
// Starting with iOS 13 on iPad, the user agent string no longer has the
// word "iPad" in it. It looks very similar to desktop Safari. This seems
// to be intentional on Apple's part.
// See: https://forums.developer.apple.com/thread/119186
//
// So if it's an Apple device with multi-touch support, assume it's a mobile
// device. If some future iOS version starts masking their user agent on
// both iPhone & iPad, this clause should still work. If a future
// multi-touch desktop Mac is released, this will need some adjustment.
//
// As of January 2020, this is mainly used to adjust the default UI config
// for mobile devices, so it's low risk if something changes to break this
// detection.
return shaka.util.Platform.isApple() && navigator.maxTouchPoints > 1;
}
/**
* Check if the user agent contains a key. This is the best way we know of
* right now to detect platforms. If there is a better way, please send a
* PR.
*
* @param {string} key
* @return {boolean}
* @private
*/
static userAgentContains_(key) {
const userAgent = navigator.userAgent || '';
return userAgent.includes(key);
}
/**
* For canPlayType queries, we just need any instance.
*
* First, use a cached element from a previous query.
* Second, search the page for one.
* Third, create a temporary one.
*
* Cached elements expire in one second so that they can be GC'd or removed.
*
* @return {!HTMLMediaElement}
*/
static anyMediaElement() {
const Platform = shaka.util.Platform;
if (Platform.cachedMediaElement_) {
return Platform.cachedMediaElement_;
}
if (!Platform.cacheExpirationTimer_) {
Platform.cacheExpirationTimer_ = new shaka.util.Timer(() => {
Platform.cachedMediaElement_ = null;
});
}
Platform.cachedMediaElement_ = /** @type {HTMLMediaElement} */(
document.getElementsByTagName('video')[0] ||
document.getElementsByTagName('audio')[0]);
if (!Platform.cachedMediaElement_) {
Platform.cachedMediaElement_ = /** @type {!HTMLMediaElement} */(
document.createElement('video'));
}
Platform.cacheExpirationTimer_.tickAfter(/* seconds= */ 1);
return Platform.cachedMediaElement_;
}
/**
* Returns true if the platform requires encryption information in all init
* segments. For such platforms, MediaSourceEngine will attempt to work
* around a lack of such info by inserting fake encryption information into
* initialization segments.
*
* @return {boolean}
* @see https://github.com/google/shaka-player/issues/2759
*/
static requiresEncryptionInfoInAllInitSegments() {
const Platform = shaka.util.Platform;
return Platform.isTizen() || Platform.isXboxOne();
}
/**
* Returns true if MediaKeys is polyfilled
*
* @return {boolean}
*/
static isMediaKeysPolyfilled() {
if (window.shakaMediaKeysPolyfill) {
return true;
}
return false;
}
};
/** @private {shaka.util.Timer} */
shaka.util.Platform.cacheExpirationTimer_ = null;
/** @private {HTMLMediaElement} */
shaka.util.Platform.cachedMediaElement_ = null;