/*! @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()}`;
}
};