Source: lib/device/abstract_device.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2025 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.device.AbstractDevice');
  7. goog.require('shaka.device.IDevice');
  8. goog.require('shaka.util.Dom');
  9. goog.require('shaka.util.Lazy');
  10. /**
  11. * @abstract
  12. * @implements {shaka.device.IDevice}
  13. */
  14. shaka.device.AbstractDevice = class {
  15. constructor() {
  16. /** @private {!shaka.util.Lazy<!shaka.device.IDevice.DeviceType>} */
  17. this.abstractDeviceType_ = new shaka.util.Lazy(() => {
  18. const vrKeywords = [
  19. // General Oculus devices
  20. 'Oculus',
  21. // Quest devices
  22. 'Quest',
  23. ];
  24. const isVr = vrKeywords.some((keyword) => {
  25. return navigator.userAgent.includes(keyword.toLowerCase());
  26. });
  27. if (isVr) {
  28. return shaka.device.IDevice.DeviceType.VR;
  29. }
  30. if (navigator.userAgent.match(/Smart( ?|_)TV/i) ||
  31. navigator.userAgent.match(/Android ?TV/i)) {
  32. return shaka.device.IDevice.DeviceType.TV;
  33. }
  34. if (navigator.userAgentData) {
  35. if (navigator.userAgentData.mobile) {
  36. return shaka.device.IDevice.DeviceType.MOBILE;
  37. } else {
  38. return shaka.device.IDevice.DeviceType.DESKTOP;
  39. }
  40. }
  41. if (/(?:iPhone|iPad|iPod)/.test(navigator.userAgent)) {
  42. return shaka.device.IDevice.DeviceType.MOBILE;
  43. }
  44. if (navigator.userAgentData && navigator.userAgentData.platform) {
  45. if (navigator.userAgentData.platform.toLowerCase() == 'android') {
  46. return shaka.device.IDevice.DeviceType.MOBILE;
  47. } else {
  48. return shaka.device.IDevice.DeviceType.DESKTOP;
  49. }
  50. }
  51. if (navigator.userAgent.includes('Android')) {
  52. return shaka.device.IDevice.DeviceType.MOBILE;
  53. }
  54. return shaka.device.IDevice.DeviceType.DESKTOP;
  55. });
  56. /** @private {!shaka.util.Lazy<shaka.device.IDevice.BrowserEngine>} */
  57. this.browserEngine_ = new shaka.util.Lazy(() => {
  58. if (navigator.vendor.includes('Apple') &&
  59. (navigator.userAgent.includes('Version/') ||
  60. navigator.userAgent.includes('OS/'))) {
  61. return shaka.device.IDevice.BrowserEngine.WEBKIT;
  62. }
  63. if (navigator.userAgent.includes('Edge/')) {
  64. return shaka.device.IDevice.BrowserEngine.EDGE;
  65. }
  66. if (navigator.userAgent.includes('Chrome/')) {
  67. return shaka.device.IDevice.BrowserEngine.CHROMIUM;
  68. }
  69. if (navigator.userAgent.includes('Firefox/')) {
  70. return shaka.device.IDevice.BrowserEngine.GECKO;
  71. }
  72. return shaka.device.IDevice.BrowserEngine.UNKNOWN;
  73. });
  74. }
  75. /**
  76. * @override
  77. */
  78. supportsMediaSource() {
  79. const mediaSource = window.ManagedMediaSource || window.MediaSource;
  80. // Browsers that lack a media source implementation will have no reference
  81. // to |window.MediaSource|. Platforms that we see having problematic media
  82. // source implementations will have this reference removed via a polyfill.
  83. if (!mediaSource) {
  84. return false;
  85. }
  86. // Some very old MediaSource implementations didn't have isTypeSupported.
  87. if (!mediaSource.isTypeSupported) {
  88. return false;
  89. }
  90. return true;
  91. }
  92. /**
  93. * @override
  94. */
  95. supportsMediaType(mimeType) {
  96. const video = shaka.util.Dom.anyMediaElement();
  97. return video.canPlayType(mimeType) != '';
  98. }
  99. /**
  100. * @override
  101. */
  102. supportsMediaCapabilities() {
  103. return !!navigator.mediaCapabilities;
  104. }
  105. /**
  106. * @override
  107. */
  108. getDeviceType() {
  109. return this.abstractDeviceType_.value();
  110. }
  111. /**
  112. * @override
  113. */
  114. getBrowserEngine() {
  115. return this.browserEngine_.value();
  116. }
  117. /**
  118. * @override
  119. */
  120. requiresEncryptionInfoInAllInitSegments(keySystem, contentType) {
  121. return false;
  122. }
  123. /**
  124. * @override
  125. */
  126. requiresClearAndEncryptedInitSegments() {
  127. return false;
  128. }
  129. /**
  130. * @override
  131. */
  132. insertEncryptionDataBeforeClear() {
  133. return false;
  134. }
  135. /**
  136. * @override
  137. */
  138. requiresTfhdFix(contentType) {
  139. return false;
  140. }
  141. /**
  142. * @override
  143. */
  144. requiresEC3InitSegments() {
  145. return false;
  146. }
  147. /**
  148. * @override
  149. */
  150. supportsSequenceMode() {
  151. return true;
  152. }
  153. /**
  154. * @override
  155. */
  156. supportsSmoothCodecSwitching() {
  157. return true;
  158. }
  159. /**
  160. * @override
  161. */
  162. supportsServerCertificate() {
  163. return true;
  164. }
  165. /**
  166. * @override
  167. */
  168. seekDelay() {
  169. return 0;
  170. }
  171. /**
  172. * @override
  173. */
  174. detectMaxHardwareResolution() {
  175. return Promise.resolve({width: Infinity, height: Infinity});
  176. }
  177. /**
  178. * @override
  179. */
  180. shouldOverrideDolbyVisionCodecs() {
  181. return false;
  182. }
  183. /**
  184. * @override
  185. */
  186. shouldAvoidUseTextDecoderEncoder() {
  187. return false;
  188. }
  189. /**
  190. * @override
  191. */
  192. adjustConfig(config) {
  193. const deviceType = this.getDeviceType();
  194. if (deviceType === shaka.device.IDevice.DeviceType.TV ||
  195. deviceType === shaka.device.IDevice.DeviceType.CONSOLE ||
  196. deviceType === shaka.device.IDevice.DeviceType.CAST) {
  197. config.ads.customPlayheadTracker = true;
  198. config.ads.skipPlayDetection = true;
  199. config.ads.supportsMultipleMediaElements = false;
  200. }
  201. return config;
  202. }
  203. /**
  204. * @override
  205. */
  206. supportsOfflineStorage() {
  207. return !!window.indexedDB;
  208. }
  209. /**
  210. * @override
  211. */
  212. rejectCodecs() {
  213. return [];
  214. }
  215. /**
  216. * @override
  217. */
  218. getHdrLevel(preferHLG) {
  219. if (window.matchMedia !== undefined &&
  220. window.matchMedia('(color-gamut: p3)').matches) {
  221. return preferHLG ? 'HLG' : 'PQ';
  222. }
  223. return 'SDR';
  224. }
  225. /**
  226. * @override
  227. */
  228. supportsAirPlay() {
  229. return false;
  230. }
  231. /**
  232. * @override
  233. */
  234. misreportAC3UsingDrm() {
  235. return false;
  236. }
  237. /**
  238. * @override
  239. */
  240. returnLittleEndianUsingPlayReady() {
  241. return false;
  242. }
  243. /**
  244. * @override
  245. */
  246. supportsEncryptionSchemePolyfill() {
  247. return true;
  248. }
  249. /**
  250. * @override
  251. */
  252. misreportsSupportForPersistentLicenses() {
  253. return false;
  254. }
  255. /**
  256. * @override
  257. */
  258. supportStandardVP9Checking() {
  259. return true;
  260. }
  261. /**
  262. * @override
  263. */
  264. createMediaKeysWhenCheckingSupport() {
  265. return true;
  266. }
  267. /**
  268. * @override
  269. */
  270. disableHEVCSupport() {
  271. return false;
  272. }
  273. /**
  274. * @override
  275. */
  276. toString() {
  277. return `Device: ${this.getDeviceName()} v${this.getVersion()}; ` +
  278. `Type: ${this.getDeviceType()}`;
  279. }
  280. };