mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
feat: Add Device API (#8210)
The goal is to simplify and abstract feature logic detection. Currently lots of places depend on various calls to `shaka.util.Platform` and mainteinance of this is hard & not easy to read. By introducing device API ideally rest of the player logic would look into device features instead of directly checking platform. Additionally we can more easily cache needed values, so we won't have to parse user agent several times anymore. --------- Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
This commit is contained in:
committed by
GitHub
parent
9ac3f51473
commit
970d7756ea
@@ -12,4 +12,5 @@
|
||||
+@text
|
||||
+@optionalText
|
||||
+@transmuxer
|
||||
+@devices
|
||||
+@ui
|
||||
|
||||
+6
-1
@@ -20,6 +20,12 @@
|
||||
+../../lib/deprecate/enforcer.js
|
||||
+../../lib/deprecate/version.js
|
||||
|
||||
+../../lib/device/abstract_device.js
|
||||
+../../lib/device/apple_browser.js
|
||||
+../../lib/device/default_browser.js
|
||||
+../../lib/device/device_factory.js
|
||||
+../../lib/device/i_device.js
|
||||
|
||||
+../../lib/drm/drm_engine.js
|
||||
+../../lib/drm/drm_utils.js
|
||||
+../../lib/drm/fairplay.js
|
||||
@@ -113,7 +119,6 @@
|
||||
+../../lib/util/object_utils.js
|
||||
+../../lib/util/operation_manager.js
|
||||
+../../lib/util/periods.js
|
||||
+../../lib/util/platform.js
|
||||
+../../lib/util/player_configuration.js
|
||||
+../../lib/util/pssh.js
|
||||
+../../lib/util/public_promise.js
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Device
|
||||
|
||||
+../../lib/device/chromecast.js
|
||||
+../../lib/device/hisense.js
|
||||
+../../lib/device/playstation.js
|
||||
+../../lib/device/tizen.js
|
||||
+../../lib/device/vizio.js
|
||||
+../../lib/device/webkit_stb.js
|
||||
+../../lib/device/webos.js
|
||||
+../../lib/device/xbox.js
|
||||
@@ -61,3 +61,10 @@ chrome.webview.hostObjects.sync = {};
|
||||
|
||||
/** @const */
|
||||
chrome.webview.hostObjects.sync.Windows = Windows;
|
||||
|
||||
/**
|
||||
* Typedef for the module interface.
|
||||
*
|
||||
* @typedef {typeof Windows}
|
||||
*/
|
||||
var WinRT;
|
||||
|
||||
@@ -12,6 +12,8 @@ goog.require('shaka.Player');
|
||||
goog.require('shaka.ads.InterstitialAd');
|
||||
goog.require('shaka.ads.InterstitialStaticAd');
|
||||
goog.require('shaka.ads.Utils');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.media.PreloadManager');
|
||||
goog.require('shaka.net.NetworkingEngine');
|
||||
@@ -21,7 +23,6 @@ goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.EventManager');
|
||||
goog.require('shaka.util.FakeEvent');
|
||||
goog.require('shaka.util.IReleasable');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.PublicPromise');
|
||||
goog.require('shaka.util.StringUtils');
|
||||
goog.require('shaka.util.Timer');
|
||||
@@ -251,8 +252,7 @@ shaka.ads.InterstitialAdManager = class {
|
||||
this.baseVideo_, 'seeked', this.onSeeked_);
|
||||
this.eventManager_.listen(
|
||||
this.baseVideo_, 'ended', this.checkForInterstitials_);
|
||||
if ('requestVideoFrameCallback' in this.baseVideo_ &&
|
||||
!shaka.util.Platform.isSmartTV()) {
|
||||
if ('requestVideoFrameCallback' in this.baseVideo_ && !this.isSmartTV_()) {
|
||||
const baseVideo = /** @type {!HTMLVideoElement} */ (this.baseVideo_);
|
||||
const videoFrameCallback = (now, metadata) => {
|
||||
if (this.videoCallbackId_ == -1) {
|
||||
@@ -1080,7 +1080,7 @@ shaka.ads.InterstitialAdManager = class {
|
||||
const detachBasePlayerPromise = new shaka.util.PublicPromise();
|
||||
const checkState = async (e) => {
|
||||
if (e['state'] == 'detach') {
|
||||
if (shaka.util.Platform.isSmartTV()) {
|
||||
if (this.isSmartTV_()) {
|
||||
await new Promise(
|
||||
(resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
|
||||
}
|
||||
@@ -1747,6 +1747,20 @@ shaka.ads.InterstitialAdManager = class {
|
||||
return Array.from(this.interstitials_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
isSmartTV_() {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const deviceType = device.getDeviceType();
|
||||
if (deviceType == shaka.device.IDevice.DeviceType.TV ||
|
||||
deviceType == shaka.device.IDevice.DeviceType.CONSOLE ||
|
||||
deviceType == shaka.device.IDevice.DeviceType.CAST) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!shaka.extern.AdInterstitial} interstitial
|
||||
|
||||
@@ -9,13 +9,14 @@ goog.provide('shaka.cast.CastReceiver');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.Player');
|
||||
goog.require('shaka.cast.CastUtils');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.EventManager');
|
||||
goog.require('shaka.util.FakeEvent');
|
||||
goog.require('shaka.util.FakeEventTarget');
|
||||
goog.require('shaka.util.IDestroyable');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Timer');
|
||||
|
||||
|
||||
@@ -271,7 +272,9 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
|
||||
// browser is a Chromecast before starting the receiver manager. We
|
||||
// wouldn't do browser detection except for debugging, so only do this in
|
||||
// uncompiled mode.
|
||||
if (shaka.util.Platform.isChromecast()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const deviceType = device.getDeviceType();
|
||||
if (deviceType === shaka.device.IDevice.DeviceType.CAST) {
|
||||
manager.start();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.AbstractDevice');
|
||||
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.util.Dom');
|
||||
goog.require('shaka.util.Lazy');
|
||||
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
* @implements {shaka.device.IDevice}
|
||||
*/
|
||||
shaka.device.AbstractDevice = class {
|
||||
constructor() {
|
||||
/** @private {!shaka.util.Lazy<!shaka.device.IDevice.DeviceType>} */
|
||||
this.abstractDeviceType_ = new shaka.util.Lazy(() => {
|
||||
if (navigator.userAgent.match(/Smart( ?|_)TV/i) ||
|
||||
navigator.userAgent.match(/Android ?TV/i)) {
|
||||
return shaka.device.IDevice.DeviceType.TV;
|
||||
}
|
||||
if (navigator.userAgentData) {
|
||||
if (navigator.userAgentData.mobile) {
|
||||
return shaka.device.IDevice.DeviceType.MOBILE;
|
||||
} else {
|
||||
return shaka.device.IDevice.DeviceType.DESKTOP;
|
||||
}
|
||||
}
|
||||
if (/(?:iPhone|iPad|iPod)/.test(navigator.userAgent)) {
|
||||
return shaka.device.IDevice.DeviceType.MOBILE;
|
||||
}
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform) {
|
||||
if (navigator.userAgentData.platform.toLowerCase() == 'android') {
|
||||
return shaka.device.IDevice.DeviceType.MOBILE;
|
||||
} else {
|
||||
return shaka.device.IDevice.DeviceType.DESKTOP;
|
||||
}
|
||||
}
|
||||
if (navigator.userAgent.includes('Android')) {
|
||||
return shaka.device.IDevice.DeviceType.MOBILE;
|
||||
}
|
||||
return shaka.device.IDevice.DeviceType.DESKTOP;
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<shaka.device.IDevice.BrowserEngine>} */
|
||||
this.browserEngine_ = new shaka.util.Lazy(() => {
|
||||
if (navigator.vendor.includes('Apple') &&
|
||||
(navigator.userAgent.includes('Version/') ||
|
||||
navigator.userAgent.includes('OS/'))) {
|
||||
return shaka.device.IDevice.BrowserEngine.WEBKIT;
|
||||
}
|
||||
if (navigator.userAgent.includes('Edge/')) {
|
||||
return shaka.device.IDevice.BrowserEngine.EDGE;
|
||||
}
|
||||
if (navigator.userAgent.includes('Chrome/')) {
|
||||
return shaka.device.IDevice.BrowserEngine.CHROMIUM;
|
||||
}
|
||||
if (navigator.userAgent.includes('Firefox/')) {
|
||||
return shaka.device.IDevice.BrowserEngine.GECKO;
|
||||
}
|
||||
return shaka.device.IDevice.BrowserEngine.UNKNOWN;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaSource() {
|
||||
const mediaSource = window.ManagedMediaSource || window.MediaSource;
|
||||
// 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 (!mediaSource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some very old MediaSource implementations didn't have isTypeSupported.
|
||||
if (!mediaSource.isTypeSupported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaType(mimeType) {
|
||||
const video = shaka.util.Dom.anyMediaElement();
|
||||
return video.canPlayType(mimeType) != '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return !!navigator.mediaCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return this.abstractDeviceType_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return this.browserEngine_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresEncryptionInfoInAllInitSegments(keySystem, contentType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresClearAndEncryptedInitSegments() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
insertEncryptionDataBeforeClear() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresTfhdFix(contentType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresEC3InitSegments() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSequenceMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSmoothCodecSwitching() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsServerCertificate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
seekDelay() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
detectMaxHardwareResolution() {
|
||||
return Promise.resolve({width: Infinity, height: Infinity});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
shouldOverrideDolbyVisionCodecs() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
shouldAvoidUseTextDecoderEncoder() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
const deviceType = this.getDeviceType();
|
||||
if (deviceType === shaka.device.IDevice.DeviceType.TV ||
|
||||
deviceType === shaka.device.IDevice.DeviceType.CONSOLE ||
|
||||
deviceType === shaka.device.IDevice.DeviceType.CAST) {
|
||||
config.ads.customPlayheadTracker = true;
|
||||
config.ads.skipPlayDetection = true;
|
||||
config.ads.supportsMultipleMediaElements = false;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsOfflineStorage() {
|
||||
return !!window.indexedDB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
rejectCodecs() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getHdrLevel(preferHLG) {
|
||||
if (window.matchMedia !== undefined &&
|
||||
window.matchMedia('(color-gamut: p3)').matches) {
|
||||
return preferHLG ? 'HLG' : 'PQ';
|
||||
}
|
||||
return 'SDR';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsAirPlay() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
misreportAC3UsingDrm() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
returnLittleEndianUsingPlayReady() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsEncryptionSchemePolyfill() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
misreportsSupportForPersistentLicenses() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportStandardVP9Checking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
createMediaKeysWhenCheckingSupport() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
disableHEVCSupport() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
toString() {
|
||||
return `Device: ${this.getDeviceName()} v${this.getVersion()}; ` +
|
||||
`Type: ${this.getDeviceType()}`;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.AppleBrowser');
|
||||
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.util.Lazy');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.AppleBrowser = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @private {!shaka.util.Lazy<?number>} */
|
||||
this.version_ = new shaka.util.Lazy(() => {
|
||||
// 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;
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<!shaka.device.IDevice.DeviceType>} */
|
||||
this.deviceType_ = new shaka.util.Lazy(() => {
|
||||
if (/(?:iPhone|iPad|iPod)/.test(navigator.userAgent) ||
|
||||
navigator.maxTouchPoints > 1) {
|
||||
return shaka.device.IDevice.DeviceType.MOBILE;
|
||||
}
|
||||
if ('xr' in navigator) {
|
||||
return shaka.device.IDevice.DeviceType.VR;
|
||||
}
|
||||
return shaka.device.IDevice.DeviceType.DESKTOP;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.version_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'Apple Browser';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return this.deviceType_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return shaka.device.IDevice.BrowserEngine.WEBKIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresEncryptionInfoInAllInitSegments(keySystem, contentType) {
|
||||
return contentType === 'audio';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
insertEncryptionDataBeforeClear() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresTfhdFix(contentType) {
|
||||
return contentType === 'audio';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
config.abr.minTimeToSwitch = 0.5;
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsAirPlay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isAppleBrowser_() {
|
||||
if (!(navigator.vendor || '').includes('Apple')) {
|
||||
return false;
|
||||
}
|
||||
if (/(?:iPhone|iPad|iPod)/.test(navigator.userAgent) ||
|
||||
navigator.maxTouchPoints > 1) {
|
||||
return true;
|
||||
}
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform &&
|
||||
navigator.userAgentData.platform.toLowerCase() == 'macos') {
|
||||
return true;
|
||||
} else if (navigator.platform &&
|
||||
navigator.platform.toLowerCase().includes('mac')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.AppleBrowser.isAppleBrowser_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.AppleBrowser());
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.Chromecast');
|
||||
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.util.Lazy');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.Chromecast = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
if (!shaka.device.Chromecast.isChromecast_()) {
|
||||
throw new Error('Not a Chromecast device!');
|
||||
}
|
||||
super();
|
||||
|
||||
/** @private {!shaka.util.Lazy<?number>} */
|
||||
this.version_ = new shaka.util.Lazy(() => {
|
||||
// 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;
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<shaka.device.Chromecast.OsType_>} */
|
||||
this.osType_ = new shaka.util.Lazy(() => {
|
||||
let fieldToCheck = (navigator.userAgentData &&
|
||||
navigator.userAgentData.platform) || navigator.userAgent;
|
||||
fieldToCheck = fieldToCheck.toLowerCase();
|
||||
if (fieldToCheck.includes('android')) {
|
||||
return shaka.device.Chromecast.OsType_.ANDROID;
|
||||
} else if (fieldToCheck.includes('fuchsia')) {
|
||||
return shaka.device.Chromecast.OsType_.FUCHSIA;
|
||||
} else {
|
||||
return shaka.device.Chromecast.OsType_.LINUX;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.version_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'Chromecast with ' + this.osType_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.CAST;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return shaka.device.IDevice.BrowserEngine.CHROMIUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return super.supportsMediaCapabilities() &&
|
||||
this.osType_.value() !== shaka.device.Chromecast.OsType_.LINUX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSmoothCodecSwitching() {
|
||||
return this.osType_.value() !== shaka.device.Chromecast.OsType_.LINUX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
seekDelay() {
|
||||
switch (this.osType_.value()) {
|
||||
case shaka.device.Chromecast.OsType_.ANDROID:
|
||||
return 0;
|
||||
case shaka.device.Chromecast.OsType_.FUCHSIA:
|
||||
return 3;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async detectMaxHardwareResolution() {
|
||||
// In our tests, the original Chromecast seems to have trouble decoding
|
||||
// above 1080p. It would be a waste to select a higher res anyway, given
|
||||
// that the device only outputs 1080p to begin with.
|
||||
// Chromecast has an extension to query the device/display's resolution.
|
||||
const hasCanDisplayType = window.cast && cast.__platform__ &&
|
||||
cast.__platform__.canDisplayType;
|
||||
|
||||
// Some hub devices can only do 720p. Default to that.
|
||||
const maxResolution = {width: 1280, height: 720};
|
||||
|
||||
try {
|
||||
if (hasCanDisplayType && await cast.__platform__.canDisplayType(
|
||||
'video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
|
||||
// The device and display can both do 4k. Assume a 4k limit.
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
} else if (hasCanDisplayType && await cast.__platform__.canDisplayType(
|
||||
'video/mp4; codecs="avc1.640028"; width=1920; height=1080')) {
|
||||
// Most Chromecasts can do 1080p.
|
||||
maxResolution.width = 1920;
|
||||
maxResolution.height = 1080;
|
||||
}
|
||||
} catch (error) {
|
||||
// This shouldn't generally happen. Log the error.
|
||||
shaka.log.alwaysError('Failed to check canDisplayType:', error);
|
||||
// Now ignore the error and let the 720p default stand.
|
||||
}
|
||||
|
||||
return maxResolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
// Chromecast has long hardware pipeline that respond slowly to seeking.
|
||||
// Therefore we should not seek when we detect a stall on this platform.
|
||||
// Instead, default stallSkip to 0 to force the stall detector to pause
|
||||
// and play instead.
|
||||
config.streaming.stallSkip = 0;
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsOfflineStorage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Vizio TV.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isChromecast_() {
|
||||
return navigator.userAgent.includes('CrKey') &&
|
||||
!navigator.userAgent.includes('VIZIO SmartCast');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @enum {string}
|
||||
*/
|
||||
shaka.device.Chromecast.OsType_ = {
|
||||
ANDROID: 'Android',
|
||||
FUCHSIA: 'Fuchsia',
|
||||
LINUX: 'Linux',
|
||||
};
|
||||
|
||||
if (shaka.device.Chromecast.isChromecast_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.Chromecast());
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.DefaultBrowser');
|
||||
|
||||
goog.require('shaka.debug.RunningInLab');
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.drm.DrmUtils');
|
||||
goog.require('shaka.util.Lazy');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.DefaultBrowser = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @private {!shaka.util.Lazy<?number>} */
|
||||
this.version_ = new shaka.util.Lazy(() => {
|
||||
// Looking for something like "Chrome/106.0.0.0" or Firefox/135.0
|
||||
const match = navigator.userAgent.match(/(Chrome|Firefox)\/(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[2], /* base= */ 10);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<string>} */
|
||||
this.deviceName_ = new shaka.util.Lazy(() => {
|
||||
// Legacy Edge contains "Edge/version".
|
||||
// Chromium-based Edge contains "Edg/version" (no "e").
|
||||
if (navigator.userAgent.match(/Edge?\//)) {
|
||||
return 'Edge';
|
||||
} else if (navigator.userAgent.includes('Chrome')) {
|
||||
return 'Chrome';
|
||||
} else if (navigator.userAgent.includes('Firefox')) {
|
||||
return 'Firefox';
|
||||
}
|
||||
return 'Unknown';
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<boolean>} */
|
||||
this.isWindows_ = new shaka.util.Lazy(() => {
|
||||
// Try the newer standard first.
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform) {
|
||||
return navigator.userAgentData.platform.toLowerCase() == 'windows';
|
||||
}
|
||||
// Fall back to the old API, with less strict matching.
|
||||
if (!navigator.platform) {
|
||||
return false;
|
||||
}
|
||||
return navigator.platform.toLowerCase().includes('win32');
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<boolean>} */
|
||||
this.supportsSmoothCodecSwitching_ = new shaka.util.Lazy(() => {
|
||||
if (!navigator.userAgent.match(/Edge?\//)) {
|
||||
return true;
|
||||
}
|
||||
return !this.isWindows_.value();
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<boolean>} */
|
||||
this.isSonyTV_ = new shaka.util.Lazy(() => {
|
||||
return navigator.userAgent.includes('sony.hbbtv.tv.G5');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.version_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return this.deviceName_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresEncryptionInfoInAllInitSegments(keySystem) {
|
||||
if (shaka.drm.DrmUtils.isPlayReadyKeySystem(keySystem)) {
|
||||
return this.deviceName_.value() === 'Edge' && this.isWindows_.value();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresClearAndEncryptedInitSegments() {
|
||||
return this.deviceName_.value() === 'Edge' && this.isWindows_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
insertEncryptionDataBeforeClear() {
|
||||
return this.deviceName_.value() === 'Edge' && this.isWindows_.value(); ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSmoothCodecSwitching() {
|
||||
return this.supportsSmoothCodecSwitching_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
|
||||
// Other browsers different than Edge only supports HW PlayReady with the
|
||||
// recommendation keysystem on Windows, so we do a direct mapping here.
|
||||
if (this.isWindows_.value() && this.deviceName_.value() !== 'Edge') {
|
||||
config.drm.keySystemsMapping = {
|
||||
'com.microsoft.playready':
|
||||
'com.microsoft.playready.recommendation',
|
||||
};
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
returnLittleEndianUsingPlayReady() {
|
||||
return this.deviceName_.value() === 'Edge' || this.isSonyTV_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
createMediaKeysWhenCheckingSupport() {
|
||||
if (goog.DEBUG && shaka.debug.RunningInLab && this.isWindows_.value() &&
|
||||
this.getBrowserEngine() === shaka.device.IDevice.BrowserEngine.GECKO) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
disableHEVCSupport() {
|
||||
// It seems that HEVC on Firefox Windows is incomplete.
|
||||
return this.isWindows_.value() &&
|
||||
this.getBrowserEngine() === shaka.device.IDevice.BrowserEngine.GECKO;
|
||||
}
|
||||
};
|
||||
|
||||
shaka.device.DeviceFactory.registerDefaultDeviceFactory(
|
||||
() => new shaka.device.DefaultBrowser());
|
||||
@@ -0,0 +1,64 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.DeviceFactory');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.util.Lazy');
|
||||
goog.requireType('shaka.device.IDevice');
|
||||
|
||||
|
||||
shaka.device.DeviceFactory = class {
|
||||
/**
|
||||
* @param {?function(): shaka.device.IDevice} deviceFactory
|
||||
*/
|
||||
static registerDeviceFactory(deviceFactory) {
|
||||
goog.asserts.assert(!shaka.device.DeviceFactory.factory_,
|
||||
'Device Factory should NOT be defined');
|
||||
shaka.device.DeviceFactory.factory_ = deviceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?function(): shaka.device.IDevice} deviceFactory
|
||||
*/
|
||||
static registerDefaultDeviceFactory(deviceFactory) {
|
||||
goog.asserts.assert(!shaka.device.DeviceFactory.factory_,
|
||||
'Default device Factory should NOT be defined');
|
||||
shaka.device.DeviceFactory.defaultFactory_ = deviceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {shaka.device.IDevice}
|
||||
*/
|
||||
static getDevice() {
|
||||
goog.asserts.assert(shaka.device.DeviceFactory.factory_ ||
|
||||
shaka.device.DeviceFactory.defaultFactory_,
|
||||
'Device Factory should be defined');
|
||||
return shaka.device.DeviceFactory.device_.value();
|
||||
}
|
||||
};
|
||||
|
||||
/** @private {?function(): shaka.device.IDevice} */
|
||||
shaka.device.DeviceFactory.factory_ = null;
|
||||
|
||||
/** @private {?function(): shaka.device.IDevice} */
|
||||
shaka.device.DeviceFactory.defaultFactory_ = null;
|
||||
|
||||
/** @private {!shaka.util.Lazy<shaka.device.IDevice>} */
|
||||
shaka.device.DeviceFactory.device_ = new shaka.util.Lazy(() => {
|
||||
let device = undefined;
|
||||
if (shaka.device.DeviceFactory.factory_) {
|
||||
device = shaka.device.DeviceFactory.factory_();
|
||||
}
|
||||
if (!device && shaka.device.DeviceFactory.defaultFactory_) {
|
||||
device = shaka.device.DeviceFactory.defaultFactory_();
|
||||
}
|
||||
if (device) {
|
||||
shaka.log.info(device.toString());
|
||||
}
|
||||
return device;
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.Hisense');
|
||||
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.Hisense = class extends shaka.device.AbstractDevice {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'Hisense';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.TV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
detectMaxHardwareResolution() {
|
||||
const maxResolution = {width: 1920, height: 1080};
|
||||
let supports4k = null;
|
||||
if (window.Hisense_Get4KSupportState) {
|
||||
try {
|
||||
// eslint-disable-next-line new-cap
|
||||
supports4k = window.Hisense_Get4KSupportState();
|
||||
} catch (e) {
|
||||
shaka.log.debug('Hisense: Failed to get 4K support state', e);
|
||||
}
|
||||
}
|
||||
if (supports4k == null) {
|
||||
// If API is not there or not working for whatever reason, fallback to
|
||||
// user agent check, as it contains UHD or FHD info.
|
||||
supports4k = navigator.userAgent.includes('UHD');
|
||||
}
|
||||
if (supports4k) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
}
|
||||
|
||||
return Promise.resolve(maxResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
// Hisense has long hardware pipeline that respond slowly to seeking.
|
||||
// Therefore we should not seek when we detect a stall on this platform.
|
||||
// Instead, default stallSkip to 0 to force the stall detector to pause
|
||||
// and play instead.
|
||||
config.streaming.stallSkip = 0;
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isHisense_() {
|
||||
return navigator.userAgent.includes('Hisense') ||
|
||||
navigator.userAgent.includes('VIDAA');
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.Hisense.isHisense_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.Hisense());
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.IDevice');
|
||||
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
shaka.device.IDevice = 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}
|
||||
*/
|
||||
supportsMediaSource() {}
|
||||
|
||||
/**
|
||||
* Returns true if the media type is supported natively by the platform.
|
||||
*
|
||||
* @param {string} mimeType
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsMediaType(mimeType) {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsMediaCapabilities() {}
|
||||
|
||||
/**
|
||||
* Version of device or null if unknown.
|
||||
* @return {?number}
|
||||
*/
|
||||
getVersion() {}
|
||||
|
||||
/**
|
||||
* Friendly device name.
|
||||
* @return {string}
|
||||
*/
|
||||
getDeviceName() {}
|
||||
|
||||
/**
|
||||
* @return {!shaka.device.IDevice.DeviceType}
|
||||
*/
|
||||
getDeviceType() {}
|
||||
|
||||
/**
|
||||
* @return {!shaka.device.IDevice.BrowserEngine}
|
||||
*/
|
||||
getBrowserEngine() {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {?string} keySystem
|
||||
* @param {?string} contentType
|
||||
* @return {boolean}
|
||||
* @see https://github.com/shaka-project/shaka-player/issues/2759
|
||||
*/
|
||||
requiresEncryptionInfoInAllInitSegments(keySystem, contentType) {}
|
||||
|
||||
/**
|
||||
* Returns true if the platform requires both clear & encryption information
|
||||
* in clear init segments. For such platforms, MediaSourceEngine will attempt
|
||||
* to work around a lack of such info by inserting fake information into
|
||||
* initialization segments. It is called only when
|
||||
* <code>requiresEncryptionInfoInAllInitSegments()</code> is also true
|
||||
* and works as the extension of it.
|
||||
*
|
||||
* @return {boolean}
|
||||
* @see https://github.com/shaka-project/shaka-player/pull/6719
|
||||
*/
|
||||
requiresClearAndEncryptedInitSegments() {}
|
||||
|
||||
/**
|
||||
* Indicates should the encryption data be inserted before or after
|
||||
* the clear data in the init segment.
|
||||
* @return {boolean}
|
||||
*/
|
||||
insertEncryptionDataBeforeClear() {}
|
||||
|
||||
/**
|
||||
* @param {string} contentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
requiresTfhdFix(contentType) {}
|
||||
|
||||
/**
|
||||
* Returns true if the platform requires AC-3 signalling in init
|
||||
* segments to be replaced with EC-3 signalling.
|
||||
* For such platforms, MediaSourceEngine will attempt to work
|
||||
* around it by inserting fake EC-3 signalling into
|
||||
* initialization segments.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
requiresEC3InitSegments() {}
|
||||
|
||||
/**
|
||||
* Returns true if the platform supports SourceBuffer "sequence mode".
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsSequenceMode() {}
|
||||
|
||||
/**
|
||||
* Returns if codec switching SMOOTH is known reliable device support.
|
||||
*
|
||||
* Some devices are known not to support <code>SourceBuffer.changeType</code>
|
||||
* well. These devices should use the reload strategy. If a device
|
||||
* reports that it supports <code<changeType</code> but supports it unreliably
|
||||
* it should be disallowed in this method.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsSmoothCodecSwitching() {}
|
||||
|
||||
/**
|
||||
* On some platforms, the act of seeking can take a significant amount
|
||||
* of time, so we need to delay a seek.
|
||||
* @return {number}
|
||||
*/
|
||||
seekDelay() {}
|
||||
|
||||
/**
|
||||
* Detect the maximum resolution that the platform's hardware can handle.
|
||||
*
|
||||
* @return {!Promise<shaka.extern.Resolution>}
|
||||
*/
|
||||
detectMaxHardwareResolution() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsServerCertificate() {}
|
||||
|
||||
/**
|
||||
* Adjusts player configuration with device specific tweaks. Changes are done
|
||||
* in-place and the same object is returned.
|
||||
* @param {shaka.extern.PlayerConfiguration} config
|
||||
* @return {shaka.extern.PlayerConfiguration}
|
||||
*/
|
||||
adjustConfig(config) {}
|
||||
|
||||
/**
|
||||
* Checks should Dolby Vision codecs be overridden to their H.264 and H.265
|
||||
* equivalents.
|
||||
* @return {boolean}
|
||||
*/
|
||||
shouldOverrideDolbyVisionCodecs() {}
|
||||
|
||||
/**
|
||||
* Indicates whether or not to use window.TextDecoder and window.TextEncoder
|
||||
* even if they are available
|
||||
* @return {boolean}
|
||||
*/
|
||||
shouldAvoidUseTextDecoderEncoder() {}
|
||||
|
||||
/**
|
||||
* Checks does the platform supports offline storage by IDB.
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsOfflineStorage() {}
|
||||
|
||||
/**
|
||||
* Lists all codecs that should be rejected by MediaSource.
|
||||
* @return {!Array<string>}
|
||||
*/
|
||||
rejectCodecs() {}
|
||||
|
||||
/**
|
||||
* Check the current HDR level supported by the screen.
|
||||
*
|
||||
* @param {boolean} preferHLG
|
||||
* @return {string}
|
||||
*/
|
||||
getHdrLevel(preferHLG) {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsAirPlay() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
misreportAC3UsingDrm() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
returnLittleEndianUsingPlayReady() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportsEncryptionSchemePolyfill() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
misreportsSupportForPersistentLicenses() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
supportStandardVP9Checking() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
createMediaKeysWhenCheckingSupport() {}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
disableHEVCSupport() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum {string}
|
||||
*/
|
||||
shaka.device.IDevice.DeviceType = {
|
||||
'DESKTOP': 'DESKTOP',
|
||||
'MOBILE': 'MOBILE',
|
||||
'TV': 'TV',
|
||||
'VR': 'VR',
|
||||
'CONSOLE': 'CONSOLE',
|
||||
'CAST': 'CAST',
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum {string}
|
||||
*/
|
||||
shaka.device.IDevice.BrowserEngine = {
|
||||
'CHROMIUM': 'CHROMIUM',
|
||||
'EDGE': 'EDGE',
|
||||
'GECKO': 'GECKO',
|
||||
'WEBKIT': 'WEBKIT',
|
||||
'UNKNOWN': 'UNKNOWN',
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.PlayStation');
|
||||
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.util.Lazy');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.PlayStation = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @private {!shaka.util.Lazy<?number>} */
|
||||
this.version_ = new shaka.util.Lazy(() => {
|
||||
const match = navigator.userAgent.match(/PlayStation (\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1], 10);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'PlayStation';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.CONSOLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return shaka.device.IDevice.BrowserEngine.WEBKIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.version_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSequenceMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSmoothCodecSwitching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
shouldAvoidUseTextDecoderEncoder() {
|
||||
return this.getVersion() === 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async detectMaxHardwareResolution() {
|
||||
const maxResolution = {width: 1920, height: 1080};
|
||||
let supports4K = false;
|
||||
try {
|
||||
const result = await window.msdk.device.getDisplayInfo();
|
||||
supports4K = result.resolution === '4K';
|
||||
} catch (e) {
|
||||
try {
|
||||
const result = await window.msdk.device.getDisplayInfoImmediate();
|
||||
supports4K = result.resolution === '4K';
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn(
|
||||
'PlayStation: Failed to get the display info:', e);
|
||||
}
|
||||
}
|
||||
if (supports4K) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
}
|
||||
|
||||
return maxResolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
// The PS4 only supports the Playready DRM, so it should
|
||||
// prefer that key system by default to improve startup performance.
|
||||
if (this.getVersion() === 4) {
|
||||
config.drm.preferredKeySystems.push('com.microsoft.playready');
|
||||
}
|
||||
config.streaming.clearDecodingCache = true;
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
returnLittleEndianUsingPlayReady() {
|
||||
return this.getVersion() === 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsEncryptionSchemePolyfill() {
|
||||
return this.getVersion() !== 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isPlayStation_() {
|
||||
return navigator.userAgent.includes('PlayStation');
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.PlayStation.isPlayStation_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.PlayStation());
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.Tizen');
|
||||
|
||||
goog.require('shaka.config.CrossBoundaryStrategy');
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.Tizen = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const match = navigator.userAgent.match(/Tizen (\d+).(\d+)/);
|
||||
|
||||
/** @private {?number} */
|
||||
this.osMajorVersion_ = match ? parseInt(match[1], 10) : null;
|
||||
|
||||
/** @private {?number} */
|
||||
this.osMinorVersion_ = match ? parseInt(match[2], 10) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.osMajorVersion_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'Tizen';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return shaka.device.IDevice.BrowserEngine.CHROMIUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.TV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresEncryptionInfoInAllInitSegments(keySystem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresEC3InitSegments() {
|
||||
return this.getVersion() === 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSequenceMode() {
|
||||
const version = this.getVersion();
|
||||
return version !== null ? version >= 4 : super.supportsSequenceMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSmoothCodecSwitching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsServerCertificate() {
|
||||
// Tizen 5.0 and earlier do not support server certificates.
|
||||
if (!this.osMajorVersion_ || !this.osMinorVersion_) {
|
||||
return super.supportsServerCertificate();
|
||||
}
|
||||
if (this.osMajorVersion_ === 5) {
|
||||
return this.osMinorVersion_ >= 5;
|
||||
}
|
||||
return this.osMajorVersion_ > 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
detectMaxHardwareResolution() {
|
||||
const maxResolution = {width: 1920, height: 1080};
|
||||
try {
|
||||
if (webapis.systeminfo && webapis.systeminfo.getMaxVideoResolution) {
|
||||
const maxVideoResolution =
|
||||
webapis.systeminfo.getMaxVideoResolution();
|
||||
maxResolution.width = maxVideoResolution.width;
|
||||
maxResolution.height = maxVideoResolution.height;
|
||||
} else {
|
||||
if (webapis.productinfo.is8KPanelSupported &&
|
||||
webapis.productinfo.is8KPanelSupported()) {
|
||||
maxResolution.width = 7680;
|
||||
maxResolution.height = 4320;
|
||||
} else if (webapis.productinfo.isUdPanelSupported &&
|
||||
webapis.productinfo.isUdPanelSupported()) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn('Tizen: Error detecting screen size, default ' +
|
||||
'screen size 1920x1080.');
|
||||
}
|
||||
|
||||
return Promise.resolve(maxResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
|
||||
config.drm.ignoreDuplicateInitData = this.getVersion() !== 2;
|
||||
|
||||
if (this.getVersion() === 3) {
|
||||
config.streaming.crossBoundaryStrategy =
|
||||
shaka.config.CrossBoundaryStrategy.RESET;
|
||||
}
|
||||
config.streaming.shouldFixTimestampOffset = true;
|
||||
// Tizen has long hardware pipeline that respond slowly to seeking.
|
||||
// Therefore we should not seek when we detect a stall on this platform.
|
||||
// Instead, default stallSkip to 0 to force the stall detector to pause
|
||||
// and play instead.
|
||||
config.streaming.stallSkip = 0;
|
||||
config.streaming.gapPadding = 2;
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
rejectCodecs() {
|
||||
// Tizen's implementation of MSE does not work well with opus. To prevent
|
||||
// the player from trying to play opus on Tizen, we will override media
|
||||
// source to always reject opus content.
|
||||
const codecs = [];
|
||||
if (this.osMajorVersion_ !== null && this.osMajorVersion_ < 5) {
|
||||
codecs.push('opus');
|
||||
}
|
||||
return codecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
misreportAC3UsingDrm() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
misreportsSupportForPersistentLicenses() {
|
||||
return this.getVersion() === 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isTizen_() {
|
||||
return navigator.userAgent.includes('Tizen');
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.Tizen.isTizen_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.Tizen());
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.Vizio');
|
||||
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.Vizio = class extends shaka.device.AbstractDevice {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'Vizio';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.TV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Vizio TV.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isVizio_() {
|
||||
return navigator.userAgent.includes('VIZIO SmartCast');
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.Vizio.isVizio_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.Vizio());
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.WebKitSTB');
|
||||
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.util.Lazy');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.WebKitSTB = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* SkyQ STB
|
||||
*
|
||||
* @private {!shaka.util.Lazy<boolean>}
|
||||
*/
|
||||
this.isSkyQ_ = new shaka.util.Lazy(() => {
|
||||
return navigator.userAgent.includes('DT_STB_BCM');
|
||||
});
|
||||
|
||||
/** @private {!shaka.util.Lazy<?number>} */
|
||||
this.version_ = new shaka.util.Lazy(() => {
|
||||
if (navigator.userAgent.includes('DT_STB_BCM')) {
|
||||
return 11;
|
||||
}
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.version_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'WebKit STB';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.TV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return shaka.device.IDevice.BrowserEngine.WEBKIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSequenceMode() {
|
||||
// See: https://bugs.webkit.org/show_bug.cgi?id=210341
|
||||
const version = this.version_.value();
|
||||
return version !== null ? version >= 15 : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
detectMaxHardwareResolution() {
|
||||
const maxResolution = {
|
||||
width: window.screen.width * window.devicePixelRatio,
|
||||
height: window.screen.height * window.devicePixelRatio,
|
||||
};
|
||||
return Promise.resolve(maxResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsEncryptionSchemePolyfill() {
|
||||
return !this.isSkyQ_.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isWebkitSTB_() {
|
||||
if (navigator.userAgent.includes('DT_STB_BCM') ||
|
||||
navigator.userAgent.includes('DT_STB_BCM')) {
|
||||
return true;
|
||||
}
|
||||
if (!(navigator.vendor || '').includes('Apple')) {
|
||||
return false;
|
||||
}
|
||||
if (/(?:iPhone|iPad|iPod)/.test(navigator.userAgent) ||
|
||||
navigator.maxTouchPoints > 1) {
|
||||
return false;
|
||||
}
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform &&
|
||||
navigator.userAgentData.platform.toLowerCase() == 'macos') {
|
||||
return false;
|
||||
} else if (navigator.platform &&
|
||||
navigator.platform.toLowerCase().includes('mac')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.WebKitSTB.isWebkitSTB_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.WebKitSTB());
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.WebOS');
|
||||
|
||||
goog.require('shaka.config.CrossBoundaryStrategy');
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.WebOS = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @private {?number} */
|
||||
this.osVersion_ = this.guessWebOSVersion_();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.osVersion_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'WebOS';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.TV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return shaka.device.IDevice.BrowserEngine.CHROMIUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsMediaCapabilities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSequenceMode() {
|
||||
const version = this.getVersion();
|
||||
return version !== null ? version > 3 : super.supportsSequenceMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsSmoothCodecSwitching() {
|
||||
const version = this.getVersion();
|
||||
return version !== null ?
|
||||
version > 6 : super.supportsSmoothCodecSwitching();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsServerCertificate() {
|
||||
const version = this.getVersion();
|
||||
return version !== null ? version > 3 : super.supportsServerCertificate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
detectMaxHardwareResolution() {
|
||||
const maxResolution = {width: 1920, height: 1080};
|
||||
try {
|
||||
const deviceInfo =
|
||||
/** @type {{screenWidth: number, screenHeight: number}} */(
|
||||
JSON.parse(window.PalmSystem.deviceInfo));
|
||||
// WebOS has always been able to do 1080p. Assume a 1080p limit.
|
||||
maxResolution.width = Math.max(1920, deviceInfo['screenWidth']);
|
||||
maxResolution.height = Math.max(1080, deviceInfo['screenHeight']);
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn('WebOS: Error detecting screen size, default ' +
|
||||
'screen size 1920x1080.');
|
||||
}
|
||||
|
||||
return Promise.resolve(maxResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
|
||||
if (this.getVersion() === 3) {
|
||||
config.streaming.crossBoundaryStrategy =
|
||||
shaka.config.CrossBoundaryStrategy.RESET;
|
||||
}
|
||||
config.streaming.shouldFixTimestampOffset = true;
|
||||
// WebOS has long hardware pipeline that respond slowly to seeking.
|
||||
// Therefore we should not seek when we detect a stall on this platform.
|
||||
// Instead, default stallSkip to 0 to force the stall detector to pause
|
||||
// and play instead.
|
||||
config.streaming.stallSkip = 0;
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {?number}
|
||||
* @private
|
||||
*/
|
||||
guessWebOSVersion_() {
|
||||
let browserVersion = null;
|
||||
const match = navigator.userAgent.match(/Chrome\/(\d+)/);
|
||||
if (match) {
|
||||
browserVersion = parseInt(match[1], /* base= */ 10);
|
||||
}
|
||||
|
||||
switch (browserVersion) {
|
||||
case 38:
|
||||
return 3;
|
||||
case 53:
|
||||
return 4;
|
||||
case 68:
|
||||
return 5;
|
||||
case 79:
|
||||
return 6;
|
||||
case 87:
|
||||
return 22;
|
||||
case 94:
|
||||
return 23;
|
||||
case 108:
|
||||
return 24;
|
||||
case 120:
|
||||
return 25;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportStandardVP9Checking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isWebOS_() {
|
||||
return navigator.userAgent.includes('Web0S');
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.WebOS.isWebOS_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.WebOS());
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.device.Xbox');
|
||||
|
||||
goog.require('shaka.device.AbstractDevice');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
shaka.device.Xbox = class extends shaka.device.AbstractDevice {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @private {boolean} */
|
||||
this.isLegacyEdge_ = navigator.userAgent.includes('Edge/');
|
||||
|
||||
// Looking for something like "Edg/106.0.0.0".
|
||||
const match = navigator.userAgent.match(/Edge?\/(\d+)/);
|
||||
|
||||
/** @private {?number} */
|
||||
this.version_ = match ? parseInt(match[1], /* base= */ 10) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getVersion() {
|
||||
return this.version_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceName() {
|
||||
return 'Xbox';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getDeviceType() {
|
||||
return shaka.device.IDevice.DeviceType.CONSOLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBrowserEngine() {
|
||||
return this.isLegacyEdge_ ? shaka.device.IDevice.BrowserEngine.EDGE :
|
||||
shaka.device.IDevice.BrowserEngine.CHROMIUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
requiresEncryptionInfoInAllInitSegments(keySystem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
insertEncryptionDataBeforeClear() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
shouldOverrideDolbyVisionCodecs() {
|
||||
return this.isLegacyEdge_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
detectMaxHardwareResolution() {
|
||||
const maxResolution = {width: 1920, height: 1080};
|
||||
const winRT = shaka.device.Xbox.getWinRT_();
|
||||
|
||||
if (winRT) {
|
||||
try {
|
||||
const protectionCapabilities =
|
||||
new winRT.Media.Protection.ProtectionCapabilities();
|
||||
const protectionResult =
|
||||
winRT.Media.Protection.ProtectionCapabilityResult;
|
||||
// isTypeSupported may return "maybe", which means the operation
|
||||
// is not completed. This means we need to retry
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
|
||||
let result = null;
|
||||
const type =
|
||||
'video/mp4;codecs="hvc1,mp4a";features="decode-res-x=3840,' +
|
||||
'decode-res-y=2160,decode-bitrate=20000,decode-fps=30,' +
|
||||
'decode-bpc=10,display-res-x=3840,display-res-y=2160,' +
|
||||
'display-bpc=8"';
|
||||
const keySystem = 'com.microsoft.playready.recommendation';
|
||||
do {
|
||||
result = protectionCapabilities.isTypeSupported(type, keySystem);
|
||||
} while (result === protectionResult.maybe);
|
||||
if (result === protectionResult.probably) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
}
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
|
||||
'screen size 1920x1080.');
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(maxResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
adjustConfig(config) {
|
||||
super.adjustConfig(config);
|
||||
|
||||
// The Xbox One browser does not detect DRM key changes signalled by a
|
||||
// change in the PSSH in media segments. We need to parse PSSH from media
|
||||
// segments to detect key changes.
|
||||
config.drm.parseInbandPsshEnabled = this.isLegacyEdge_;
|
||||
// The Xbox only supports the Playready DRM, so it should
|
||||
// prefer that key system by default to improve startup performance.
|
||||
config.drm.preferredKeySystems.push('com.microsoft.playready');
|
||||
if (this.isLegacyEdge_) {
|
||||
config.streaming.gapPadding = 0.01;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
supportsOfflineStorage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {?WinRT}
|
||||
* @private
|
||||
*/
|
||||
static getWinRT_() {
|
||||
let winRT = null;
|
||||
try {
|
||||
// Try to access to WinRT for WebView, if it's not defined,
|
||||
// try to access to WinRT for WebView2, if it's not defined either,
|
||||
// let it throw.
|
||||
if (typeof Windows !== 'undefined') {
|
||||
winRT = Windows;
|
||||
} else {
|
||||
winRT = chrome.webview.hostObjects.sync.Windows;
|
||||
}
|
||||
} catch (e) {}
|
||||
return winRT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is an Xbox One.
|
||||
*
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isXbox_() {
|
||||
return navigator.userAgent.includes('Xbox One') ||
|
||||
shaka.device.Xbox.getWinRT_() !== null;
|
||||
}
|
||||
};
|
||||
|
||||
if (shaka.device.Xbox.isXbox_()) {
|
||||
shaka.device.DeviceFactory.registerDeviceFactory(
|
||||
() => new shaka.device.Xbox());
|
||||
}
|
||||
+16
-14
@@ -7,8 +7,9 @@
|
||||
goog.provide('shaka.drm.DrmEngine');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.debug.RunningInLab');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.drm.DrmUtils');
|
||||
goog.require('shaka.net.NetworkingEngine');
|
||||
goog.require('shaka.util.ArrayUtils');
|
||||
@@ -23,7 +24,6 @@ goog.require('shaka.util.Iterables');
|
||||
goog.require('shaka.util.ManifestParserUtils');
|
||||
goog.require('shaka.util.MapUtils');
|
||||
goog.require('shaka.util.ObjectUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Pssh');
|
||||
goog.require('shaka.util.PublicPromise');
|
||||
goog.require('shaka.util.StreamUtils');
|
||||
@@ -1725,12 +1725,11 @@ shaka.drm.DrmEngine = class {
|
||||
// NOTE that we skip this if byteLength != 16. This is used for Edge
|
||||
// which uses single-byte dummy key IDs.
|
||||
// However, unlike Edge and Chromecast, Tizen doesn't have this problem.
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (shaka.drm.DrmUtils.isPlayReadyKeySystem(
|
||||
this.currentDrmInfo_.keySystem) &&
|
||||
keyId.byteLength == 16 &&
|
||||
(shaka.util.Platform.isEdge() ||
|
||||
shaka.util.Platform.isPS4() ||
|
||||
shaka.util.Platform.isSonyTV())) {
|
||||
device.returnLittleEndianUsingPlayReady()) {
|
||||
// Read out some fields in little-endian:
|
||||
const dataView = shaka.util.BufferUtils.toDataView(keyId);
|
||||
const part0 = dataView.getUint32(0, /* LE= */ true);
|
||||
@@ -1906,6 +1905,8 @@ shaka.drm.DrmEngine = class {
|
||||
/** @type {!Map<string, ?shaka.extern.DrmSupportType>} */
|
||||
const support = new Map();
|
||||
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
|
||||
/**
|
||||
* @param {string} keySystem
|
||||
* @param {MediaKeySystemAccess} access
|
||||
@@ -1917,10 +1918,7 @@ shaka.drm.DrmEngine = class {
|
||||
// Workaround: Our automated test lab runs Windows browsers under a
|
||||
// headless service. In this environment, Firefox's CDMs seem to crash
|
||||
// when we create the CDM here.
|
||||
if (goog.DEBUG && // not a production build
|
||||
shaka.util.Platform.isWindows() && // on Windows
|
||||
shaka.util.Platform.isFirefox() && // with Firefox
|
||||
shaka.debug.RunningInLab) { // in our headless lab
|
||||
if (!device.createMediaKeysWhenCheckingSupport()) {
|
||||
// Reject this, since it crashes our tests.
|
||||
throw new Error('Suppressing Firefox Windows DRM in testing!');
|
||||
} else {
|
||||
@@ -1944,7 +1942,7 @@ shaka.drm.DrmEngine = class {
|
||||
// does. It doesn't fail until you call update() with a license
|
||||
// response, which is way too late.
|
||||
// This is a work-around for #894.
|
||||
if (shaka.util.Platform.isTizen3()) {
|
||||
if (device.misreportsSupportForPersistentLicenses()) {
|
||||
persistentState = false;
|
||||
}
|
||||
|
||||
@@ -2031,7 +2029,8 @@ shaka.drm.DrmEngine = class {
|
||||
// is not set. This is a workaround for that issue.
|
||||
const TIMEOUT_FOR_CHECK_ACCESS_IN_SECONDS = 5;
|
||||
let access;
|
||||
if (shaka.util.Platform.isAndroid()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getDeviceType() == shaka.device.IDevice.DeviceType.MOBILE) {
|
||||
access =
|
||||
await shaka.util.Functional.promiseWithTimeout(
|
||||
TIMEOUT_FOR_CHECK_ACCESS_IN_SECONDS,
|
||||
@@ -2080,7 +2079,8 @@ shaka.drm.DrmEngine = class {
|
||||
// is not set. This is a workaround for that issue.
|
||||
const TIMEOUT_FOR_DECODING_INFO_IN_SECONDS = 5;
|
||||
let decodingInfo;
|
||||
if (shaka.util.Platform.isAndroid()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getDeviceType() == shaka.device.IDevice.DeviceType.MOBILE) {
|
||||
decodingInfo =
|
||||
await shaka.util.Functional.promiseWithTimeout(
|
||||
TIMEOUT_FOR_DECODING_INFO_IN_SECONDS,
|
||||
@@ -2110,8 +2110,10 @@ shaka.drm.DrmEngine = class {
|
||||
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
||||
// session but playback will never start
|
||||
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
||||
if (shaka.drm.DrmUtils.isClearKeySystem(keySystem) &&
|
||||
shaka.util.Platform.isApple()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT &&
|
||||
shaka.drm.DrmUtils.isClearKeySystem(keySystem)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
goog.provide('shaka.media.ContentWorkarounds');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.util.BufferUtils');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.Mp4BoxParsers');
|
||||
goog.require('shaka.util.Mp4Generator');
|
||||
goog.require('shaka.util.Mp4Parser');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Uint8ArrayUtils');
|
||||
|
||||
|
||||
@@ -194,8 +194,8 @@ shaka.media.ContentWorkarounds = class {
|
||||
// patched one, otherwise video element throws following error:
|
||||
// CHUNK_DEMUXER_ERROR_APPEND_FAILED: Sample encryption info is not
|
||||
// available.
|
||||
if (shaka.util.Platform.isEdge() && shaka.util.Platform.isWindows() &&
|
||||
!shaka.util.Platform.isXboxOne()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.requiresClearAndEncryptedInitSegments()) {
|
||||
const doubleInitSegment = new Uint8Array(initSegment.byteLength +
|
||||
modifiedInitSegment.byteLength);
|
||||
doubleInitSegment.set(modifiedInitSegment);
|
||||
@@ -340,10 +340,10 @@ shaka.media.ContentWorkarounds = class {
|
||||
// For other platforms, we cut and insert at the end of the source box. It's
|
||||
// not clear why this is necessary on Xbox One, but it seems to be evidence
|
||||
// of another bug in the firmware implementation of MediaSource & EME.
|
||||
const cutPoint = (shaka.util.Platform.isApple() ||
|
||||
shaka.util.Platform.isXboxOne() || shaka.util.Platform.isEdge()) ?
|
||||
sourceBox.start :
|
||||
sourceBox.start + sourceBox.size;
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const cutPoint = device.insertEncryptionDataBeforeClear() ?
|
||||
sourceBox.start :
|
||||
sourceBox.start + sourceBox.size;
|
||||
|
||||
// The data before the cut point will be copied to the same location as
|
||||
// before. The data after that will be appended after the added metadata
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
goog.provide('shaka.media.ManifestParser');
|
||||
|
||||
goog.require('shaka.Deprecate');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
|
||||
// TODO: revisit this when Closure Compiler supports partially-exported classes.
|
||||
@@ -67,7 +67,8 @@ shaka.media.ManifestParser = class {
|
||||
|
||||
// Make sure all registered parsers are shown, but only for MSE-enabled
|
||||
// platforms where our parsers matter.
|
||||
if (shaka.util.Platform.supportsMediaSource()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.supportsMediaSource()) {
|
||||
for (const type of ManifestParser.parsersByMime.keys()) {
|
||||
support[type] = true;
|
||||
}
|
||||
@@ -88,10 +89,10 @@ shaka.media.ManifestParser = class {
|
||||
for (const type of testMimeTypes) {
|
||||
// Only query our parsers for MSE-enabled platforms. Otherwise, query a
|
||||
// temporary media element for native support for these types.
|
||||
if (shaka.util.Platform.supportsMediaSource()) {
|
||||
if (device.supportsMediaSource()) {
|
||||
support[type] = ManifestParser.parsersByMime.has(type);
|
||||
} else {
|
||||
support[type] = shaka.util.Platform.supportsMediaType(type);
|
||||
support[type] = device.supportsMediaType(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +142,8 @@ shaka.media.ManifestParser = class {
|
||||
*/
|
||||
static isSupported(mimeType) {
|
||||
// Without MediaSource, our own parsers are useless.
|
||||
if (!shaka.util.Platform.supportsMediaSource()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (!device.supportsMediaSource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ goog.provide('shaka.media.MediaSourceEngine');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.config.CodecSwitchingStrategy');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.media.Capabilities');
|
||||
goog.require('shaka.media.ContentWorkarounds');
|
||||
goog.require('shaka.media.ClosedCaptionParser');
|
||||
@@ -31,7 +32,6 @@ goog.require('shaka.util.ManifestParserUtils');
|
||||
goog.require('shaka.util.MimeUtils');
|
||||
goog.require('shaka.util.Mp4BoxParsers');
|
||||
goog.require('shaka.util.Mp4Parser');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.PublicPromise');
|
||||
goog.require('shaka.util.StreamUtils');
|
||||
goog.require('shaka.util.TimeUtils');
|
||||
@@ -372,14 +372,15 @@ shaka.media.MediaSourceEngine = class {
|
||||
];
|
||||
|
||||
const support = {};
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
for (const type of testMimeTypes) {
|
||||
if (shaka.text.TextEngine.isTypeSupported(type)) {
|
||||
support[type] = true;
|
||||
} else if (shaka.util.Platform.supportsMediaSource()) {
|
||||
} else if (device.supportsMediaSource()) {
|
||||
support[type] = shaka.media.Capabilities.isTypeSupported(type) ||
|
||||
shaka.transmuxer.TransmuxerEngine.isSupported(type);
|
||||
} else {
|
||||
support[type] = shaka.util.Platform.supportsMediaType(type);
|
||||
support[type] = device.supportsMediaType(type);
|
||||
}
|
||||
|
||||
const basicType = type.split(';')[0];
|
||||
@@ -2104,7 +2105,6 @@ shaka.media.MediaSourceEngine = class {
|
||||
*/
|
||||
workAroundBrokenPlatforms_(stream, segment, reference, contentType) {
|
||||
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
||||
const Platform = shaka.util.Platform;
|
||||
|
||||
const isMp4 = shaka.util.MimeUtils.getContainerType(
|
||||
this.sourceBufferTypes_.get(contentType)) == 'mp4';
|
||||
@@ -2120,6 +2120,7 @@ shaka.media.MediaSourceEngine = class {
|
||||
isEncrypted = reference.initSegmentReference.encrypted;
|
||||
}
|
||||
const uri = reference ? reference.getUris()[0] : null;
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
|
||||
if (this.config_.correctEc3Enca &&
|
||||
isInitSegment &&
|
||||
@@ -2138,20 +2139,20 @@ shaka.media.MediaSourceEngine = class {
|
||||
// transformation on MP4 containers.
|
||||
// See: https://github.com/shaka-project/shaka-player/issues/2759
|
||||
if (this.config_.insertFakeEncryptionInInit && encryptionExpected &&
|
||||
Platform.requiresEncryptionInfoInAllInitSegments(keySystem,
|
||||
device.requiresEncryptionInfoInAllInitSegments(keySystem,
|
||||
contentType)) {
|
||||
if (isInitSegment) {
|
||||
shaka.log.debug('Forcing fake encryption information in init segment.');
|
||||
segment =
|
||||
shaka.media.ContentWorkarounds.fakeEncryption(stream, segment, uri);
|
||||
} else if (!isEncrypted && Platform.requiresTfhdFix(contentType)) {
|
||||
} else if (!isEncrypted && device.requiresTfhdFix(contentType)) {
|
||||
shaka.log.debug(
|
||||
'Forcing fake encryption information in media segment.');
|
||||
segment = shaka.media.ContentWorkarounds.fakeMediaEncryption(segment);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInitSegment && Platform.requiresEC3InitSegments()) {
|
||||
if (isInitSegment && device.requiresEC3InitSegments()) {
|
||||
shaka.log.debug('Forcing fake EC-3 information in init segment.');
|
||||
segment = shaka.media.ContentWorkarounds.fakeEC3(segment);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ goog.provide('shaka.media.Playhead');
|
||||
goog.provide('shaka.media.SrcEqualsPlayhead');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.media.Capabilities');
|
||||
goog.require('shaka.media.GapJumpingController');
|
||||
@@ -17,7 +18,6 @@ goog.require('shaka.media.VideoWrapper');
|
||||
goog.require('shaka.util.EventManager');
|
||||
goog.require('shaka.util.IReleasable');
|
||||
goog.require('shaka.util.MediaReadyState');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Timer');
|
||||
goog.requireType('shaka.media.PresentationTimeline');
|
||||
|
||||
@@ -529,12 +529,12 @@ shaka.media.MediaSourcePlayhead = class {
|
||||
if (mightNeedCorrectiveSeek &&
|
||||
Math.abs(targetTime - currentTime) > gapLimit) {
|
||||
let canCorrectiveSeek = false;
|
||||
if (shaka.util.Platform.isSeekingSlow()) {
|
||||
const seekDelay = shaka.device.DeviceFactory.getDevice().seekDelay();
|
||||
if (seekDelay) {
|
||||
// You can only seek like this every so often. This is to prevent an
|
||||
// infinite loop on systems where changing currentTime takes a
|
||||
// significant amount of time (e.g. Chromecast).
|
||||
const time = Date.now() / 1000;
|
||||
const seekDelay = shaka.util.Platform.isFuchsiaCastDevice() ? 3 : 1;
|
||||
if (!this.lastCorrectiveSeek_ ||
|
||||
this.lastCorrectiveSeek_ < time - seekDelay) {
|
||||
this.lastCorrectiveSeek_ = time;
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
goog.provide('shaka.media.PreferenceBasedCriteria');
|
||||
|
||||
goog.require('shaka.config.CodecSwitchingStrategy');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.media.AdaptationSet');
|
||||
goog.require('shaka.media.AdaptationSetCriteria');
|
||||
goog.require('shaka.media.Capabilities');
|
||||
goog.require('shaka.util.LanguageUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
|
||||
/**
|
||||
@@ -322,7 +322,8 @@ shaka.media.PreferenceBasedCriteria = class {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
hdrLevel = shaka.util.Platform.getHdrLevel(someHLG);
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
hdrLevel = device.getHdrLevel(someHLG);
|
||||
}
|
||||
return variants.filter((variant) => {
|
||||
if (variant.video && variant.video.hdr && variant.video.hdr != hdrLevel) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
goog.provide('shaka.offline.indexeddb.StorageMechanism');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.offline.StorageMuxer');
|
||||
goog.require('shaka.offline.indexeddb.EmeSessionStorageCell');
|
||||
@@ -15,7 +16,6 @@ goog.require('shaka.offline.indexeddb.V2StorageCell');
|
||||
goog.require('shaka.offline.indexeddb.V5StorageCell');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.PublicPromise');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Timer');
|
||||
|
||||
|
||||
@@ -403,15 +403,8 @@ shaka.offline.indexeddb.StorageMechanismOpenTimeout = 5;
|
||||
shaka.offline.StorageMuxer.register(
|
||||
'idb',
|
||||
() => {
|
||||
// Offline storage is not supported on the Chromecast Linux/Fuchsia or
|
||||
// Xbox One platforms.
|
||||
if ((shaka.util.Platform.isChromecast() &&
|
||||
!shaka.util.Platform.isAndroidCastDevice()) ||
|
||||
shaka.util.Platform.isXboxOne()) {
|
||||
return null;
|
||||
}
|
||||
// Offline storage requires the IndexedDB API.
|
||||
if (!window.indexedDB) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (!device.supportsOfflineStorage()) {
|
||||
return null;
|
||||
}
|
||||
return new shaka.offline.indexeddb.StorageMechanism();
|
||||
|
||||
@@ -8,6 +8,7 @@ goog.provide('shaka.offline.Storage');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.Player');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.drm.DrmEngine');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.media.ManifestParser');
|
||||
@@ -34,7 +35,6 @@ goog.require('shaka.util.IDestroyable');
|
||||
goog.require('shaka.util.Iterables');
|
||||
goog.require('shaka.util.ManifestParserUtils');
|
||||
goog.require('shaka.util.MimeUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.PlayerConfiguration');
|
||||
goog.require('shaka.util.StreamUtils');
|
||||
goog.requireType('shaka.media.SegmentReference');
|
||||
@@ -171,7 +171,8 @@ shaka.offline.Storage = class {
|
||||
// Our Storage system is useless without MediaSource. MediaSource allows us
|
||||
// to pull data from anywhere (including our Storage system) and feed it to
|
||||
// the video element.
|
||||
if (!shaka.util.Platform.supportsMediaSource()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (!device.supportsMediaSource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+51
-29
@@ -9,6 +9,8 @@ goog.provide('shaka.Player');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.config.CrossBoundaryStrategy');
|
||||
goog.require('shaka.Deprecate');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.drm.DrmEngine');
|
||||
goog.require('shaka.drm.DrmUtils');
|
||||
goog.require('shaka.log');
|
||||
@@ -62,7 +64,6 @@ goog.require('shaka.util.MimeUtils');
|
||||
goog.require('shaka.util.Mutex');
|
||||
goog.require('shaka.util.NumberUtils');
|
||||
goog.require('shaka.util.ObjectUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.PlayerConfiguration');
|
||||
goog.require('shaka.util.PublicPromise');
|
||||
goog.require('shaka.util.Stats');
|
||||
@@ -1226,18 +1227,20 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
}
|
||||
|
||||
// We do not support IE
|
||||
if (shaka.util.Platform.isIE()) {
|
||||
const userAgent = navigator.userAgent || '';
|
||||
if (userAgent.includes('Trident/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have MediaSource (MSE) support, we should be able to use Shaka.
|
||||
if (shaka.util.Platform.supportsMediaSource()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.supportsMediaSource()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we don't have MSE, we _may_ be able to use Shaka. Look for native HLS
|
||||
// support, and call this platform usable if we have it.
|
||||
return shaka.util.Platform.supportsMediaType('application/x-mpegurl');
|
||||
return device.supportsMediaType('application/x-mpegurl');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1262,8 +1265,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
}
|
||||
const manifest = shaka.media.ManifestParser.probeSupport();
|
||||
const media = shaka.media.MediaSourceEngine.probeSupport();
|
||||
const hardwareResolution =
|
||||
await shaka.util.Platform.detectMaxHardwareResolution();
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
goog.asserts.assert(device, 'device must be non-null');
|
||||
const hardwareResolution = await device.detectMaxHardwareResolution();
|
||||
|
||||
/** @type {shaka.extern.SupportType} */
|
||||
const ret = {
|
||||
@@ -1332,8 +1336,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
}
|
||||
|
||||
// Only initialize media source if the platform supports it.
|
||||
if (initializeMediaSource &&
|
||||
shaka.util.Platform.supportsMediaSource() &&
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (initializeMediaSource && device.supportsMediaSource() &&
|
||||
!this.mediaSourceEngine_) {
|
||||
await this.initializeMediaSourceEngineInner_();
|
||||
}
|
||||
@@ -1658,7 +1662,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
this.mutex_.release();
|
||||
}
|
||||
|
||||
if (initializeMediaSource && shaka.util.Platform.supportsMediaSource() &&
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (initializeMediaSource && device.supportsMediaSource() &&
|
||||
!this.mediaSourceEngine_ && this.video_) {
|
||||
await this.initializeMediaSourceEngineInner_();
|
||||
}
|
||||
@@ -1979,7 +1984,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
}, 'loadInner_');
|
||||
preloadManager.stopQueuingLatePhaseQueuedOperations();
|
||||
|
||||
if (this.mimeType_ && shaka.util.Platform.isApple() &&
|
||||
if (this.mimeType_ &&
|
||||
shaka.device.DeviceFactory.getDevice().supportsAirPlay() &&
|
||||
shaka.util.MimeUtils.isHlsType(this.mimeType_)) {
|
||||
this.mediaSourceEngine_.addSecondarySource(
|
||||
this.assetUri_, this.mimeType_);
|
||||
@@ -2268,8 +2274,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
if (this.maxHwRes_.width == Infinity &&
|
||||
this.maxHwRes_.height == Infinity &&
|
||||
!this.config_.ignoreHardwareResolution) {
|
||||
const maxResolution =
|
||||
await shaka.util.Platform.detectMaxHardwareResolution();
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
goog.asserts.assert(device, 'device must be non-null');
|
||||
const maxResolution = await device.detectMaxHardwareResolution();
|
||||
this.maxHwRes_.width = maxResolution.width;
|
||||
this.maxHwRes_.height = maxResolution.height;
|
||||
}
|
||||
@@ -2482,8 +2489,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
const retryParams = this.config_.manifest.retryParameters;
|
||||
let mimeType = await shaka.net.NetworkingUtils.getMimeType(
|
||||
assetUri, this.networkingEngine_, retryParams);
|
||||
if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
|
||||
mimeType = 'application/vnd.apple.mpegurl';
|
||||
if (mimeType == 'application/x-mpegurl') {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT) {
|
||||
mimeType = 'application/vnd.apple.mpegurl';
|
||||
}
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
@@ -2500,19 +2511,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
* @private
|
||||
*/
|
||||
shouldUseSrcEquals_(assetUri, mimeType) {
|
||||
const Platform = shaka.util.Platform;
|
||||
const MimeUtils = shaka.util.MimeUtils;
|
||||
|
||||
// If we are using a platform that does not support media source, we will
|
||||
// fall back to src= to handle all playback.
|
||||
if (!Platform.supportsMediaSource()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (!device.supportsMediaSource()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mimeType) {
|
||||
// If we have a MIME type, check if the browser can play it natively.
|
||||
// This will cover both single files and native HLS.
|
||||
const mediaElement = this.video_ || Platform.anyMediaElement();
|
||||
const mediaElement = this.video_ || shaka.util.Dom.anyMediaElement();
|
||||
const canPlayNatively = mediaElement.canPlayType(mimeType) != '';
|
||||
|
||||
// If we can't play natively, then src= isn't an option.
|
||||
@@ -2537,7 +2548,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
// version there.
|
||||
if (MimeUtils.isHlsType(mimeType)) {
|
||||
// Native FairPlay HLS can be preferred on Apple platforms.
|
||||
if (Platform.isApple() &&
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT &&
|
||||
(this.config_.drm.servers['com.apple.fps'] ||
|
||||
this.config_.drm.servers['com.apple.fps.1_0'])) {
|
||||
return this.config_.streaming.useNativeHlsForFairPlay;
|
||||
@@ -2593,8 +2606,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
* @private
|
||||
*/
|
||||
async initializeMediaSourceEngineInner_() {
|
||||
goog.asserts.assert(
|
||||
shaka.util.Platform.supportsMediaSource(),
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
goog.asserts.assert(device.supportsMediaSource(),
|
||||
'We should not be initializing media source on a platform that ' +
|
||||
'does not support media source.');
|
||||
goog.asserts.assert(
|
||||
@@ -3306,11 +3319,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
|
||||
mediaElement.src = playbackUri;
|
||||
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
|
||||
// Tizen 3 / WebOS won't load anything unless you call load() explicitly,
|
||||
// no matter the value of the preload attribute. This is harmful on some
|
||||
// other platforms by triggering unbounded loading of media data, but is
|
||||
// necessary here.
|
||||
if (shaka.util.Platform.isTizen() || shaka.util.Platform.isWebOS()) {
|
||||
if (device.getDeviceType() == shaka.device.IDevice.DeviceType.TV) {
|
||||
mediaElement.load();
|
||||
}
|
||||
|
||||
@@ -3319,7 +3334,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
// Note: this only happens when there are not autoplay.
|
||||
if (mediaElement.preload != 'none' && !mediaElement.autoplay &&
|
||||
shaka.util.MimeUtils.isHlsType(mimeType) &&
|
||||
shaka.util.Platform.isApple()) {
|
||||
device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT) {
|
||||
mediaElement.load();
|
||||
}
|
||||
|
||||
@@ -4247,8 +4263,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
* @export
|
||||
*/
|
||||
configure(config, value) {
|
||||
const Platform = shaka.util.Platform;
|
||||
|
||||
goog.asserts.assert(this.config_, 'Config must not be null!');
|
||||
goog.asserts.assert(typeof(config) == 'object' || arguments.length == 2,
|
||||
'String configs should have values!');
|
||||
@@ -4288,8 +4302,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
'streaming.useNativeHlsOnSafari configuration',
|
||||
'Please Use streaming.useNativeHlsForFairPlay or ' +
|
||||
'streaming.preferNativeHls instead.');
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
config['streaming']['preferNativeHls'] =
|
||||
config['streaming']['useNativeHlsOnSafari'] && Platform.isApple();
|
||||
config['streaming']['useNativeHlsOnSafari'] &&
|
||||
device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT;
|
||||
delete config['streaming']['useNativeHlsOnSafari'];
|
||||
}
|
||||
|
||||
@@ -6924,7 +6941,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
}
|
||||
|
||||
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
||||
if (forced && shaka.util.Platform.isApple()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (forced && device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT) {
|
||||
// See: https://github.com/whatwg/html/issues/4472
|
||||
kind = 'forced';
|
||||
}
|
||||
@@ -7588,9 +7607,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
// On iOS where the Fullscreen API is not available we prefer
|
||||
// NativeTextDisplayer because it works with the Fullscreen API of the
|
||||
// video element itself.
|
||||
const Platform = shaka.util.Platform;
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (this.videoContainer_ &&
|
||||
(!Platform.isApple() || document.fullscreenEnabled)) {
|
||||
(document.fullscreenEnabled || device.getBrowserEngine() !==
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT)) {
|
||||
return new shaka.text.UITextDisplayer(
|
||||
this.video_, this.videoContainer_);
|
||||
} else {
|
||||
@@ -8819,7 +8839,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
error.code == shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW ||
|
||||
error.code == shaka.util.Error.Code.STREAMING_NOT_ALLOWED ||
|
||||
error.code == shaka.util.Error.Code.TRANSMUXING_FAILED)) {
|
||||
if (shaka.util.Platform.isApple() &&
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT &&
|
||||
error.code == shaka.util.Error.Code.VIDEO_ERROR) {
|
||||
// Wait until the MSE error occurs
|
||||
return;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
goog.provide('shaka.polyfill.EncryptionScheme');
|
||||
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.polyfill');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
/**
|
||||
* @summary A polyfill to add support for EncryptionScheme queries in EME.
|
||||
@@ -28,7 +28,8 @@ shaka.polyfill.EncryptionScheme = class {
|
||||
// caused by unsupported encryptionScheme handling. These platforms do not
|
||||
// require the polyfill, and forcing encryptionScheme processing can result
|
||||
// in playback crashes.
|
||||
if (shaka.util.Platform.isPS4() || shaka.util.Platform.isSkyQ()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (!device.supportsEncryptionSchemePolyfill()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
goog.provide('shaka.polyfill.MediaCapabilities');
|
||||
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.drm.DrmUtils');
|
||||
goog.require('shaka.media.Capabilities');
|
||||
goog.require('shaka.polyfill');
|
||||
goog.require('shaka.util.MimeUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
|
||||
/**
|
||||
@@ -27,41 +28,7 @@ shaka.polyfill.MediaCapabilities = class {
|
||||
* @export
|
||||
*/
|
||||
static install() {
|
||||
// We can enable MediaCapabilities in Android and Fuchsia devices, but not
|
||||
// in Linux devices because the implementation is buggy.
|
||||
// Since MediaCapabilities implementation is buggy in Apple browsers, we
|
||||
// should always install polyfill for Apple browsers.
|
||||
// See: https://github.com/shaka-project/shaka-player/issues/3530
|
||||
// TODO: re-evaluate MediaCapabilities in the future versions of Apple
|
||||
// Browsers.
|
||||
// Since MediaCapabilities implementation is buggy in PS5 browsers, we
|
||||
// should always install polyfill for PS5 browsers.
|
||||
// See: https://github.com/shaka-project/shaka-player/issues/3582
|
||||
// TODO: re-evaluate MediaCapabilities in the future versions of PS5
|
||||
// Browsers.
|
||||
// Since MediaCapabilities implementation does not exist in PS4 browsers, we
|
||||
// should always install polyfill.
|
||||
// Since MediaCapabilities implementation is buggy in Tizen browsers, we
|
||||
// should always install polyfill for Tizen browsers.
|
||||
// Since MediaCapabilities implementation is buggy in WebOS browsers, we
|
||||
// should always install polyfill for WebOS browsers.
|
||||
// Since MediaCapabilities implementation is buggy in EOS browsers, we
|
||||
// should always install polyfill for EOS browsers.
|
||||
// Since MediaCapabilities implementation is buggy in Hisense browsers, we
|
||||
// should always install polyfill for Hisense browsers.
|
||||
let canUseNativeMCap = true;
|
||||
if (shaka.util.Platform.isOlderChromecast() ||
|
||||
shaka.util.Platform.isApple() ||
|
||||
shaka.util.Platform.isPS5() ||
|
||||
shaka.util.Platform.isPS4() ||
|
||||
shaka.util.Platform.isWebOS() ||
|
||||
shaka.util.Platform.isTizen() ||
|
||||
shaka.util.Platform.isHisense() ||
|
||||
shaka.util.Platform.isVizio() ||
|
||||
shaka.util.Platform.isWebkitSTB()) {
|
||||
canUseNativeMCap = false;
|
||||
}
|
||||
if (canUseNativeMCap && navigator.mediaCapabilities) {
|
||||
if (shaka.device.DeviceFactory.getDevice().supportsMediaCapabilities()) {
|
||||
shaka.log.info(
|
||||
'MediaCapabilities: Native mediaCapabilities support found.');
|
||||
return;
|
||||
@@ -104,11 +71,13 @@ shaka.polyfill.MediaCapabilities = class {
|
||||
configuration: mediaDecodingConfig,
|
||||
};
|
||||
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
|
||||
const videoConfig = mediaDecodingConfig['video'];
|
||||
const audioConfig = mediaDecodingConfig['audio'];
|
||||
|
||||
if (mediaDecodingConfig.type == 'media-source') {
|
||||
if (!shaka.util.Platform.supportsMediaSource()) {
|
||||
if (!device.supportsMediaSource()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -131,7 +100,7 @@ shaka.polyfill.MediaCapabilities = class {
|
||||
} else if (mediaDecodingConfig.type == 'file') {
|
||||
if (videoConfig) {
|
||||
const contentType = videoConfig.contentType;
|
||||
const isSupported = shaka.util.Platform.supportsMediaType(contentType);
|
||||
const isSupported = device.supportsMediaType(contentType);
|
||||
if (!isSupported) {
|
||||
return res;
|
||||
}
|
||||
@@ -139,7 +108,7 @@ shaka.polyfill.MediaCapabilities = class {
|
||||
|
||||
if (audioConfig) {
|
||||
const contentType = audioConfig.contentType;
|
||||
const isSupported = shaka.util.Platform.supportsMediaType(contentType);
|
||||
const isSupported = device.supportsMediaType(contentType);
|
||||
if (!isSupported) {
|
||||
return res;
|
||||
}
|
||||
@@ -179,7 +148,9 @@ shaka.polyfill.MediaCapabilities = class {
|
||||
// Cast platforms will additionally check canDisplayType(), which
|
||||
// accepts extended MIME type parameters.
|
||||
// See: https://github.com/shaka-project/shaka-player/issues/4726
|
||||
if (shaka.util.Platform.isChromecast()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const deviceType = device.getDeviceType();
|
||||
if (deviceType === shaka.device.IDevice.DeviceType.CAST) {
|
||||
const isSupported =
|
||||
await shaka.polyfill.MediaCapabilities.canCastDisplayType_(
|
||||
videoConfig);
|
||||
@@ -195,8 +166,11 @@ shaka.polyfill.MediaCapabilities = class {
|
||||
* @private
|
||||
*/
|
||||
static checkAudioSupport_(audioConfig) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const deviceType = device.getDeviceType();
|
||||
let extendedType = audioConfig.contentType;
|
||||
if (shaka.util.Platform.isChromecast() && audioConfig.spatialRendering) {
|
||||
if (deviceType === shaka.device.IDevice.DeviceType.CAST &&
|
||||
audioConfig.spatialRendering) {
|
||||
extendedType += '; spatialRendering=true';
|
||||
}
|
||||
return shaka.media.Capabilities.isTypeSupported(extendedType);
|
||||
@@ -227,7 +201,7 @@ shaka.polyfill.MediaCapabilities = class {
|
||||
// report EC-3 support. So query EC-3 as a fallback for AC-3.
|
||||
// See https://github.com/shaka-project/shaka-player/issues/2989 for
|
||||
// details.
|
||||
if (shaka.util.Platform.isTizen() &&
|
||||
if (shaka.device.DeviceFactory.getDevice().misreportAC3UsingDrm() &&
|
||||
audioConfig.contentType.includes('codecs="ac-3"')) {
|
||||
capability.contentType = 'audio/mp4; codecs="ec-3"';
|
||||
}
|
||||
|
||||
+12
-23
@@ -6,10 +6,11 @@
|
||||
|
||||
goog.provide('shaka.polyfill.MediaSource');
|
||||
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.polyfill');
|
||||
goog.require('shaka.util.MimeUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
/**
|
||||
* @summary A polyfill to patch MSE bugs.
|
||||
@@ -28,7 +29,10 @@ shaka.polyfill.MediaSource = class {
|
||||
// example, and instances are only accessible after setting up MediaSource
|
||||
// on a video element. Because of this, we use UA detection and other
|
||||
// platform detection tricks to decide which patches to install.
|
||||
const safariVersion = shaka.util.Platform.safariVersion();
|
||||
const BrowserEngine = shaka.device.IDevice.BrowserEngine;
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const safariVersion = device.getBrowserEngine() === BrowserEngine.WEBKIT ?
|
||||
device.getVersion() : null;
|
||||
|
||||
if (!window.MediaSource && !window.ManagedMediaSource) {
|
||||
shaka.log.info('No MSE implementation available.');
|
||||
@@ -60,30 +64,15 @@ shaka.polyfill.MediaSource = class {
|
||||
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
|
||||
shaka.polyfill.MediaSource.stubAbort_();
|
||||
}
|
||||
} else if (shaka.util.Platform.isZenterio()) {
|
||||
// Zenterio uses WPE based on Webkit 607.x.x which do not correctly
|
||||
// implement abort() on SourceBuffer.
|
||||
// Calling abort() before appending a segment causes that segment to be
|
||||
// incomplete in the buffer.
|
||||
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
|
||||
shaka.polyfill.MediaSource.stubAbort_();
|
||||
// If you remove up to a keyframe, Webkit 607.x.x incorrectly will also
|
||||
// remove that keyframe and the content up to the next.
|
||||
// Offsetting the end of the removal range seems to help.
|
||||
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
|
||||
shaka.polyfill.MediaSource.patchRemovalRange_();
|
||||
} else if (shaka.util.Platform.isTizen2() ||
|
||||
shaka.util.Platform.isTizen3() ||
|
||||
shaka.util.Platform.isTizen4()) {
|
||||
shaka.log.info('Rejecting Opus.');
|
||||
// Tizen's implementation of MSE does not work well with opus. To prevent
|
||||
// the player from trying to play opus on Tizen, we will override media
|
||||
// source to always reject opus content.
|
||||
shaka.polyfill.MediaSource.rejectCodec_('opus');
|
||||
} else {
|
||||
shaka.log.info('Using native MSE as-is.');
|
||||
}
|
||||
|
||||
for (const codec of device.rejectCodecs()) {
|
||||
shaka.log.info(`Rejecting ${codec}.`);
|
||||
shaka.polyfill.MediaSource.rejectCodec_(codec);
|
||||
}
|
||||
|
||||
if (window.MediaSource || window.ManagedMediaSource) {
|
||||
// TS content is broken on all browsers in general.
|
||||
// See https://github.com/shaka-project/shaka-player/issues/4955
|
||||
@@ -212,7 +201,7 @@ shaka.polyfill.MediaSource = class {
|
||||
static patchVp09_() {
|
||||
const originalIsTypeSupported = MediaSource.isTypeSupported;
|
||||
|
||||
if (shaka.util.Platform.isWebOS()) {
|
||||
if (!shaka.device.DeviceFactory.getDevice().supportStandardVP9Checking()) {
|
||||
// Don't do this on LG webOS as otherwise it is unable
|
||||
// to play vp09 at all.
|
||||
return;
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
goog.provide('shaka.polyfill.PatchedMediaKeysCert');
|
||||
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.polyfill');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
|
||||
/**
|
||||
@@ -26,9 +26,10 @@ shaka.polyfill.PatchedMediaKeysCert = class {
|
||||
// No MediaKeys available
|
||||
return;
|
||||
}
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
if (MediaKeys.prototype.setServerCertificate &&
|
||||
!shaka.polyfill.PatchedMediaKeysCert.hasInvalidImplementation_()) {
|
||||
device.supportsServerCertificate()) {
|
||||
// setServerCertificate is there and userAgent seems to be valid.
|
||||
return;
|
||||
}
|
||||
@@ -48,15 +49,6 @@ shaka.polyfill.PatchedMediaKeysCert = class {
|
||||
shaka.log.debug('PatchedMediaKeysCert.setServerCertificate');
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static hasInvalidImplementation_() {
|
||||
return shaka.util.Platform.isTizen3() || shaka.util.Platform.isTizen4() ||
|
||||
shaka.util.Platform.isTizen5_0() || shaka.util.Platform.isWebOS3();
|
||||
}
|
||||
};
|
||||
|
||||
shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysCert.install);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
goog.provide('shaka.polyfill.VideoPlaybackQuality');
|
||||
|
||||
goog.require('shaka.polyfill');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Dom');
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,8 @@ shaka.polyfill.VideoPlaybackQuality = class {
|
||||
}
|
||||
|
||||
if ('webkitDroppedFrameCount' in proto ||
|
||||
shaka.util.Platform.isWebOS3()) {
|
||||
typeof shaka.util.Dom.anyMediaElement().webkitDroppedFrameCount ===
|
||||
'number') {
|
||||
proto.getVideoPlaybackQuality =
|
||||
shaka.polyfill.VideoPlaybackQuality.webkit_;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
goog.provide('shaka.text.NativeTextDisplayer');
|
||||
|
||||
goog.require('mozilla.LanguageMapping');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.text.Utils');
|
||||
goog.require('shaka.util.EventManager');
|
||||
goog.require('shaka.util.FakeEvent');
|
||||
goog.require('shaka.util.LanguageUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Timer');
|
||||
goog.requireType('shaka.Player');
|
||||
|
||||
@@ -87,7 +88,9 @@ shaka.text.NativeTextDisplayer = class {
|
||||
if (track.language in mozilla.LanguageMapping) {
|
||||
trackNode.srclang = track.language;
|
||||
}
|
||||
if (shaka.util.Platform.isChrome()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.CHROMIUM) {
|
||||
// The built-in captions menu in Chrome may refuse to list invalid
|
||||
// subtitles. The data URL is just to avoid this.
|
||||
trackNode.src = 'data:,WEBVTT';
|
||||
@@ -324,7 +327,9 @@ shaka.text.NativeTextDisplayer = class {
|
||||
* @private
|
||||
*/
|
||||
static getTrackKind_(track) {
|
||||
if (track.forced && shaka.util.Platform.isApple()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (track.forced && device.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT) {
|
||||
return 'forced';
|
||||
} else if (
|
||||
track.kind === 'caption' || (
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
goog.provide('shaka.transmuxer.Ac3Transmuxer');
|
||||
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.media.Capabilities');
|
||||
goog.require('shaka.transmuxer.Ac3');
|
||||
goog.require('shaka.transmuxer.TransmuxerEngine');
|
||||
@@ -14,7 +15,6 @@ goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.Id3Utils');
|
||||
goog.require('shaka.util.ManifestParserUtils');
|
||||
goog.require('shaka.util.Mp4Generator');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Uint8ArrayUtils');
|
||||
|
||||
|
||||
@@ -87,7 +87,8 @@ shaka.transmuxer.Ac3Transmuxer = class {
|
||||
*/
|
||||
convertCodecs(contentType, mimeType) {
|
||||
if (this.isAc3Container_(mimeType)) {
|
||||
if (shaka.util.Platform.requiresEC3InitSegments()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.requiresEC3InitSegments()) {
|
||||
return 'audio/mp4; codecs="ec-3"';
|
||||
} else {
|
||||
return 'audio/mp4; codecs="ac-3"';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
goog.provide('shaka.util.Dom');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.util.Timer');
|
||||
|
||||
|
||||
// TODO: revisit this when Closure Compiler supports partially-exported classes.
|
||||
@@ -159,6 +160,41 @@ shaka.util.Dom = class {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (shaka.util.Dom.cachedMediaElement_) {
|
||||
return shaka.util.Dom.cachedMediaElement_;
|
||||
}
|
||||
|
||||
if (!shaka.util.Dom.cacheExpirationTimer_) {
|
||||
shaka.util.Dom.cacheExpirationTimer_ = new shaka.util.Timer(() => {
|
||||
shaka.util.Dom.cachedMediaElement_ = null;
|
||||
});
|
||||
}
|
||||
|
||||
shaka.util.Dom.cachedMediaElement_ = /** @type {HTMLMediaElement} */(
|
||||
document.getElementsByTagName('video')[0] ||
|
||||
document.getElementsByTagName('audio')[0]);
|
||||
|
||||
if (!shaka.util.Dom.cachedMediaElement_) {
|
||||
shaka.util.Dom.cachedMediaElement_ = /** @type {!HTMLMediaElement} */(
|
||||
document.createElement('video'));
|
||||
}
|
||||
|
||||
shaka.util.Dom.cacheExpirationTimer_.tickAfter(/* seconds= */ 1);
|
||||
return shaka.util.Dom.cachedMediaElement_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a new font on the page. If the font was already loaded, it does
|
||||
* nothing.
|
||||
@@ -196,3 +232,9 @@ shaka.util.Dom = class {
|
||||
}
|
||||
};
|
||||
|
||||
/** @private {shaka.util.Timer} */
|
||||
shaka.util.Dom.cacheExpirationTimer_ = null;
|
||||
|
||||
/** @private {HTMLMediaElement} */
|
||||
shaka.util.Dom.cachedMediaElement_ = null;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
goog.provide('shaka.util.Mp4Generator');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.util.ManifestParserUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Uint8ArrayUtils');
|
||||
|
||||
|
||||
@@ -303,7 +303,8 @@ shaka.util.Mp4Generator = class {
|
||||
if (streamInfo.codecs.includes('mp3')) {
|
||||
audioCodec = 'mp3';
|
||||
} else if (streamInfo.codecs.includes('ac-3')) {
|
||||
if (shaka.util.Platform.requiresEC3InitSegments()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.requiresEC3InitSegments()) {
|
||||
audioCodec = 'ec-3';
|
||||
} else {
|
||||
audioCodec = 'ac-3';
|
||||
|
||||
@@ -1,932 +0,0 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('shaka.util.Platform');
|
||||
|
||||
goog.require('shaka.drm.DrmUtils');
|
||||
goog.require('shaka.log');
|
||||
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() {
|
||||
const mediaSource = window.ManagedMediaSource || window.MediaSource;
|
||||
// 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 (!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 6 TV.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isTizen6() {
|
||||
return shaka.util.Platform.userAgentContains_('Tizen 6');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a Tizen 5.0 TV.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isTizen5_0() {
|
||||
return shaka.util.Platform.userAgentContains_('Tizen 5.0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a Tizen 5 TV.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isTizen5() {
|
||||
return shaka.util.Platform.userAgentContains_('Tizen 5');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
||||
return shaka.util.Platform.isWebOS() &&
|
||||
shaka.util.Platform.chromeVersion() === 38;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a WebOS 4.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isWebOS4() {
|
||||
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
||||
return shaka.util.Platform.isWebOS() &&
|
||||
shaka.util.Platform.chromeVersion() === 53;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a WebOS 5.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isWebOS5() {
|
||||
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
||||
return shaka.util.Platform.isWebOS() &&
|
||||
shaka.util.Platform.chromeVersion() === 68;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a WebOS 6.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isWebOS6() {
|
||||
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
||||
return shaka.util.Platform.isWebOS() &&
|
||||
shaka.util.Platform.chromeVersion() === 79;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a Google Chromecast.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isChromecast() {
|
||||
const Platform = shaka.util.Platform;
|
||||
return Platform.userAgentContains_('CrKey') && !Platform.isVizio();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a Google Chromecast without
|
||||
* Android or Fuchsia.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isOlderChromecast() {
|
||||
const Platform = shaka.util.Platform;
|
||||
return Platform.isChromecast() &&
|
||||
!Platform.isAndroid() && !Platform.isFuchsia();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a Google Chromecast with Android
|
||||
* (i.e. Chromecast with GoogleTV).
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isAndroidCastDevice() {
|
||||
const Platform = shaka.util.Platform;
|
||||
return Platform.isChromecast() && Platform.isAndroid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is a Google Chromecast with Fuchsia
|
||||
* (i.e. Google Nest Hub).
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isFuchsiaCastDevice() {
|
||||
const Platform = shaka.util.Platform;
|
||||
return Platform.isChromecast() && Platform.isFuchsia();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Legacy user agent will also contain the "Chrome" keyword, so we
|
||||
// need to make sure this is not Edge Legacy.
|
||||
return shaka.util.Platform.userAgentContains_('Chrome') &&
|
||||
!shaka.util.Platform.isLegacyEdge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Firefox.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isFirefox() {
|
||||
return shaka.util.Platform.userAgentContains_('Firefox');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 shaka.util.Platform.isAppleVendor_() &&
|
||||
(shaka.util.Platform.isMac() || shaka.util.Platform.isIOS());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isPS4() {
|
||||
return shaka.util.Platform.userAgentContains_('PlayStation 4');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Sony TV.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isSonyTV() {
|
||||
return shaka.util.Platform.userAgentContains_('sony.hbbtv.tv.G5');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Hisense.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isHisense() {
|
||||
return shaka.util.Platform.userAgentContains_('Hisense') ||
|
||||
shaka.util.Platform.userAgentContains_('VIDAA');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Vizio TV.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isVizio() {
|
||||
return shaka.util.Platform.userAgentContains_('VIZIO SmartCast');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Orange.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isOrange() {
|
||||
return shaka.util.Platform.userAgentContains_('SOPOpenBrowser');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is SkyQ STB.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isSkyQ() {
|
||||
return shaka.util.Platform.userAgentContains_('Sky_STB');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform is Deutsche Telecom Zenterio STB.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isZenterio() {
|
||||
return shaka.util.Platform.userAgentContains_('DT_STB_BCM');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a major version number for Safari, or Webkit-based STBs,
|
||||
* 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() && !shaka.util.Platform.isWebkitSTB()) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses if the platform is an Apple mobile one (iOS, iPad, iPod).
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isIOS() {
|
||||
if (/(?:iPhone|iPad|iPod)/.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.isAppleVendor_() && navigator.maxTouchPoints > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses if the platform is a mobile one.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isMobile() {
|
||||
if (navigator.userAgentData) {
|
||||
return navigator.userAgentData.mobile;
|
||||
}
|
||||
return shaka.util.Platform.isIOS() || shaka.util.Platform.isAndroid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the platform is a Mac, regardless of the browser.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isMac() {
|
||||
// Try the newer standard first.
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform) {
|
||||
return navigator.userAgentData.platform.toLowerCase() == 'macos';
|
||||
}
|
||||
// Fall back to the old API, with less strict matching.
|
||||
if (!navigator.platform) {
|
||||
return false;
|
||||
}
|
||||
return navigator.platform.toLowerCase().includes('mac');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the platform is a VisionOS.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isVisionOS() {
|
||||
if (!shaka.util.Platform.isMac()) {
|
||||
return false;
|
||||
}
|
||||
if (!('xr' in navigator)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks is non-Apple STB based on Webkit.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isWebkitSTB() {
|
||||
return shaka.util.Platform.isAppleVendor_() &&
|
||||
!shaka.util.Platform.isApple();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the platform is a Windows, regardless of the browser.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isWindows() {
|
||||
// Try the newer standard first.
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform) {
|
||||
return navigator.userAgentData.platform.toLowerCase() == 'windows';
|
||||
}
|
||||
// Fall back to the old API, with less strict matching.
|
||||
if (!navigator.platform) {
|
||||
return false;
|
||||
}
|
||||
return navigator.platform.toLowerCase().includes('win32');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the platform is a Android, regardless of the browser.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isAndroid() {
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform) {
|
||||
return navigator.userAgentData.platform.toLowerCase() == 'android';
|
||||
}
|
||||
return shaka.util.Platform.userAgentContains_('Android');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the platform is a Fuchsia, regardless of the browser.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isFuchsia() {
|
||||
if (navigator.userAgentData && navigator.userAgentData.platform) {
|
||||
return navigator.userAgentData.platform.toLowerCase() == 'fuchsia';
|
||||
}
|
||||
return shaka.util.Platform.userAgentContains_('Fuchsia');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the platform is controlled by a remote control.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isSmartTV() {
|
||||
const Platform = shaka.util.Platform;
|
||||
if (Platform.isTizen() || Platform.isWebOS() ||
|
||||
Platform.isXboxOne() || Platform.isPS4() ||
|
||||
Platform.isPS5() || Platform.isChromecast() ||
|
||||
Platform.isHisense() || Platform.isVizio() ||
|
||||
Platform.isWebkitSTB()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static isAppleVendor_() {
|
||||
return (navigator.vendor || '').includes('Apple');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {?string} keySystem
|
||||
* @param {string} contentType
|
||||
* @return {boolean}
|
||||
* @see https://github.com/shaka-project/shaka-player/issues/2759
|
||||
*/
|
||||
static requiresEncryptionInfoInAllInitSegments(keySystem, contentType) {
|
||||
const Platform = shaka.util.Platform;
|
||||
const isPlayReady = shaka.drm.DrmUtils.isPlayReadyKeySystem(keySystem);
|
||||
return (Platform.isApple() && contentType === 'audio') ||
|
||||
Platform.isTizen() || Platform.isXboxOne() || Platform.isOrange() ||
|
||||
(Platform.isEdge() && Platform.isWindows() && isPlayReady);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} contentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
static requiresTfhdFix(contentType) {
|
||||
return shaka.util.Platform.isApple() && contentType === 'audio';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the platform requires AC-3 signalling in init
|
||||
* segments to be replaced with EC-3 signalling.
|
||||
* For such platforms, MediaSourceEngine will attempt to work
|
||||
* around it by inserting fake EC-3 signalling into
|
||||
* initialization segments.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static requiresEC3InitSegments() {
|
||||
return shaka.util.Platform.isTizen3();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the platform supports SourceBuffer "sequence mode".
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static supportsSequenceMode() {
|
||||
const Platform = shaka.util.Platform;
|
||||
if (Platform.isTizen3() || Platform.isTizen2() ||
|
||||
Platform.isWebOS3() || Platform.isPS4() || Platform.isPS5()) {
|
||||
return false;
|
||||
}
|
||||
// See: https://bugs.webkit.org/show_bug.cgi?id=210341
|
||||
const safariVersion = Platform.safariVersion();
|
||||
if (Platform.isWebkitSTB() && safariVersion != null && safariVersion < 15) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if codec switching SMOOTH is known reliable device support.
|
||||
*
|
||||
* Some devices are known not to support <code>SourceBuffer.changeType</code>
|
||||
* well. These devices should use the reload strategy. If a device
|
||||
* reports that it supports <code<changeType</code> but supports it unreliably
|
||||
* it should be disallowed in this method.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static supportsSmoothCodecSwitching() {
|
||||
const Platform = shaka.util.Platform;
|
||||
// All Tizen versions (up to Tizen 8) do not support SMOOTH so far.
|
||||
// webOS seems to support SMOOTH from webOS 22.
|
||||
if (Platform.isTizen() || Platform.isPS4() || Platform.isPS5() ||
|
||||
Platform.isWebOS6()) {
|
||||
return false;
|
||||
}
|
||||
// Older chromecasts without GoogleTV seem to not support SMOOTH properly.
|
||||
if (Platform.isOlderChromecast()) {
|
||||
return false;
|
||||
}
|
||||
// See: https://chromium-review.googlesource.com/c/chromium/src/+/4577759
|
||||
if (Platform.isWindows() && Platform.isEdge()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* On some platforms, such as v1 Chromecasts, the act of seeking can take a
|
||||
* significant amount of time.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isSeekingSlow() {
|
||||
const Platform = shaka.util.Platform;
|
||||
if (Platform.isChromecast()) {
|
||||
if (Platform.isAndroidCastDevice()) {
|
||||
// Android-based Chromecasts are new enough to not be a problem.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect the maximum resolution that the platform's hardware can handle.
|
||||
*
|
||||
* @return {!Promise<shaka.extern.Resolution>}
|
||||
*/
|
||||
static async detectMaxHardwareResolution() {
|
||||
const Platform = shaka.util.Platform;
|
||||
|
||||
/** @type {shaka.extern.Resolution} */
|
||||
const maxResolution = {
|
||||
width: Infinity,
|
||||
height: Infinity,
|
||||
};
|
||||
|
||||
if (Platform.isChromecast()) {
|
||||
// In our tests, the original Chromecast seems to have trouble decoding
|
||||
// above 1080p. It would be a waste to select a higher res anyway, given
|
||||
// that the device only outputs 1080p to begin with.
|
||||
// Chromecast has an extension to query the device/display's resolution.
|
||||
const hasCanDisplayType = window.cast && cast.__platform__ &&
|
||||
cast.__platform__.canDisplayType;
|
||||
|
||||
// Some hub devices can only do 720p. Default to that.
|
||||
maxResolution.width = 1280;
|
||||
maxResolution.height = 720;
|
||||
|
||||
try {
|
||||
if (hasCanDisplayType && await cast.__platform__.canDisplayType(
|
||||
'video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
|
||||
// The device and display can both do 4k. Assume a 4k limit.
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
} else if (hasCanDisplayType && await cast.__platform__.canDisplayType(
|
||||
'video/mp4; codecs="avc1.640028"; width=1920; height=1080')) {
|
||||
// Most Chromecasts can do 1080p.
|
||||
maxResolution.width = 1920;
|
||||
maxResolution.height = 1080;
|
||||
}
|
||||
} catch (error) {
|
||||
// This shouldn't generally happen. Log the error.
|
||||
shaka.log.alwaysError('Failed to check canDisplayType:', error);
|
||||
// Now ignore the error and let the 720p default stand.
|
||||
}
|
||||
} else if (Platform.isTizen()) {
|
||||
const devicePixelRatio = window.devicePixelRatio;
|
||||
maxResolution.width = window.screen.width * devicePixelRatio > 1920 ?
|
||||
3840 : 1920;
|
||||
maxResolution.height = window.screen.height * devicePixelRatio > 1080 ?
|
||||
2160 : 1080;
|
||||
try {
|
||||
if (webapis.systeminfo && webapis.systeminfo.getMaxVideoResolution) {
|
||||
const maxVideoResolution =
|
||||
webapis.systeminfo.getMaxVideoResolution();
|
||||
maxResolution.width = maxVideoResolution.width;
|
||||
maxResolution.height = maxVideoResolution.height;
|
||||
} else {
|
||||
if (webapis.productinfo.is8KPanelSupported &&
|
||||
webapis.productinfo.is8KPanelSupported()) {
|
||||
maxResolution.width = 7680;
|
||||
maxResolution.height = 4320;
|
||||
} else if (webapis.productinfo.isUdPanelSupported &&
|
||||
webapis.productinfo.isUdPanelSupported()) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn('Tizen: Error detecting screen size, default ' +
|
||||
'screen size 1920x1080.');
|
||||
}
|
||||
} else if (Platform.isWebOS()) {
|
||||
try {
|
||||
const deviceInfo =
|
||||
/** @type {{screenWidth: number, screenHeight: number}} */(
|
||||
JSON.parse(window.PalmSystem.deviceInfo));
|
||||
// WebOS has always been able to do 1080p. Assume a 1080p limit.
|
||||
maxResolution.width = Math.max(1920, deviceInfo['screenWidth']);
|
||||
maxResolution.height = Math.max(1080, deviceInfo['screenHeight']);
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn('WebOS: Error detecting screen size, default ' +
|
||||
'screen size 1920x1080.');
|
||||
maxResolution.width = 1920;
|
||||
maxResolution.height = 1080;
|
||||
}
|
||||
} else if (Platform.isHisense()) {
|
||||
let supports4k = null;
|
||||
if (window.Hisense_Get4KSupportState) {
|
||||
try {
|
||||
// eslint-disable-next-line new-cap
|
||||
supports4k = window.Hisense_Get4KSupportState();
|
||||
} catch (e) {
|
||||
shaka.log.debug('Hisense: Failed to get 4K support state', e);
|
||||
}
|
||||
}
|
||||
if (supports4k == null) {
|
||||
// If API is not there or not working for whatever reason, fallback to
|
||||
// user agent check, as it contains UHD or FHD info.
|
||||
supports4k = Platform.userAgentContains_('UHD');
|
||||
}
|
||||
if (supports4k) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
} else {
|
||||
maxResolution.width = 1920;
|
||||
maxResolution.height = 1080;
|
||||
}
|
||||
} else if (Platform.isPS4() || Platform.isPS5()) {
|
||||
let supports4K = false;
|
||||
try {
|
||||
const result = await window.msdk.device.getDisplayInfo();
|
||||
supports4K = result.resolution === '4K';
|
||||
} catch (e) {
|
||||
try {
|
||||
const result = await window.msdk.device.getDisplayInfoImmediate();
|
||||
supports4K = result.resolution === '4K';
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn(
|
||||
'PlayStation: Failed to get the display info:', e);
|
||||
}
|
||||
}
|
||||
if (supports4K) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
} else {
|
||||
maxResolution.width = 1920;
|
||||
maxResolution.height = 1080;
|
||||
}
|
||||
} else {
|
||||
// For Xbox and UWP apps.
|
||||
let winRT = undefined;
|
||||
try {
|
||||
// Try to access to WinRT for WebView, if it's not defined,
|
||||
// try to access to WinRT for WebView2, if it's not defined either,
|
||||
// let it throw.
|
||||
if (typeof Windows !== 'undefined') {
|
||||
winRT = Windows;
|
||||
} else {
|
||||
winRT = chrome.webview.hostObjects.sync.Windows;
|
||||
}
|
||||
} catch (e) {}
|
||||
if (winRT) {
|
||||
maxResolution.width = 1920;
|
||||
maxResolution.height = 1080;
|
||||
try {
|
||||
const protectionCapabilities =
|
||||
new winRT.Media.Protection.ProtectionCapabilities();
|
||||
const protectionResult =
|
||||
winRT.Media.Protection.ProtectionCapabilityResult;
|
||||
// isTypeSupported may return "maybe", which means the operation
|
||||
// is not completed. This means we need to retry
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
|
||||
let result = null;
|
||||
const type =
|
||||
'video/mp4;codecs="hvc1,mp4a";features="decode-res-x=3840,' +
|
||||
'decode-res-y=2160,decode-bitrate=20000,decode-fps=30,' +
|
||||
'decode-bpc=10,display-res-x=3840,display-res-y=2160,' +
|
||||
'display-bpc=8"';
|
||||
const keySystem = 'com.microsoft.playready.recommendation';
|
||||
do {
|
||||
result = protectionCapabilities.isTypeSupported(type, keySystem);
|
||||
} while (result === protectionResult.maybe);
|
||||
if (result === protectionResult.probably) {
|
||||
maxResolution.width = 3840;
|
||||
maxResolution.height = 2160;
|
||||
}
|
||||
} catch (e) {
|
||||
shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
|
||||
'screen size 1920x1080.');
|
||||
}
|
||||
} else if (Platform.isXboxOne()) {
|
||||
maxResolution.width = 1920;
|
||||
maxResolution.height = 1080;
|
||||
shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
|
||||
'screen size 1920x1080.');
|
||||
}
|
||||
}
|
||||
return maxResolution;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check the current HDR level supported by the screen.
|
||||
*
|
||||
* @param {boolean} preferHLG
|
||||
* @return {string}
|
||||
*/
|
||||
static getHdrLevel(preferHLG) {
|
||||
if (window.matchMedia !== undefined &&
|
||||
window.matchMedia('(color-gamut: p3)').matches) {
|
||||
return preferHLG ? 'HLG' : 'PQ';
|
||||
}
|
||||
return 'SDR';
|
||||
}
|
||||
};
|
||||
|
||||
/** @private {shaka.util.Timer} */
|
||||
shaka.util.Platform.cacheExpirationTimer_ = null;
|
||||
|
||||
/** @private {HTMLMediaElement} */
|
||||
shaka.util.Platform.cachedMediaElement_ = null;
|
||||
@@ -12,6 +12,7 @@ goog.require('shaka.config.AutoShowText');
|
||||
goog.require('shaka.config.CodecSwitchingStrategy');
|
||||
goog.require('shaka.config.CrossBoundaryStrategy');
|
||||
goog.require('shaka.config.RepeatMode');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.drm.DrmUtils');
|
||||
goog.require('shaka.drm.FairPlay');
|
||||
goog.require('shaka.log');
|
||||
@@ -21,7 +22,6 @@ goog.require('shaka.net.NetworkingEngine');
|
||||
goog.require('shaka.util.ConfigUtils');
|
||||
goog.require('shaka.util.LanguageUtils');
|
||||
goog.require('shaka.util.ManifestParserUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
|
||||
/**
|
||||
@@ -41,6 +41,8 @@ shaka.util.PlayerConfiguration = class {
|
||||
|
||||
let abrMaxHeight = Infinity;
|
||||
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
|
||||
// Some browsers implement the Network Information API, which allows
|
||||
// retrieving information about a user's network connection.
|
||||
if (navigator.connection) {
|
||||
@@ -92,36 +94,17 @@ shaka.util.PlayerConfiguration = class {
|
||||
updateExpirationTime: 1,
|
||||
preferredKeySystems: [],
|
||||
keySystemsMapping: {},
|
||||
// The Xbox One browser does not detect DRM key changes signalled by a
|
||||
// change in the PSSH in media segments. We need to parse PSSH from media
|
||||
// segments to detect key changes.
|
||||
parseInbandPsshEnabled: shaka.util.Platform.isXboxOne(),
|
||||
parseInbandPsshEnabled: false,
|
||||
minHdcpVersion: '',
|
||||
ignoreDuplicateInitData: !shaka.util.Platform.isTizen2(),
|
||||
ignoreDuplicateInitData: true,
|
||||
defaultAudioRobustnessForWidevine: 'SW_SECURE_CRYPTO',
|
||||
defaultVideoRobustnessForWidevine: 'SW_SECURE_DECODE',
|
||||
};
|
||||
|
||||
// The Xbox One and PS4 only support the Playready DRM, so they should
|
||||
// prefer that key system by default to improve startup performance.
|
||||
if (shaka.util.Platform.isXboxOne() ||
|
||||
shaka.util.Platform.isPS4()) {
|
||||
drm.preferredKeySystems.push('com.microsoft.playready');
|
||||
}
|
||||
|
||||
// Other browsers different than Edge only supports HW PlayReady with the
|
||||
// recommendation keysystem on Windows, so we do a direct mapping here.
|
||||
if (shaka.util.Platform.isWindows() && !shaka.util.Platform.isEdge()) {
|
||||
drm.keySystemsMapping = {
|
||||
'com.microsoft.playready':
|
||||
'com.microsoft.playready.recommendation',
|
||||
};
|
||||
}
|
||||
|
||||
let codecSwitchingStrategy = shaka.config.CodecSwitchingStrategy.RELOAD;
|
||||
let multiTypeVariantsAllowed = false;
|
||||
if (shaka.media.Capabilities.isChangeTypeSupported() &&
|
||||
shaka.util.Platform.supportsSmoothCodecSwitching()) {
|
||||
device.supportsSmoothCodecSwitching()) {
|
||||
codecSwitchingStrategy = shaka.config.CodecSwitchingStrategy.SMOOTH;
|
||||
multiTypeVariantsAllowed = true;
|
||||
}
|
||||
@@ -186,7 +169,7 @@ shaka.util.PlayerConfiguration = class {
|
||||
mediaPlaylistFullMimeType:
|
||||
'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"',
|
||||
liveSegmentsDelay: 3,
|
||||
sequenceMode: shaka.util.Platform.supportsSequenceMode(),
|
||||
sequenceMode: device.supportsSequenceMode(),
|
||||
ignoreManifestTimestampsInSegmentsMode: false,
|
||||
disableCodecGuessing: false,
|
||||
disableClosedCaptionsDetection: false,
|
||||
@@ -281,47 +264,14 @@ shaka.util.PlayerConfiguration = class {
|
||||
vodDynamicPlaybackRateBufferRatio: 0.5,
|
||||
preloadNextUrlWindow: 30,
|
||||
loadTimeout: 30,
|
||||
clearDecodingCache: shaka.util.Platform.isPS4() ||
|
||||
shaka.util.Platform.isPS5(),
|
||||
clearDecodingCache: false,
|
||||
dontChooseCodecs: false,
|
||||
shouldFixTimestampOffset: shaka.util.Platform.isWebOS() ||
|
||||
shaka.util.Platform.isTizen(),
|
||||
shouldFixTimestampOffset: false,
|
||||
avoidEvictionOnQuotaExceededError: false,
|
||||
crossBoundaryStrategy: shaka.config.CrossBoundaryStrategy.KEEP,
|
||||
returnToEndOfLiveWindowWhenOutside: false,
|
||||
};
|
||||
|
||||
// WebOS, Tizen, Chromecast and Hisense have long hardware pipelines
|
||||
// that respond slowly to seeking.
|
||||
// Therefore we should not seek when we detect a stall
|
||||
// on one of these platforms. Instead, default stallSkip to 0 to force the
|
||||
// stall detector to pause and play instead.
|
||||
if (shaka.util.Platform.isWebOS() ||
|
||||
shaka.util.Platform.isTizen() ||
|
||||
shaka.util.Platform.isChromecast() ||
|
||||
shaka.util.Platform.isHisense()) {
|
||||
streaming.stallSkip = 0;
|
||||
}
|
||||
|
||||
if (shaka.util.Platform.isLegacyEdge() ||
|
||||
shaka.util.Platform.isXboxOne()) {
|
||||
streaming.gapPadding = 0.01;
|
||||
}
|
||||
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
streaming.gapPadding = 2;
|
||||
}
|
||||
|
||||
if (shaka.util.Platform.isWebOS3()) {
|
||||
streaming.crossBoundaryStrategy =
|
||||
shaka.config.CrossBoundaryStrategy.RESET;
|
||||
}
|
||||
|
||||
if (shaka.util.Platform.isTizen3()) {
|
||||
streaming.crossBoundaryStrategy =
|
||||
shaka.config.CrossBoundaryStrategy.RESET_TO_ENCRYPTED;
|
||||
}
|
||||
|
||||
const networking = {
|
||||
forceHTTP: false,
|
||||
forceHTTPS: false,
|
||||
@@ -397,7 +347,7 @@ shaka.util.PlayerConfiguration = class {
|
||||
clearBufferSwitch: false,
|
||||
safeMarginSwitch: 0,
|
||||
cacheLoadThreshold: 20,
|
||||
minTimeToSwitch: shaka.util.Platform.isApple() ? 0.5 : 0,
|
||||
minTimeToSwitch: 0,
|
||||
preferNetworkInformationBandwidth: false,
|
||||
removeLatencyFromFirstPacketTime: true,
|
||||
};
|
||||
@@ -446,19 +396,10 @@ shaka.util.PlayerConfiguration = class {
|
||||
durationReductionEmitsUpdateEnd: true,
|
||||
};
|
||||
|
||||
let customPlayheadTracker = false;
|
||||
let skipPlayDetection = false;
|
||||
let supportsMultipleMediaElements = true;
|
||||
if (shaka.util.Platform.isSmartTV()) {
|
||||
customPlayheadTracker = true;
|
||||
skipPlayDetection = true;
|
||||
supportsMultipleMediaElements = false;
|
||||
}
|
||||
|
||||
const ads = {
|
||||
customPlayheadTracker,
|
||||
skipPlayDetection,
|
||||
supportsMultipleMediaElements,
|
||||
customPlayheadTracker: false,
|
||||
skipPlayDetection: false,
|
||||
supportsMultipleMediaElements: true,
|
||||
disableHLSInterstitial: false,
|
||||
disableDASHInterstitial: false,
|
||||
allowPreloadOnDomElements: true,
|
||||
@@ -541,7 +482,7 @@ shaka.util.PlayerConfiguration = class {
|
||||
config.preferredVideoHdrLevel);
|
||||
};
|
||||
|
||||
return config;
|
||||
return device.adjustConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -627,7 +568,8 @@ shaka.util.PlayerConfiguration = class {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
hdrLevel = shaka.util.Platform.getHdrLevel(someHLG);
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
hdrLevel = device.getHdrLevel(someHLG);
|
||||
}
|
||||
|
||||
/** @type {!Array<shaka.extern.Track>} */
|
||||
|
||||
+17
-10
@@ -8,6 +8,8 @@ goog.provide('shaka.util.StreamUtils');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.config.AutoShowText');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.lcevc.Dec');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.media.Capabilities');
|
||||
@@ -18,7 +20,6 @@ goog.require('shaka.util.ManifestParserUtils');
|
||||
goog.require('shaka.util.MimeUtils');
|
||||
goog.require('shaka.util.MultiMap');
|
||||
goog.require('shaka.util.ObjectUtils');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.requireType('shaka.drm.DrmEngine');
|
||||
|
||||
|
||||
@@ -426,7 +427,8 @@ shaka.util.StreamUtils = class {
|
||||
goog.asserts.assert(navigator.mediaCapabilities,
|
||||
'MediaCapabilities should be valid.');
|
||||
|
||||
if (shaka.util.Platform.isXboxOne()) {
|
||||
if (shaka.device.DeviceFactory.getDevice()
|
||||
.shouldOverrideDolbyVisionCodecs()) {
|
||||
shaka.util.StreamUtils.overrideDolbyVisionCodecs(manifest.variants);
|
||||
}
|
||||
await shaka.util.StreamUtils.getDecodingInfosForVariants(
|
||||
@@ -521,9 +523,11 @@ shaka.util.StreamUtils = class {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isXboxOne = shaka.util.Platform.isXboxOne();
|
||||
const isFirefoxAndroid = shaka.util.Platform.isFirefox() &&
|
||||
shaka.util.Platform.isAndroid();
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const isXboxOne = device.getDeviceName() === 'Xbox';
|
||||
const isFirefoxAndroid =
|
||||
device.getDeviceType() === shaka.device.IDevice.DeviceType.MOBILE &&
|
||||
device.getBrowserEngine() === shaka.device.IDevice.BrowserEngine.GECKO;
|
||||
|
||||
// See: https://github.com/shaka-project/shaka-player/issues/3860
|
||||
const video = variant.video;
|
||||
@@ -671,7 +675,9 @@ shaka.util.StreamUtils = class {
|
||||
// is not set. This is a workaround for that issue.
|
||||
const TIMEOUT_FOR_DECODING_INFO_IN_SECONDS = 5;
|
||||
let promise;
|
||||
if (shaka.util.Platform.isAndroid()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getDeviceType() ==
|
||||
shaka.device.IDevice.DeviceType.MOBILE) {
|
||||
promise = shaka.util.Functional.promiseWithTimeout(
|
||||
TIMEOUT_FOR_DECODING_INFO_IN_SECONDS,
|
||||
navigator.mediaCapabilities.decodingInfo(copy),
|
||||
@@ -1067,8 +1073,10 @@ shaka.util.StreamUtils = class {
|
||||
// currently don't support 'fLaC', while 'flac' is supported by most
|
||||
// major browsers.
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const webkit = shaka.device.IDevice.BrowserEngine.WEBKIT;
|
||||
if (codecs.toLowerCase() == 'flac') {
|
||||
if (!shaka.util.Platform.isApple()) {
|
||||
if (device.getBrowserEngine() != webkit) {
|
||||
return 'flac';
|
||||
} else {
|
||||
return 'fLaC';
|
||||
@@ -1077,7 +1085,7 @@ shaka.util.StreamUtils = class {
|
||||
|
||||
// The same is true for 'Opus'.
|
||||
if (codecs.toLowerCase() === 'opus') {
|
||||
if (!shaka.util.Platform.isApple()) {
|
||||
if (device.getBrowserEngine() != webkit) {
|
||||
return 'opus';
|
||||
} else {
|
||||
if (shaka.util.MimeUtils.getContainerType(mimeType) == 'mp4') {
|
||||
@@ -1088,8 +1096,7 @@ shaka.util.StreamUtils = class {
|
||||
}
|
||||
}
|
||||
|
||||
if (codecs.toLowerCase() == 'ac-3' &&
|
||||
shaka.util.Platform.requiresEC3InitSegments()) {
|
||||
if (codecs.toLowerCase() == 'ac-3' && device.requiresEC3InitSegments()) {
|
||||
return 'ec-3';
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
goog.provide('shaka.util.StringUtils');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.util.BufferUtils');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.Lazy');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
|
||||
/**
|
||||
@@ -38,7 +38,8 @@ shaka.util.StringUtils = class {
|
||||
uint8 = uint8.subarray(3);
|
||||
}
|
||||
|
||||
if (window.TextDecoder && !shaka.util.Platform.isPS4()) {
|
||||
if (window.TextDecoder && !shaka.device.DeviceFactory.getDevice()
|
||||
.shouldAvoidUseTextDecoderEncoder()) {
|
||||
// Use the TextDecoder interface to decode the text. This has the
|
||||
// advantage compared to the previously-standard decodeUriComponent that
|
||||
// it will continue parsing even if it finds an invalid UTF8 character,
|
||||
@@ -208,7 +209,8 @@ shaka.util.StringUtils = class {
|
||||
* @export
|
||||
*/
|
||||
static toUTF8(str) {
|
||||
if (window.TextEncoder && !shaka.util.Platform.isPS4()) {
|
||||
if (window.TextEncoder && !shaka.device.DeviceFactory.getDevice()
|
||||
.shouldAvoidUseTextDecoderEncoder()) {
|
||||
const utf8Encoder = new TextEncoder();
|
||||
return shaka.util.BufferUtils.toArrayBuffer(utf8Encoder.encode(str));
|
||||
} else {
|
||||
|
||||
@@ -16,6 +16,16 @@ goog.require('shaka.ads.AdManager');
|
||||
goog.require('shaka.cast.CastProxy');
|
||||
goog.require('shaka.cast.CastReceiver');
|
||||
goog.require('shaka.dash.DashParser');
|
||||
goog.require('shaka.device.AppleBrowser');
|
||||
goog.require('shaka.device.Chromecast');
|
||||
goog.require('shaka.device.DefaultBrowser');
|
||||
goog.require('shaka.device.Hisense');
|
||||
goog.require('shaka.device.PlayStation');
|
||||
goog.require('shaka.device.Tizen');
|
||||
goog.require('shaka.device.Vizio');
|
||||
goog.require('shaka.device.WebKitSTB');
|
||||
goog.require('shaka.device.WebOS');
|
||||
goog.require('shaka.device.Xbox');
|
||||
goog.require('shaka.drm.FairPlay');
|
||||
goog.require('shaka.hls.HlsParser');
|
||||
goog.require('shaka.mss.MssParser');
|
||||
|
||||
@@ -24,7 +24,8 @@ describe('Interstitial Ad manager', () => {
|
||||
beforeEach(() => {
|
||||
// Allows us to use a timer instead of requestVideoFrameCallback
|
||||
// (which doesn't work well in all platform tests)
|
||||
spyOn(shaka.util.Platform, 'isSmartTV').and.returnValue(true);
|
||||
spyOn(deviceDetected, 'getDeviceType')
|
||||
.and.returnValue(shaka.device.IDevice.DeviceType.TV);
|
||||
|
||||
function dependencyInjector(player) {
|
||||
// Create a networking engine that always returns an empty buffer.
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('Ads', () => {
|
||||
const streamUri = '/base/test/test/assets/hls-interstitial/main.m3u8';
|
||||
|
||||
it('with support for multiple media elements', async () => {
|
||||
if (shaka.util.Platform.isSmartTV()) {
|
||||
if (!player.getConfiguration().ads.supportsMultipleMediaElements) {
|
||||
pending('Platform without support for multiple media elements.');
|
||||
}
|
||||
player.configure('ads.supportsMultipleMediaElements', true);
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
// only be run on Chrome and Chromecast.
|
||||
/** @return {boolean} */
|
||||
const castReceiverIntegrationSupport =
|
||||
() => shaka.util.Platform.isChrome() || shaka.util.Platform.isChromecast();
|
||||
() => deviceDetected.getDeviceName() === 'Chrome' ||
|
||||
deviceDetected.getDeviceType() === shaka.device.IDevice.DeviceType.CAST;
|
||||
filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
||||
const CastReceiver = shaka.cast.CastReceiver;
|
||||
const CastUtils = shaka.cast.CastUtils;
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
// only be run on Chrome and Chromecast.
|
||||
/** @return {boolean} */
|
||||
const castReceiverSupport =
|
||||
() => shaka.util.Platform.isChrome() || shaka.util.Platform.isChromecast();
|
||||
() => deviceDetected.getDeviceName() === 'Chrome' ||
|
||||
deviceDetected.getDeviceType() === shaka.device.IDevice.DeviceType.CAST;
|
||||
filterDescribe('CastReceiver', castReceiverSupport, () => {
|
||||
const CastReceiver = shaka.cast.CastReceiver;
|
||||
const CastUtils = shaka.cast.CastUtils;
|
||||
@@ -54,6 +55,8 @@ filterDescribe('CastReceiver', castReceiverSupport, () => {
|
||||
mockReceiverApi = createMockReceiverApi();
|
||||
mockCanDisplayType = jasmine.createSpy('canDisplayType');
|
||||
mockCanDisplayType.and.returnValue(false);
|
||||
spyOn(deviceDetected, 'getDeviceType').and
|
||||
.returnValue(shaka.device.IDevice.DeviceType.CAST);
|
||||
|
||||
// We're using quotes to access window.cast because the compiler
|
||||
// knows about lots of Cast-specific APIs we aren't mocking. We
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('Codec Switching', () => {
|
||||
if (!shaka.media.Capabilities.isChangeTypeSupported()) {
|
||||
pending('SourceBuffer.changeType is not supported');
|
||||
}
|
||||
if (!shaka.util.Platform.supportsSmoothCodecSwitching()) {
|
||||
if (!deviceDetected.supportsSmoothCodecSwitching()) {
|
||||
pending('SourceBuffer.changeType is not considered ' +
|
||||
'reliable on this device');
|
||||
}
|
||||
@@ -172,7 +172,7 @@ describe('Codec Switching', () => {
|
||||
if (!shaka.media.Capabilities.isChangeTypeSupported()) {
|
||||
pending('SourceBuffer.changeType is not supported');
|
||||
}
|
||||
if (!shaka.util.Platform.supportsSmoothCodecSwitching()) {
|
||||
if (!deviceDetected.supportsSmoothCodecSwitching()) {
|
||||
pending('SourceBuffer.changeType is not considered ' +
|
||||
'reliable on this device');
|
||||
}
|
||||
@@ -248,7 +248,7 @@ describe('Codec Switching', () => {
|
||||
if (!shaka.media.Capabilities.isChangeTypeSupported()) {
|
||||
pending('SourceBuffer.changeType is not supported');
|
||||
}
|
||||
if (!shaka.util.Platform.supportsSmoothCodecSwitching()) {
|
||||
if (!deviceDetected.supportsSmoothCodecSwitching()) {
|
||||
pending('SourceBuffer.changeType is not considered ' +
|
||||
'reliable on this device');
|
||||
}
|
||||
@@ -325,7 +325,7 @@ describe('Codec Switching', () => {
|
||||
if (!shaka.media.Capabilities.isChangeTypeSupported()) {
|
||||
pending('SourceBuffer.changeType is not supported');
|
||||
}
|
||||
if (!shaka.util.Platform.supportsSmoothCodecSwitching()) {
|
||||
if (!deviceDetected.supportsSmoothCodecSwitching()) {
|
||||
pending('SourceBuffer.changeType is not considered ' +
|
||||
'reliable on this device');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
describe('Chromecast', () => {
|
||||
/* eslint-disable @stylistic/max-len */
|
||||
// cspell: disable-next-line
|
||||
const vizio = 'Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 CrKey/1.0.999999 VIZIO SmartCast(Conjure/MTKF-5.1.516.1 FW/0.6.11.1-2 Model/V50C6-J09)';
|
||||
// cspell: disable-next-line
|
||||
const chromecastBuiltinOrOlder = 'Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.225 Safari/537.36 CrKey/1.56.500000 DeviceType/Chromecast';
|
||||
// cspell: disable-next-line
|
||||
const chromecastFuchsia = 'Mozilla/5.0 (Fuchsia) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 CrKey/1.56.500000';
|
||||
// cspell: disable-next-line
|
||||
const chromecastAndroid = 'Mozilla/5.0 (Linux; Android 12; Build/STTL.240206.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.0 Safari/537.36 CrKey/1.56.500000 DeviceType/AndroidTV';
|
||||
/* eslint-enable @stylistic/max-len */
|
||||
|
||||
const Chromecast = shaka.device.Chromecast;
|
||||
const Util = shaka.test.Util;
|
||||
const originalUserAgent = navigator.userAgent;
|
||||
const userAgentData = navigator.userAgentData;
|
||||
|
||||
afterEach(() => {
|
||||
Util.setUserAgent(originalUserAgent);
|
||||
Util.setUserAgentData(userAgentData);
|
||||
});
|
||||
|
||||
it('checks Chromecast OS type', () => {
|
||||
Util.setUserAgentData(null);
|
||||
Util.setUserAgent(vizio);
|
||||
expect(() => new Chromecast()).toThrow();
|
||||
Util.setUserAgent(chromecastBuiltinOrOlder);
|
||||
expect(new Chromecast().getDeviceName()).toContain('Linux');
|
||||
Util.setUserAgent(chromecastFuchsia);
|
||||
expect(new Chromecast().getDeviceName()).toContain('Fuchsia');
|
||||
Util.setUserAgent(chromecastAndroid);
|
||||
expect(new Chromecast().getDeviceName()).toContain('Android');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
describe('Tizen', () => {
|
||||
/* eslint-disable @stylistic/max-len */
|
||||
// See: https://developer.samsung.com/smarttv/develop/guides/fundamentals/retrieving-platform-information.html
|
||||
const tizen50 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0 TV Safari/537.36';
|
||||
const tizen55 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 5.5) AppleWebKit/537.36 (KHTML, like Gecko) 69.0.3497.106.1/5.5 TV Safari/537.36';
|
||||
const tizen60 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 6.0) AppleWebKit/537.36 (KHTML, like Gecko) 76.0.3809.146/6.0 TV Safari/537.36';
|
||||
const tizen65 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 6.5) AppleWebKit/537.36 (KHTML, like Gecko) 85.0.4183.93/6.5 TV Safari/537.36';
|
||||
const tizen70 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) 94.0.4606.31/7.0 TV Safari/537.36';
|
||||
|
||||
const webOs3 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.2.1 Chrome/38.0.2125.122 Safari/537.36 WebAppManager';
|
||||
/* eslint-enable @stylistic/max-len */
|
||||
|
||||
const Util = shaka.test.Util;
|
||||
const originalUserAgent = navigator.userAgent;
|
||||
|
||||
afterEach(() => {
|
||||
Util.setUserAgent(originalUserAgent);
|
||||
});
|
||||
|
||||
it('checks Tizen version', () => {
|
||||
Util.setUserAgent(webOs3);
|
||||
expect(new shaka.device.Tizen().getVersion()).toBe(null);
|
||||
Util.setUserAgent(tizen50);
|
||||
expect(new shaka.device.Tizen().getVersion()).toBe(5);
|
||||
Util.setUserAgent(tizen55);
|
||||
expect(new shaka.device.Tizen().getVersion()).toBe(5);
|
||||
Util.setUserAgent(tizen60);
|
||||
expect(new shaka.device.Tizen().getVersion()).toBe(6);
|
||||
Util.setUserAgent(tizen65);
|
||||
expect(new shaka.device.Tizen().getVersion()).toBe(6);
|
||||
Util.setUserAgent(tizen70);
|
||||
expect(new shaka.device.Tizen().getVersion()).toBe(7);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
describe('WebOS', () => {
|
||||
/* eslint-disable @stylistic/max-len */
|
||||
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
||||
const webOs3 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.2.1 Chrome/38.0.2125.122 Safari/537.36 WebAppManager';
|
||||
const webOs4 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.34 Safari/537.36 WebAppManager';
|
||||
const webOs5 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 WebAppManager';
|
||||
const webOs6 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36 WebAppManager';
|
||||
|
||||
const tizen50 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0 TV Safari/537.36';
|
||||
/* eslint-enable @stylistic/max-len */
|
||||
|
||||
const Util = shaka.test.Util;
|
||||
const originalUserAgent = navigator.userAgent;
|
||||
|
||||
afterEach(() => {
|
||||
Util.setUserAgent(originalUserAgent);
|
||||
});
|
||||
|
||||
it('checks webOS version', () => {
|
||||
Util.setUserAgent(tizen50);
|
||||
expect(new shaka.device.WebOS().getVersion()).toBe(null);
|
||||
Util.setUserAgent(webOs3);
|
||||
expect(new shaka.device.WebOS().getVersion()).toBe(3);
|
||||
Util.setUserAgent(webOs4);
|
||||
expect(new shaka.device.WebOS().getVersion()).toBe(4);
|
||||
Util.setUserAgent(webOs5);
|
||||
expect(new shaka.device.WebOS().getVersion()).toBe(5);
|
||||
Util.setUserAgent(webOs6);
|
||||
expect(new shaka.device.WebOS().getVersion()).toBe(6);
|
||||
});
|
||||
});
|
||||
@@ -104,7 +104,7 @@ describe('LCEVC Integration', () => {
|
||||
|
||||
describe('SEI Integration', () => {
|
||||
it('Should decode LCEVC in FMP4 DASH manifest', async () => {
|
||||
if (shaka.util.Platform.isTizen() || shaka.util.Platform.isChromecast()) {
|
||||
if (isPlatformUnsupported()) {
|
||||
pending('Disabled on unsupported platform.');
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('LCEVC Integration', () => {
|
||||
});
|
||||
|
||||
it('Should decode LCEVC in FMP4 HLS manifest', async () => {
|
||||
if (shaka.util.Platform.isTizen() || shaka.util.Platform.isChromecast()) {
|
||||
if (isPlatformUnsupported()) {
|
||||
pending('Disabled on unsupported platform.');
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ describe('LCEVC Integration', () => {
|
||||
});
|
||||
|
||||
it('Should decode LCEVC in TS HLS manifest', async () => {
|
||||
if (shaka.util.Platform.isTizen() || shaka.util.Platform.isChromecast()) {
|
||||
if (isPlatformUnsupported()) {
|
||||
pending('Disabled on unsupported platform.');
|
||||
}
|
||||
|
||||
@@ -142,4 +142,12 @@ describe('LCEVC Integration', () => {
|
||||
await testPlayback(seiManifests.TS_HLS);
|
||||
});
|
||||
});
|
||||
|
||||
/** @return {boolean} */
|
||||
function isPlatformUnsupported() {
|
||||
const DeviceType = shaka.device.IDevice.DeviceType;
|
||||
return deviceDetected.getDeviceType() === DeviceType.CAST ||
|
||||
(deviceDetected.getDeviceType() === DeviceType.TV &&
|
||||
deviceDetected.getDeviceName() === 'Tizen');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,7 +108,8 @@ describe('ContentWorkarounds', () => {
|
||||
if (!shakaSupport.drm[keySystem]) {
|
||||
pending('Needed DRM is not supported on this platform');
|
||||
}
|
||||
if (shaka.util.Platform.isTizen3()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen' &&
|
||||
deviceDetected.getVersion() === 3) {
|
||||
pending('Tizen 3 currently does not support mixed clear ' +
|
||||
'encrypted content');
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@ describe('ContentWorkarounds', () => {
|
||||
}
|
||||
|
||||
it('faked encryption on Edge returns two init segments', () => {
|
||||
spyOn(shaka.util.Platform, 'isEdge').and.returnValue(true);
|
||||
spyOn(shaka.util.Platform, 'isWindows').and.returnValue(true);
|
||||
spyOn(deviceDetected, 'requiresClearAndEncryptedInitSegments')
|
||||
.and.returnValue(true);
|
||||
|
||||
const unencrypted = new Uint8Array([
|
||||
0x00, 0x00, 0x00, 0x20, // size
|
||||
|
||||
@@ -710,7 +710,7 @@ describe('MediaSourceEngine', () => {
|
||||
|
||||
it('extracts ID3 metadata from AAC', async () => {
|
||||
if (!MediaSource.isTypeSupported('audio/aac') ||
|
||||
!shaka.util.Platform.supportsSequenceMode()) {
|
||||
!deviceDetected.supportsSequenceMode()) {
|
||||
pending('Raw AAC codec is not supported by the platform.');
|
||||
}
|
||||
metadata = shaka.test.TestScheme.DATA['id3-metadata_aac'];
|
||||
|
||||
@@ -213,10 +213,10 @@ describe('MediaSourceEngine', () => {
|
||||
shaka.media.MediaSourceEngine.prototype.createMediaSource =
|
||||
Util.spyFunc(createMediaSourceSpy);
|
||||
|
||||
requiresEncryptionInfoInAllInitSegmentsSpy = spyOn(shaka.util.Platform,
|
||||
requiresEncryptionInfoInAllInitSegmentsSpy = spyOn(deviceDetected,
|
||||
'requiresEncryptionInfoInAllInitSegments').and.returnValue(false);
|
||||
|
||||
requiresEC3InitSegments = spyOn(shaka.util.Platform,
|
||||
requiresEC3InitSegments = spyOn(deviceDetected,
|
||||
'requiresEC3InitSegments').and.returnValue(false);
|
||||
|
||||
fakeEncryptionSpy = spyOn(shaka.media.ContentWorkarounds, 'fakeEncryption')
|
||||
|
||||
@@ -173,9 +173,10 @@ describe('Playhead', () => {
|
||||
|
||||
function calculateGap(time) {
|
||||
let jumpTo = time;
|
||||
if (shaka.util.Platform.isLegacyEdge() ||
|
||||
shaka.util.Platform.isXboxOne() ||
|
||||
shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.EDGE ||
|
||||
deviceDetected.getDeviceName() === 'Xbox' ||
|
||||
deviceDetected.getDeviceName() === 'Tizen') {
|
||||
const gapPadding = shaka.util.PlayerConfiguration.createDefault()
|
||||
.streaming.gapPadding;
|
||||
jumpTo = Math.ceil((jumpTo + gapPadding) * 100) / 100;
|
||||
@@ -695,7 +696,7 @@ describe('Playhead', () => {
|
||||
}); // does not clamp playhead if setLiveSeekableRange is used
|
||||
|
||||
it('doesn\'t repeatedly re-seek in seeking slow platforms', () => {
|
||||
if (!shaka.util.Platform.isSeekingSlow()) {
|
||||
if (!deviceDetected.seekDelay()) {
|
||||
pending('No seeking slow platform');
|
||||
}
|
||||
video.readyState = HTMLMediaElement.HAVE_METADATA;
|
||||
|
||||
@@ -302,7 +302,7 @@ describe('StreamingEngine', () => {
|
||||
// Experimentally, we find that playback rates above 2x in this test seem
|
||||
// to cause decoder failures on Tizen 3. This is out of our control, and
|
||||
// seems to be a Tizen bug, so this test is skipped on Tizen completely.
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen') {
|
||||
pending('High playbackRate tests cause decoder errors on Tizen 3.');
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,8 @@ filterDescribe('Offline', supportsStorage, () => {
|
||||
pending('Widevine persistent licenses are not supported');
|
||||
return;
|
||||
}
|
||||
if (shaka.util.Platform.isAndroid()) {
|
||||
if (deviceDetected.getDeviceType() ===
|
||||
shaka.device.IDevice.DeviceType.MOBILE) {
|
||||
pending('Skipping offline DRM tests on Android - crbug.com/1108158');
|
||||
return;
|
||||
}
|
||||
@@ -121,12 +122,13 @@ filterDescribe('Offline', supportsStorage, () => {
|
||||
pending('Widevine and PlayReady are not supported');
|
||||
return;
|
||||
}
|
||||
if (shaka.util.Platform.isAndroid()) {
|
||||
if (deviceDetected.getDeviceType() ===
|
||||
shaka.device.IDevice.DeviceType.MOBILE) {
|
||||
pending('Skipping offline DRM tests on Android - crbug.com/1108158');
|
||||
return;
|
||||
}
|
||||
|
||||
if (shaka.util.Platform.isXboxOne()) {
|
||||
if (deviceDetected.getDeviceName() === 'Xbox') {
|
||||
// Axinom won't issue a license for an Xbox One. The error message
|
||||
// from the license server says "Your DRM client's security level is
|
||||
// 150, but the entitlement message requires 2000 or higher."
|
||||
|
||||
@@ -160,7 +160,8 @@ filterDescribe('Storage', checkStorageSupport, () => {
|
||||
it('supports MSS download and playback', async () => {
|
||||
// This tests is flaky in some Chromecast devices, so we need omit it
|
||||
// for now.
|
||||
if (shaka.util.Platform.isChromecast()) {
|
||||
if (deviceDetected.getDeviceType() ===
|
||||
shaka.device.IDevice.DeviceType.CAST) {
|
||||
pending('Disabled on Chromecast.');
|
||||
}
|
||||
const url = '/base/test/test/assets/mss-clear/Manifest';
|
||||
|
||||
@@ -68,7 +68,8 @@ describe('Player Cross Boundary', () => {
|
||||
|
||||
// PlayReady on Chromecast is deprecated, so we prefer to use the DRM
|
||||
// that is officially supported.
|
||||
if (shaka.util.Platform.isChromecast()) {
|
||||
if (deviceDetected.getDeviceType() ===
|
||||
shaka.device.IDevice.DeviceType.CAST) {
|
||||
player.configure({
|
||||
drm: {
|
||||
preferredKeySystems: ['com.widevine.alpha'],
|
||||
|
||||
@@ -79,7 +79,8 @@ describe('Player Dolby Vision', () => {
|
||||
describe('P8 with fallback to HEVC', () => {
|
||||
it('with DASH', async () => {
|
||||
// This tests is flaky in Safari, so we need omit it for now.
|
||||
if (shaka.util.Platform.isApple()) {
|
||||
if (deviceDetected.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT) {
|
||||
pending('Disabled on Safari.');
|
||||
}
|
||||
if (!await Util.isTypeSupported('video/mp4; codecs="hvc1.2.4.L90.90"',
|
||||
@@ -91,7 +92,8 @@ describe('Player Dolby Vision', () => {
|
||||
|
||||
it('with master playlist (HLS)', async () => {
|
||||
// This tests is flaky in Safari, so we need omit it for now.
|
||||
if (shaka.util.Platform.isApple()) {
|
||||
if (deviceDetected.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT) {
|
||||
pending('Disabled on Safari.');
|
||||
}
|
||||
if (!await Util.isTypeSupported('video/mp4; codecs="hvc1.2.4.L90.90"',
|
||||
@@ -103,7 +105,8 @@ describe('Player Dolby Vision', () => {
|
||||
|
||||
it('with media playlist (HLS)', async () => {
|
||||
// This tests is flaky in Safari, so we need omit it for now.
|
||||
if (shaka.util.Platform.isApple()) {
|
||||
if (deviceDetected.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT) {
|
||||
pending('Disabled on Safari.');
|
||||
}
|
||||
if (!await Util.isTypeSupported('video/mp4; codecs="hvc1.2.4.L90.90"',
|
||||
|
||||
@@ -635,7 +635,7 @@ describe('Player', () => {
|
||||
});
|
||||
|
||||
it('in sequence mode', async () => {
|
||||
if (!shaka.util.Platform.supportsSequenceMode()) {
|
||||
if (!deviceDetected.supportsSequenceMode()) {
|
||||
pending('Sequence mode is not supported by the platform.');
|
||||
}
|
||||
await player.load('test:sintel_sequence_compiled');
|
||||
|
||||
+8
-8
@@ -752,8 +752,8 @@ describe('Player', () => {
|
||||
|
||||
it('only applies to DASH streams', async () => {
|
||||
video.canPlayType.and.returnValue('maybe');
|
||||
spyOn(shaka.util.Platform, 'anyMediaElement').and.returnValue(video);
|
||||
spyOn(shaka.util.Platform, 'supportsMediaSource').and.returnValue(true);
|
||||
spyOn(shaka.util.Dom, 'anyMediaElement').and.returnValue(video);
|
||||
spyOn(deviceDetected, 'supportsMediaSource').and.returnValue(true);
|
||||
// Make sure player.load() resolves for src=
|
||||
spyOn(shaka.util.MediaReadyState, 'waitForReadyState').and.callFake(
|
||||
(mediaElement, readyState, eventManager, callback) => {
|
||||
@@ -773,7 +773,7 @@ describe('Player', () => {
|
||||
|
||||
it('does not apply to non-DASH streams', async () => {
|
||||
video.canPlayType.and.returnValue('maybe');
|
||||
spyOn(shaka.util.Platform, 'supportsMediaSource').and.returnValue(true);
|
||||
spyOn(deviceDetected, 'supportsMediaSource').and.returnValue(true);
|
||||
|
||||
player.configure({
|
||||
streaming: {
|
||||
@@ -808,9 +808,9 @@ describe('Player', () => {
|
||||
|
||||
it('only applies to HLS streams', async () => {
|
||||
video.canPlayType.and.returnValue('maybe');
|
||||
spyOn(shaka.util.Platform, 'anyMediaElement').and.returnValue(video);
|
||||
spyOn(shaka.util.Platform, 'supportsMediaSource').and.returnValue(true);
|
||||
spyOn(shaka.util.Platform, 'isApple').and.returnValue(false);
|
||||
spyOn(shaka.util.Dom, 'anyMediaElement').and.returnValue(video);
|
||||
spyOn(deviceDetected, 'supportsMediaSource').and.returnValue(true);
|
||||
spyOn(deviceDetected, 'getBrowserEngine').and.returnValue('UNKNOWN');
|
||||
// Make sure player.load() resolves for src=
|
||||
spyOn(shaka.util.MediaReadyState, 'waitForReadyState').and.callFake(
|
||||
(mediaElement, readyState, eventManager, callback) => {
|
||||
@@ -830,8 +830,8 @@ describe('Player', () => {
|
||||
|
||||
it('does not apply to non-HLS streams', async () => {
|
||||
video.canPlayType.and.returnValue('maybe');
|
||||
spyOn(shaka.util.Platform, 'supportsMediaSource').and.returnValue(true);
|
||||
spyOn(shaka.util.Platform, 'isApple').and.returnValue(false);
|
||||
spyOn(deviceDetected, 'supportsMediaSource').and.returnValue(true);
|
||||
spyOn(deviceDetected, 'getBrowserEngine').and.returnValue('UNKNOWN');
|
||||
|
||||
player.configure({
|
||||
streaming: {
|
||||
|
||||
@@ -104,6 +104,8 @@ describe('MediaCapabilities', () => {
|
||||
it('should check codec support when MediaDecodingConfiguration.type ' +
|
||||
'is "media-source"', async () => {
|
||||
expect(window['MediaSource']['isTypeSupported']).toBeDefined();
|
||||
spyOn(deviceDetected, 'getDeviceType').and
|
||||
.returnValue(shaka.device.IDevice.DeviceType.DESKTOP);
|
||||
shaka.polyfill.MediaCapabilities.install();
|
||||
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
||||
|
||||
@@ -113,9 +115,8 @@ describe('MediaCapabilities', () => {
|
||||
|
||||
it('should check codec support when MediaDecodingConfiguration.type ' +
|
||||
'is "file"', async () => {
|
||||
const supportsMediaTypeSpy =
|
||||
spyOn(shaka['util']['Platform'],
|
||||
'supportsMediaType').and.returnValue(true);
|
||||
const supportsMediaTypeSpy = spyOn(deviceDetected, 'supportsMediaType')
|
||||
.and.returnValue(true);
|
||||
mockDecodingConfig.type = 'file';
|
||||
shaka.polyfill.MediaCapabilities.install();
|
||||
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
||||
@@ -193,22 +194,17 @@ describe('MediaCapabilities', () => {
|
||||
pending('Unable to delete window.cast');
|
||||
}
|
||||
|
||||
spyOn(shaka['util']['Platform'], 'isAndroid').and.returnValue(false);
|
||||
spyOn(shaka['util']['Platform'], 'isFuchsia').and.returnValue(false);
|
||||
|
||||
const isChromecastSpy =
|
||||
spyOn(shaka['util']['Platform'],
|
||||
'isChromecast').and.returnValue(true);
|
||||
const isChromecastSpy = spyOn(deviceDetected, 'getDeviceType').and
|
||||
.returnValue(shaka.device.IDevice.DeviceType.CAST);
|
||||
expect(window['MediaSource']['isTypeSupported']).toBeDefined();
|
||||
|
||||
shaka.polyfill.MediaCapabilities.install();
|
||||
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
||||
|
||||
expect(mockCanDisplayType).not.toHaveBeenCalled();
|
||||
// 1 (during install()) +
|
||||
// 1 (for video config check) +
|
||||
// 1 (for audio config check).
|
||||
expect(isChromecastSpy).toHaveBeenCalledTimes(3);
|
||||
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
||||
// 1 (fallback in canCastDisplayType()) +
|
||||
// 1 (mockDecodingConfig.audio).
|
||||
expect(supportMap.has(mockDecodingConfig.video.contentType))
|
||||
@@ -221,21 +217,17 @@ describe('MediaCapabilities', () => {
|
||||
async () => {
|
||||
// We only set the cast namespace, but not the canDisplayType() API.
|
||||
window['cast'] = {};
|
||||
spyOn(shaka['util']['Platform'], 'isAndroid').and.returnValue(false);
|
||||
spyOn(shaka['util']['Platform'], 'isFuchsia').and.returnValue(false);
|
||||
const isChromecastSpy =
|
||||
spyOn(shaka['util']['Platform'],
|
||||
'isChromecast').and.returnValue(true);
|
||||
const isChromecastSpy = spyOn(deviceDetected, 'getDeviceType').and
|
||||
.returnValue(shaka.device.IDevice.DeviceType.CAST);
|
||||
expect(window['MediaSource']['isTypeSupported']).toBeDefined();
|
||||
|
||||
shaka.polyfill.MediaCapabilities.install();
|
||||
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
||||
|
||||
expect(mockCanDisplayType).not.toHaveBeenCalled();
|
||||
// 1 (during install()) +
|
||||
// 1 (for video config check) +
|
||||
// 1 (for audio config check).
|
||||
expect(isChromecastSpy).toHaveBeenCalledTimes(3);
|
||||
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
||||
// 1 (fallback in canCastDisplayType()) +
|
||||
// 1 (mockDecodingConfig.audio).
|
||||
expect(supportMap.has(mockDecodingConfig.video.contentType))
|
||||
@@ -252,11 +244,8 @@ describe('MediaCapabilities', () => {
|
||||
window['cast'] = {
|
||||
__platform__: {canDisplayType: mockCanDisplayType},
|
||||
};
|
||||
spyOn(shaka['util']['Platform'], 'isAndroid').and.returnValue(false);
|
||||
spyOn(shaka['util']['Platform'], 'isFuchsia').and.returnValue(false);
|
||||
const isChromecastSpy =
|
||||
spyOn(shaka['util']['Platform'],
|
||||
'isChromecast').and.returnValue(true);
|
||||
const isChromecastSpy = spyOn(deviceDetected, 'getDeviceType').and
|
||||
.returnValue(shaka.device.IDevice.DeviceType.CAST);
|
||||
expect(window['MediaSource']['isTypeSupported']).toBeDefined();
|
||||
|
||||
// Tests an HDR stream's extended MIME type is correctly provided.
|
||||
@@ -283,10 +272,9 @@ describe('MediaCapabilities', () => {
|
||||
shaka.polyfill.MediaCapabilities.install();
|
||||
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
||||
|
||||
// 1 (during install()) +
|
||||
// 1 (for video config check) +
|
||||
// 1 (for audio config check).
|
||||
expect(isChromecastSpy).toHaveBeenCalledTimes(3);
|
||||
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
||||
// 1 (mockDecodingConfig.audio).
|
||||
expect(supportMap.has(chromecastType)).toBe(true);
|
||||
// Called once in canCastDisplayType.
|
||||
|
||||
+8
-1
@@ -379,8 +379,15 @@ function configureJasmineEnvironment() {
|
||||
});
|
||||
}
|
||||
|
||||
// Reset decoding config cache after each test.
|
||||
const originalDevice = shaka.device.DeviceFactory.getDevice();
|
||||
goog.asserts.assert(originalDevice, 'device must be non-null');
|
||||
window.dump(originalDevice.toString());
|
||||
window.deviceDetected = originalDevice;
|
||||
|
||||
afterEach(/** @suppress {accessControls} */ () => {
|
||||
goog.asserts.assert(originalDevice, 'device must be non-null');
|
||||
window.deviceDetected = originalDevice;
|
||||
// Reset decoding config cache after each test.
|
||||
shaka.util.StreamUtils.clearDecodingConfigCache();
|
||||
shaka.media.Capabilities.MediaSourceTypeSupportMap.clear();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Externs for device used in tests.
|
||||
* @externs
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @type {!shaka.device.IDevice}
|
||||
*/
|
||||
var deviceDetected;
|
||||
@@ -177,7 +177,9 @@ shaka.test.TextLayoutTests = class extends shaka.test.LayoutTests {
|
||||
static async supported() {
|
||||
// We only do this in our lab, where we control device a11y settings that
|
||||
// impact these tests heavily.
|
||||
if (shaka.util.Platform.isApple() && getClientArg('runningInVM')) {
|
||||
if (deviceDetected.getBrowserEngine() ===
|
||||
shaka.device.IDevice.BrowserEngine.WEBKIT &&
|
||||
getClientArg('runningInVM')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,8 +174,8 @@ shaka.test.UiUtils = class {
|
||||
// Some platforms have issues with audio-only playbacks on muted video
|
||||
// elements. Don't mute them.
|
||||
// Fuchsia reference: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/media/web_media_player_impl.cc;l=3535;drc=d23075f3
|
||||
if (!shaka.util.Platform.isTizen() &&
|
||||
!shaka.util.Platform.isFuchsiaCastDevice()) {
|
||||
if (deviceDetected.getDeviceName() !== 'Tizen' &&
|
||||
deviceDetected.getDeviceName() !== 'Chromecast with Fuchsia') {
|
||||
video.muted = true;
|
||||
}
|
||||
video.width = 600;
|
||||
|
||||
@@ -398,6 +398,9 @@ shaka.test.Util = class {
|
||||
const codecs = StreamUtils.getCorrectVideoCodecs(
|
||||
MimeUtils.getCodecs(mimetype));
|
||||
const baseMimeType = MimeUtils.getBasicType(mimetype);
|
||||
if (codecs.startsWith('hvc1.') && deviceDetected.disableHEVCSupport()) {
|
||||
return false;
|
||||
}
|
||||
// VideoConfiguration
|
||||
mediaDecodingConfig.video = {
|
||||
contentType: MimeUtils.getFullOrConvertedType(
|
||||
@@ -420,6 +423,39 @@ shaka.test.Util = class {
|
||||
await navigator.mediaCapabilities.decodingInfo(mediaDecodingConfig);
|
||||
return result.supported;
|
||||
}
|
||||
|
||||
/** @param {string} userAgent */
|
||||
static setUserAgent(userAgent) {
|
||||
shaka.test.Util.setNavigatorProperty('userAgent', userAgent);
|
||||
}
|
||||
|
||||
/** @param {?Object} userAgentData */
|
||||
static setUserAgentData(userAgentData) {
|
||||
shaka.test.Util.setNavigatorProperty('userAgentData', userAgentData);
|
||||
}
|
||||
|
||||
/** @param {string} vendor */
|
||||
static setVendor(vendor) {
|
||||
shaka.test.Util.setNavigatorProperty('vendor', vendor);
|
||||
}
|
||||
|
||||
/** @param {string} platform */
|
||||
static setPlatform(platform) {
|
||||
shaka.test.Util.setNavigatorProperty('platform', platform);
|
||||
}
|
||||
|
||||
/** @param {number} maxTouchPoints */
|
||||
static setMaxTouchPoints(maxTouchPoints) {
|
||||
shaka.test.Util.setNavigatorProperty('maxTouchPoints', maxTouchPoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
*/
|
||||
static setNavigatorProperty(key, value) {
|
||||
Object.defineProperty(navigator, key, {value, configurable: true});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -449,7 +449,8 @@ shaka.test.Waiter = class {
|
||||
// to complete without timing out.
|
||||
// We also use it on all platforms (except Tizen) because it reduces the
|
||||
// time it takes for tests to run.
|
||||
if (mediaElement.playbackRate == 1 && !shaka.util.Platform.isTizen()) {
|
||||
if (mediaElement.playbackRate == 1 &&
|
||||
deviceDetected.getDeviceName() !== 'Tizen') {
|
||||
mediaElement.playbackRate = 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('Transmuxer Player', () => {
|
||||
pending('Codec MP3 is not supported by the platform.');
|
||||
}
|
||||
// This tests is flaky in some Tizen devices, so we need omit it for now.
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen') {
|
||||
pending('Disabled on Tizen.');
|
||||
}
|
||||
await player.load('/base/test/test/assets/hls-raw-mp3/playlist.m3u8');
|
||||
@@ -177,7 +177,7 @@ describe('Transmuxer Player', () => {
|
||||
pending('Codec MP3 is not supported by the platform.');
|
||||
}
|
||||
// This tests is flaky in some Tizen devices, so we need omit it for now.
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen') {
|
||||
pending('Disabled on Tizen.');
|
||||
}
|
||||
await player.load('/base/test/test/assets/hls-ts-mp3/manifest.m3u8');
|
||||
@@ -375,7 +375,7 @@ describe('Transmuxer Player', () => {
|
||||
pending('Codec MP3 is not supported by the platform.');
|
||||
}
|
||||
// This tests is flaky in some Tizen devices, so we need omit it for now.
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen') {
|
||||
pending('Disabled on Tizen.');
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ describe('Transmuxer Player', () => {
|
||||
pending('Codec AC-3 is not supported by the platform.');
|
||||
}
|
||||
// This tests is flaky in some Tizen devices, so we need omit it for now.
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen') {
|
||||
pending('Disabled on Tizen.');
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -404,9 +404,10 @@ describe('UI', () => {
|
||||
|
||||
UiUtils.confirmElementFound(videoContainer, 'shaka-seek-bar');
|
||||
|
||||
// The default settings vary in mobile/desktop/SmartTV context.
|
||||
if (shaka.util.Platform.isMobile() ||
|
||||
shaka.util.Platform.isSmartTV()) {
|
||||
// The default settings vary in mobile/desktop context.
|
||||
const deviceType = deviceDetected.getDeviceType();
|
||||
if (deviceType == shaka.device.IDevice.DeviceType.MOBILE ||
|
||||
deviceType == shaka.device.IDevice.DeviceType.TV) {
|
||||
UiUtils.confirmElementFound(videoContainer,
|
||||
'shaka-play-button-container');
|
||||
UiUtils.confirmElementFound(videoContainer, 'shaka-play-button');
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
describe('Platform', () => {
|
||||
const originalUserAgent = navigator.userAgent;
|
||||
const originalUserAgentData = navigator.userAgentData;
|
||||
const originalVendor = navigator.vendor;
|
||||
const originalPlatform = navigator.platform;
|
||||
const originalMaxTouchPoints = navigator.maxTouchPoints;
|
||||
|
||||
/* eslint-disable @stylistic/max-len */
|
||||
const macSafari = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15';
|
||||
const ipadSafari = 'Mozilla/5.0 (iPad; CPU OS 18_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Mobile/15E148 Safari/604.1';
|
||||
const iosChrome = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1';
|
||||
// See: https://developer.samsung.com/smarttv/develop/guides/fundamentals/retrieving-platform-information.html
|
||||
const tizen50 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0 TV Safari/537.36';
|
||||
const tizen55 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 5.5) AppleWebKit/537.36 (KHTML, like Gecko) 69.0.3497.106.1/5.5 TV Safari/537.36';
|
||||
const tizen60 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 6.0) AppleWebKit/537.36 (KHTML, like Gecko) 76.0.3809.146/6.0 TV Safari/537.36';
|
||||
const tizen65 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 6.5) AppleWebKit/537.36 (KHTML, like Gecko) 85.0.4183.93/6.5 TV Safari/537.36';
|
||||
const tizen70 = 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) 94.0.4606.31/7.0 TV Safari/537.36';
|
||||
|
||||
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
||||
const webOs3 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.2.1 Chrome/38.0.2125.122 Safari/537.36 WebAppManager';
|
||||
const webOs4 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.34 Safari/537.36 WebAppManager';
|
||||
const webOs5 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 WebAppManager';
|
||||
const webOs6 = 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36 WebAppManager';
|
||||
|
||||
// cspell: disable-next-line
|
||||
const vizio = 'Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 CrKey/1.0.999999 VIZIO SmartCast(Conjure/MTKF-5.1.516.1 FW/0.6.11.1-2 Model/V50C6-J09)';
|
||||
// cspell: disable-next-line
|
||||
const chromecastBuiltinOrOlder = 'Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.225 Safari/537.36 CrKey/1.56.500000 DeviceType/Chromecast';
|
||||
// cspell: disable-next-line
|
||||
const chromecastFuchsia = 'Mozilla/5.0 (Fuchsia) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 CrKey/1.56.500000';
|
||||
// cspell: disable-next-line
|
||||
const chromecastAndroid = 'Mozilla/5.0 (Linux; Android 12; Build/STTL.240206.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.0 Safari/537.36 CrKey/1.56.500000 DeviceType/AndroidTV';
|
||||
/* eslint-enable @stylistic/max-len */
|
||||
|
||||
afterEach(() => {
|
||||
setUserAgent(originalUserAgent);
|
||||
setUserAgentData(originalUserAgentData);
|
||||
setVendor(originalVendor);
|
||||
setPlatform(originalPlatform);
|
||||
setMaxTouchPoints(originalMaxTouchPoints);
|
||||
});
|
||||
|
||||
describe('Apple', () => {
|
||||
beforeEach(() => {
|
||||
setVendor('Apple Computer, Inc.');
|
||||
setMaxTouchPoints(0);
|
||||
});
|
||||
|
||||
it('checks Safari version', () => {
|
||||
setUserAgentData(null);
|
||||
setPlatform('MacIntel');
|
||||
setUserAgent(macSafari);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(18);
|
||||
setUserAgent(ipadSafari);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(18);
|
||||
setUserAgent(iosChrome);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(10);
|
||||
setUserAgent(webOs6);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(null);
|
||||
|
||||
setVendor('Google Inc.');
|
||||
setUserAgent(macSafari);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(null);
|
||||
setUserAgent(ipadSafari);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(null);
|
||||
setUserAgent(iosChrome);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(null);
|
||||
setUserAgent(webOs6);
|
||||
expect(shaka.util.Platform.safariVersion()).toBe(null);
|
||||
});
|
||||
|
||||
it('checks is iOS', () => {
|
||||
setUserAgent(macSafari);
|
||||
expect(shaka.util.Platform.isIOS()).toBe(false);
|
||||
setUserAgent(ipadSafari);
|
||||
expect(shaka.util.Platform.isIOS()).toBe(true);
|
||||
setUserAgent(iosChrome);
|
||||
expect(shaka.util.Platform.isIOS()).toBe(true);
|
||||
setUserAgent(webOs6);
|
||||
expect(shaka.util.Platform.isIOS()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is Mac', () => {
|
||||
setUserAgentData({platform: 'macOS'});
|
||||
expect(shaka.util.Platform.isMac()).toBe(true);
|
||||
|
||||
setUserAgentData(null);
|
||||
setPlatform('MacIntel');
|
||||
expect(shaka.util.Platform.isMac()).toBe(true);
|
||||
|
||||
setPlatform('Win32');
|
||||
expect(shaka.util.Platform.isMac()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is Webkit STB', () => {
|
||||
setUserAgent(ipadSafari);
|
||||
setUserAgentData({platform: 'macOS'});
|
||||
expect(shaka.util.Platform.isWebkitSTB()).toBe(false);
|
||||
|
||||
setUserAgentData(null);
|
||||
setPlatform('MacIntel');
|
||||
expect(shaka.util.Platform.isWebkitSTB()).toBe(false);
|
||||
|
||||
setPlatform('Win32');
|
||||
expect(shaka.util.Platform.isWebkitSTB()).toBe(false);
|
||||
|
||||
setUserAgent(macSafari);
|
||||
expect(shaka.util.Platform.isWebkitSTB()).toBe(true);
|
||||
|
||||
setVendor('Google Inc.');
|
||||
expect(shaka.util.Platform.isWebkitSTB()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Samsung', () => {
|
||||
it('checks is Tizen 5', () => {
|
||||
setUserAgent(webOs3);
|
||||
expect(shaka.util.Platform.isTizen5()).toBe(false);
|
||||
setUserAgent(tizen50);
|
||||
expect(shaka.util.Platform.isTizen5()).toBe(true);
|
||||
setUserAgent(tizen55);
|
||||
expect(shaka.util.Platform.isTizen5()).toBe(true);
|
||||
setUserAgent(tizen60);
|
||||
expect(shaka.util.Platform.isTizen5()).toBe(false);
|
||||
setUserAgent(tizen65);
|
||||
expect(shaka.util.Platform.isTizen5()).toBe(false);
|
||||
setUserAgent(tizen70);
|
||||
expect(shaka.util.Platform.isTizen5()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is Tizen 5.0', () => {
|
||||
setUserAgent(webOs3);
|
||||
expect(shaka.util.Platform.isTizen5_0()).toBe(false);
|
||||
setUserAgent(tizen50);
|
||||
expect(shaka.util.Platform.isTizen5_0()).toBe(true);
|
||||
setUserAgent(tizen55);
|
||||
expect(shaka.util.Platform.isTizen5_0()).toBe(false);
|
||||
setUserAgent(tizen60);
|
||||
expect(shaka.util.Platform.isTizen5_0()).toBe(false);
|
||||
setUserAgent(tizen65);
|
||||
expect(shaka.util.Platform.isTizen5_0()).toBe(false);
|
||||
setUserAgent(tizen70);
|
||||
expect(shaka.util.Platform.isTizen5_0()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is Tizen 6', () => {
|
||||
setUserAgent(webOs3);
|
||||
expect(shaka.util.Platform.isTizen6()).toBe(false);
|
||||
setUserAgent(tizen50);
|
||||
expect(shaka.util.Platform.isTizen6()).toBe(false);
|
||||
setUserAgent(tizen55);
|
||||
expect(shaka.util.Platform.isTizen6()).toBe(false);
|
||||
setUserAgent(tizen60);
|
||||
expect(shaka.util.Platform.isTizen6()).toBe(true);
|
||||
setUserAgent(tizen65);
|
||||
expect(shaka.util.Platform.isTizen6()).toBe(true);
|
||||
setUserAgent(tizen70);
|
||||
expect(shaka.util.Platform.isTizen6()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LG', () => {
|
||||
it('checks is webOS 3', () => {
|
||||
setUserAgent(tizen50);
|
||||
expect(shaka.util.Platform.isWebOS3()).toBe(false);
|
||||
setUserAgent(webOs3);
|
||||
expect(shaka.util.Platform.isWebOS3()).toBe(true);
|
||||
setUserAgent(webOs4);
|
||||
expect(shaka.util.Platform.isWebOS3()).toBe(false);
|
||||
setUserAgent(webOs5);
|
||||
expect(shaka.util.Platform.isWebOS3()).toBe(false);
|
||||
setUserAgent(webOs6);
|
||||
expect(shaka.util.Platform.isWebOS3()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is webOS 4', () => {
|
||||
setUserAgent(tizen50);
|
||||
expect(shaka.util.Platform.isWebOS4()).toBe(false);
|
||||
setUserAgent(webOs3);
|
||||
expect(shaka.util.Platform.isWebOS4()).toBe(false);
|
||||
setUserAgent(webOs4);
|
||||
expect(shaka.util.Platform.isWebOS4()).toBe(true);
|
||||
setUserAgent(webOs5);
|
||||
expect(shaka.util.Platform.isWebOS4()).toBe(false);
|
||||
setUserAgent(webOs6);
|
||||
expect(shaka.util.Platform.isWebOS4()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is webOS 5', () => {
|
||||
setUserAgent(tizen50);
|
||||
expect(shaka.util.Platform.isWebOS5()).toBe(false);
|
||||
setUserAgent(webOs3);
|
||||
expect(shaka.util.Platform.isWebOS5()).toBe(false);
|
||||
setUserAgent(webOs4);
|
||||
expect(shaka.util.Platform.isWebOS5()).toBe(false);
|
||||
setUserAgent(webOs5);
|
||||
expect(shaka.util.Platform.isWebOS5()).toBe(true);
|
||||
setUserAgent(webOs6);
|
||||
expect(shaka.util.Platform.isWebOS5()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is webOS 6', () => {
|
||||
setUserAgent(tizen50);
|
||||
expect(shaka.util.Platform.isWebOS6()).toBe(false);
|
||||
setUserAgent(webOs3);
|
||||
expect(shaka.util.Platform.isWebOS6()).toBe(false);
|
||||
setUserAgent(webOs4);
|
||||
expect(shaka.util.Platform.isWebOS6()).toBe(false);
|
||||
setUserAgent(webOs5);
|
||||
expect(shaka.util.Platform.isWebOS6()).toBe(false);
|
||||
setUserAgent(webOs6);
|
||||
expect(shaka.util.Platform.isWebOS6()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('checks is Vizio', () => {
|
||||
setUserAgent(vizio);
|
||||
expect(shaka.util.Platform.isVizio()).toBe(true);
|
||||
expect(shaka.util.Platform.isChromecast()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is Chromecast Fuchsia', () => {
|
||||
setUserAgent(chromecastFuchsia);
|
||||
setUserAgentData(null);
|
||||
expect(shaka.util.Platform.isVizio()).toBe(false);
|
||||
expect(shaka.util.Platform.isChromecast()).toBe(true);
|
||||
expect(shaka.util.Platform.isAndroidCastDevice()).toBe(false);
|
||||
expect(shaka.util.Platform.isFuchsia()).toBe(true);
|
||||
});
|
||||
|
||||
it('checks is Chromecast Android', () => {
|
||||
setUserAgent(chromecastAndroid);
|
||||
setUserAgentData(null);
|
||||
expect(shaka.util.Platform.isVizio()).toBe(false);
|
||||
expect(shaka.util.Platform.isChromecast()).toBe(true);
|
||||
expect(shaka.util.Platform.isAndroidCastDevice()).toBe(true);
|
||||
expect(shaka.util.Platform.isFuchsia()).toBe(false);
|
||||
});
|
||||
|
||||
it('checks is Chromecast', () => {
|
||||
setUserAgent(chromecastBuiltinOrOlder);
|
||||
setUserAgentData(null);
|
||||
expect(shaka.util.Platform.isVizio()).toBe(false);
|
||||
expect(shaka.util.Platform.isChromecast()).toBe(true);
|
||||
expect(shaka.util.Platform.isAndroidCastDevice()).toBe(false);
|
||||
expect(shaka.util.Platform.isFuchsia()).toBe(false);
|
||||
});
|
||||
|
||||
/** @param {string} userAgent */
|
||||
function setUserAgent(userAgent) {
|
||||
setNavigatorProperty('userAgent', userAgent);
|
||||
}
|
||||
|
||||
/** @param {?Object} userAgentData */
|
||||
function setUserAgentData(userAgentData) {
|
||||
setNavigatorProperty('userAgentData', userAgentData);
|
||||
}
|
||||
|
||||
/** @param {string} vendor */
|
||||
function setVendor(vendor) {
|
||||
setNavigatorProperty('vendor', vendor);
|
||||
}
|
||||
|
||||
/** @param {string} platform */
|
||||
function setPlatform(platform) {
|
||||
setNavigatorProperty('platform', platform);
|
||||
}
|
||||
|
||||
/** @param {number} maxTouchPoints */
|
||||
function setMaxTouchPoints(maxTouchPoints) {
|
||||
setNavigatorProperty('maxTouchPoints', maxTouchPoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
*/
|
||||
function setNavigatorProperty(key, value) {
|
||||
Object.defineProperty(navigator, key, {value, configurable: true});
|
||||
}
|
||||
});
|
||||
@@ -901,7 +901,7 @@ describe('StreamUtils', () => {
|
||||
it('should filter variants by the best available bandwidth' +
|
||||
' for audio language', () => {
|
||||
// This test is flaky in some Tizen devices, due to codec restrictions.
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen') {
|
||||
pending('Skip flaky test in Tizen');
|
||||
}
|
||||
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||||
@@ -959,7 +959,7 @@ describe('StreamUtils', () => {
|
||||
pending('Codec HEVC is not supported by the platform.');
|
||||
}
|
||||
// This test is flaky in some Tizen devices, due to codec restrictions.
|
||||
if (shaka.util.Platform.isTizen()) {
|
||||
if (deviceDetected.getDeviceName() === 'Tizen') {
|
||||
pending('Skip flaky test in Tizen');
|
||||
}
|
||||
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||||
|
||||
Vendored
+6
-4
@@ -11,6 +11,8 @@ goog.provide('shaka.ui.ControlsPanel');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.ads.Utils');
|
||||
goog.require('shaka.cast.CastProxy');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.ui.AdInfo');
|
||||
goog.require('shaka.ui.BigPlayButton');
|
||||
@@ -28,7 +30,6 @@ goog.require('shaka.util.EventManager');
|
||||
goog.require('shaka.util.FakeEvent');
|
||||
goog.require('shaka.util.FakeEventTarget');
|
||||
goog.require('shaka.util.IDestroyable');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.require('shaka.util.Timer');
|
||||
|
||||
goog.requireType('shaka.Player');
|
||||
@@ -641,9 +642,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
// When the preferVideoFullScreenInVisionOS configuration value applies,
|
||||
// we avoid using document fullscreen, even if it is available.
|
||||
const video = /** @type {HTMLVideoElement} */(this.localVideo_);
|
||||
if (video.webkitSupportsFullscreen) {
|
||||
if (this.config_.preferVideoFullScreenInVisionOS &&
|
||||
shaka.util.Platform.isVisionOS()) {
|
||||
if (video.webkitSupportsFullscreen &&
|
||||
this.config_.preferVideoFullScreenInVisionOS) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
if (device.getDeviceType() == shaka.device.IDevice.DeviceType.VR) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -8,6 +8,7 @@
|
||||
goog.provide('shaka.ui.RemoteButton');
|
||||
|
||||
goog.require('shaka.Player');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.ui.Controls');
|
||||
goog.require('shaka.ui.Element');
|
||||
goog.require('shaka.ui.Enums');
|
||||
@@ -16,7 +17,6 @@ goog.require('shaka.ui.Localization');
|
||||
goog.require('shaka.ui.OverflowMenu');
|
||||
goog.require('shaka.ui.Utils');
|
||||
goog.require('shaka.util.Dom');
|
||||
goog.require('shaka.util.Platform');
|
||||
goog.requireType('shaka.ui.Controls');
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
|
||||
super(parent, controls);
|
||||
|
||||
/** @private {boolean} */
|
||||
this.isAirPlay_ = shaka.util.Platform.isApple();
|
||||
this.isAirPlay_ = shaka.device.DeviceFactory.getDevice().supportsAirPlay();
|
||||
|
||||
/** @private {!HTMLButtonElement} */
|
||||
this.remoteButton_ = shaka.util.Dom.createButton();
|
||||
@@ -148,7 +148,7 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
|
||||
if (this.player) {
|
||||
const disableRemote = this.video.disableRemotePlayback;
|
||||
let canCast = true;
|
||||
if (shaka.util.Platform.isApple()) {
|
||||
if (shaka.device.DeviceFactory.getDevice().supportsAirPlay()) {
|
||||
const loadMode = this.player.getLoadMode();
|
||||
const mseMode = loadMode == shaka.Player.LoadMode.MEDIA_SOURCE;
|
||||
if (mseMode && this.player.getManifestType() != 'HLS') {
|
||||
|
||||
@@ -11,6 +11,8 @@ goog.provide('shaka.ui.Overlay.TrackLabelFormat');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.Player');
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.polyfill');
|
||||
goog.require('shaka.ui.Controls');
|
||||
@@ -19,7 +21,6 @@ goog.require('shaka.util.ConfigUtils');
|
||||
goog.require('shaka.util.Dom');
|
||||
goog.require('shaka.util.FakeEvent');
|
||||
goog.require('shaka.util.IDestroyable');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
/**
|
||||
* @implements {shaka.util.IDestroyable}
|
||||
@@ -125,7 +126,8 @@ shaka.ui.Overlay = class {
|
||||
* @export
|
||||
*/
|
||||
isMobile() {
|
||||
return shaka.util.Platform.isMobile();
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
return device.getDeviceType() == shaka.device.IDevice.DeviceType.MOBILE;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +139,8 @@ shaka.ui.Overlay = class {
|
||||
* @export
|
||||
*/
|
||||
isSmartTV() {
|
||||
return shaka.util.Platform.isSmartTV();
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
return device.getDeviceType() == shaka.device.IDevice.DeviceType.TV;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-2
@@ -7,6 +7,8 @@
|
||||
|
||||
goog.provide('shaka.ui.VRManager');
|
||||
|
||||
goog.require('shaka.device.DeviceFactory');
|
||||
goog.require('shaka.device.IDevice');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.ui.VRWebgl');
|
||||
goog.require('shaka.util.Dom');
|
||||
@@ -14,7 +16,6 @@ goog.require('shaka.util.EventManager');
|
||||
goog.require('shaka.util.FakeEvent');
|
||||
goog.require('shaka.util.FakeEventTarget');
|
||||
goog.require('shaka.util.IReleasable');
|
||||
goog.require('shaka.util.Platform');
|
||||
|
||||
goog.requireType('shaka.Player');
|
||||
|
||||
@@ -373,7 +374,11 @@ shaka.ui.VRManager = class extends shaka.util.FakeEventTarget {
|
||||
}
|
||||
// The user interface is not intended for devices that are controlled with
|
||||
// a remote control, and WebGL may run slowly on these devices.
|
||||
if (shaka.util.Platform.isSmartTV()) {
|
||||
const device = shaka.device.DeviceFactory.getDevice();
|
||||
const deviceType = device.getDeviceType();
|
||||
if (deviceType == shaka.device.IDevice.DeviceType.TV ||
|
||||
deviceType == shaka.device.IDevice.DeviceType.CONSOLE ||
|
||||
deviceType == shaka.device.IDevice.DeviceType.CAST) {
|
||||
return null;
|
||||
}
|
||||
const webglContexts = [
|
||||
|
||||
Reference in New Issue
Block a user