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