Files
shaka-player/lib/util/platform.js
T
Joey Parrish 30c2cb5ec5 test: Fix platform detection for Xbox (#4685)
Xbox One was being misdetected as Chrome, causing some mistakes in which
tests are skipped.

This does not appear to affect the library itself, since isChrome() and
chromeVersion() are only used in tests currently.
2022-11-10 12:27:51 -08:00

424 lines
11 KiB
JavaScript

/*! @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');
}
/**
* Returns a major version number for Chrome, or Chromium-based browsers.
*
* For example:
* - Chrome 106.0.5249.61 returns 106.
* - Edge 106.0.1370.34 returns 106 (since this is based on Chromium).
* - Safari returns null (since this is independent of Chromium).
*
* @return {?number} A major version number or null if not Chromium-based.
*/
static chromeVersion() {
if (!shaka.util.Platform.isChrome()) {
return null;
}
// Looking for something like "Chrome/106.0.0.0".
const match = navigator.userAgent.match(/Chrome\/(\d+)/);
if (match) {
return parseInt(match[1], /* base= */ 10);
}
return null;
}
/**
* 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() &&
!shaka.util.Platform.isEOS() &&
!shaka.util.Platform.isVirginMedia() &&
!shaka.util.Platform.isPS4() &&
!shaka.util.Platform.isAmazonFireTV();
}
/**
* 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');
}
/**
* Check if the current platform is Playstation 4.
*/
static isPS4() {
return shaka.util.Platform.userAgentContains_('PlayStation 4');
}
/**
* Check if the current platform is Virgin Media device.
*/
static isVirginMedia() {
return shaka.util.Platform.userAgentContains_('VirginMedia');
}
/**
* Check if the current platform is Amazon Fire TV.
* https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html
*
* @return {boolean}
*/
static isAmazonFireTV() {
return shaka.util.Platform.userAgentContains_('AFT');
}
/**
* 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();
}
/**
* Check if the current platform is an EOS set-top box.
*
* @return {boolean}
*/
static isEOS() {
return shaka.util.Platform.userAgentContains_('PC=EOS');
}
/**
* 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/shaka-project/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;