Files
shaka-player/lib/device/abstract_device.js
T
Wojciech Tyczyński 970d7756ea 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>
2025-06-02 13:46:40 +02:00

306 lines
5.9 KiB
JavaScript

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