Source: lib/util/platform.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.Platform');
  7. goog.require('shaka.util.Timer');
  8. /**
  9. * A wrapper for platform-specific functions.
  10. *
  11. * @final
  12. */
  13. shaka.util.Platform = class {
  14. /**
  15. * Check if the current platform supports media source. We assume that if
  16. * the current platform supports media source, then we can use media source
  17. * as per its design.
  18. *
  19. * @return {boolean}
  20. */
  21. static supportsMediaSource() {
  22. // Browsers that lack a media source implementation will have no reference
  23. // to |window.MediaSource|. Platforms that we see having problematic media
  24. // source implementations will have this reference removed via a polyfill.
  25. if (!window.MediaSource) {
  26. return false;
  27. }
  28. // Some very old MediaSource implementations didn't have isTypeSupported.
  29. if (!MediaSource.isTypeSupported) {
  30. return false;
  31. }
  32. return true;
  33. }
  34. /**
  35. * Returns true if the media type is supported natively by the platform.
  36. *
  37. * @param {string} mimeType
  38. * @return {boolean}
  39. */
  40. static supportsMediaType(mimeType) {
  41. const video = shaka.util.Platform.anyMediaElement();
  42. return video.canPlayType(mimeType) != '';
  43. }
  44. /**
  45. * Check if the current platform is MS Edge.
  46. *
  47. * @return {boolean}
  48. */
  49. static isEdge() {
  50. // Legacy Edge contains "Edge/version".
  51. // Chromium-based Edge contains "Edg/version" (no "e").
  52. if (navigator.userAgent.match(/Edge?\//)) {
  53. return true;
  54. }
  55. return false;
  56. }
  57. /**
  58. * Check if the current platform is Legacy Edge.
  59. *
  60. * @return {boolean}
  61. */
  62. static isLegacyEdge() {
  63. // Legacy Edge contains "Edge/version".
  64. // Chromium-based Edge contains "Edg/version" (no "e").
  65. if (navigator.userAgent.match(/Edge\//)) {
  66. return true;
  67. }
  68. return false;
  69. }
  70. /**
  71. * Check if the current platform is MS IE.
  72. *
  73. * @return {boolean}
  74. */
  75. static isIE() {
  76. return shaka.util.Platform.userAgentContains_('Trident/');
  77. }
  78. /**
  79. * Check if the current platform is an Xbox One.
  80. *
  81. * @return {boolean}
  82. */
  83. static isXboxOne() {
  84. return shaka.util.Platform.userAgentContains_('Xbox One');
  85. }
  86. /**
  87. * Check if the current platform is a Tizen TV.
  88. *
  89. * @return {boolean}
  90. */
  91. static isTizen() {
  92. return shaka.util.Platform.userAgentContains_('Tizen');
  93. }
  94. /**
  95. * Check if the current platform is a Tizen 4 TV.
  96. *
  97. * @return {boolean}
  98. */
  99. static isTizen4() {
  100. return shaka.util.Platform.userAgentContains_('Tizen 4');
  101. }
  102. /**
  103. * Check if the current platform is a Tizen 3 TV.
  104. *
  105. * @return {boolean}
  106. */
  107. static isTizen3() {
  108. return shaka.util.Platform.userAgentContains_('Tizen 3');
  109. }
  110. /**
  111. * Check if the current platform is a Tizen 2 TV.
  112. *
  113. * @return {boolean}
  114. */
  115. static isTizen2() {
  116. return shaka.util.Platform.userAgentContains_('Tizen 2');
  117. }
  118. /**
  119. * Check if the current platform is a WebOS.
  120. *
  121. * @return {boolean}
  122. */
  123. static isWebOS() {
  124. return shaka.util.Platform.userAgentContains_('Web0S');
  125. }
  126. /**
  127. * Check if the current platform is a WebOS 3.
  128. *
  129. * @return {boolean}
  130. */
  131. static isWebOS3() {
  132. // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
  133. return shaka.util.Platform.userAgentContains_('Web0S') &&
  134. shaka.util.Platform.userAgentContains_(
  135. 'Chrome/38.0.2125.122 Safari/537.36');
  136. }
  137. /**
  138. * Check if the current platform is a Google Chromecast.
  139. *
  140. * @return {boolean}
  141. */
  142. static isChromecast() {
  143. return shaka.util.Platform.userAgentContains_('CrKey');
  144. }
  145. /**
  146. * Check if the current platform is a Android-based Cast devices.
  147. *
  148. * @return {boolean}
  149. */
  150. static isAndroidCastDevice() {
  151. return shaka.util.Platform.isChromecast() &&
  152. shaka.util.Platform.userAgentContains_('Android');
  153. }
  154. /**
  155. * Check if the current platform is Google Chrome.
  156. *
  157. * @return {boolean}
  158. */
  159. static isChrome() {
  160. // The Edge user agent will also contain the "Chrome" keyword, so we need
  161. // to make sure this is not Edge.
  162. return shaka.util.Platform.userAgentContains_('Chrome') &&
  163. !shaka.util.Platform.isEdge();
  164. }
  165. /**
  166. * Check if the current platform is from Apple.
  167. *
  168. * Returns true on all iOS browsers and on desktop Safari.
  169. *
  170. * Returns false for non-Safari browsers on macOS, which are independent of
  171. * Apple.
  172. *
  173. * @return {boolean}
  174. */
  175. static isApple() {
  176. return !!navigator.vendor && navigator.vendor.includes('Apple') &&
  177. !shaka.util.Platform.isTizen();
  178. }
  179. /**
  180. * Check if the current platform is Playstation 5.
  181. *
  182. * Returns true on Playstation 5 browsers.
  183. *
  184. * Returns false for Playstation 5 browsers
  185. *
  186. * @return {boolean}
  187. */
  188. static isPS5() {
  189. return shaka.util.Platform.userAgentContains_('PlayStation 5');
  190. }
  191. /**
  192. * Returns a major version number for Safari, or Safari-based iOS browsers.
  193. *
  194. * For example:
  195. * - Safari 13.0.4 on macOS returns 13.
  196. * - Safari on iOS 13.3.1 returns 13.
  197. * - Chrome on iOS 13.3.1 returns 13 (since this is based on Safari/WebKit).
  198. * - Chrome on macOS returns null (since this is independent of Apple).
  199. *
  200. * Returns null on Firefox on iOS, where this version information is not
  201. * available.
  202. *
  203. * @return {?number} A major version number or null if not iOS.
  204. */
  205. static safariVersion() {
  206. // All iOS browsers and desktop Safari will return true for isApple().
  207. if (!shaka.util.Platform.isApple()) {
  208. return null;
  209. }
  210. // This works for iOS Safari and desktop Safari, which contain something
  211. // like "Version/13.0" indicating the major Safari or iOS version.
  212. let match = navigator.userAgent.match(/Version\/(\d+)/);
  213. if (match) {
  214. return parseInt(match[1], /* base= */ 10);
  215. }
  216. // This works for all other browsers on iOS, which contain something like
  217. // "OS 13_3" indicating the major & minor iOS version.
  218. match = navigator.userAgent.match(/OS (\d+)(?:_\d+)?/);
  219. if (match) {
  220. return parseInt(match[1], /* base= */ 10);
  221. }
  222. return null;
  223. }
  224. /**
  225. * Check if the current platform is Apple Safari
  226. * or Safari-based iOS browsers.
  227. *
  228. * @return {boolean}
  229. */
  230. static isSafari() {
  231. return !!shaka.util.Platform.safariVersion();
  232. }
  233. /**
  234. * Guesses if the platform is a mobile one (iOS or Android).
  235. *
  236. * @return {boolean}
  237. */
  238. static isMobile() {
  239. if (/(?:iPhone|iPad|iPod|Android)/.test(navigator.userAgent)) {
  240. // This is Android, iOS, or iPad < 13.
  241. return true;
  242. }
  243. // Starting with iOS 13 on iPad, the user agent string no longer has the
  244. // word "iPad" in it. It looks very similar to desktop Safari. This seems
  245. // to be intentional on Apple's part.
  246. // See: https://forums.developer.apple.com/thread/119186
  247. //
  248. // So if it's an Apple device with multi-touch support, assume it's a mobile
  249. // device. If some future iOS version starts masking their user agent on
  250. // both iPhone & iPad, this clause should still work. If a future
  251. // multi-touch desktop Mac is released, this will need some adjustment.
  252. //
  253. // As of January 2020, this is mainly used to adjust the default UI config
  254. // for mobile devices, so it's low risk if something changes to break this
  255. // detection.
  256. return shaka.util.Platform.isApple() && navigator.maxTouchPoints > 1;
  257. }
  258. /**
  259. * Check if the user agent contains a key. This is the best way we know of
  260. * right now to detect platforms. If there is a better way, please send a
  261. * PR.
  262. *
  263. * @param {string} key
  264. * @return {boolean}
  265. * @private
  266. */
  267. static userAgentContains_(key) {
  268. const userAgent = navigator.userAgent || '';
  269. return userAgent.includes(key);
  270. }
  271. /**
  272. * For canPlayType queries, we just need any instance.
  273. *
  274. * First, use a cached element from a previous query.
  275. * Second, search the page for one.
  276. * Third, create a temporary one.
  277. *
  278. * Cached elements expire in one second so that they can be GC'd or removed.
  279. *
  280. * @return {!HTMLMediaElement}
  281. */
  282. static anyMediaElement() {
  283. const Platform = shaka.util.Platform;
  284. if (Platform.cachedMediaElement_) {
  285. return Platform.cachedMediaElement_;
  286. }
  287. if (!Platform.cacheExpirationTimer_) {
  288. Platform.cacheExpirationTimer_ = new shaka.util.Timer(() => {
  289. Platform.cachedMediaElement_ = null;
  290. });
  291. }
  292. Platform.cachedMediaElement_ = /** @type {HTMLMediaElement} */(
  293. document.getElementsByTagName('video')[0] ||
  294. document.getElementsByTagName('audio')[0]);
  295. if (!Platform.cachedMediaElement_) {
  296. Platform.cachedMediaElement_ = /** @type {!HTMLMediaElement} */(
  297. document.createElement('video'));
  298. }
  299. Platform.cacheExpirationTimer_.tickAfter(/* seconds= */ 1);
  300. return Platform.cachedMediaElement_;
  301. }
  302. /**
  303. * Returns true if the platform requires encryption information in all init
  304. * segments. For such platforms, MediaSourceEngine will attempt to work
  305. * around a lack of such info by inserting fake encryption information into
  306. * initialization segments.
  307. *
  308. * @return {boolean}
  309. * @see https://github.com/shaka-project/shaka-player/issues/2759
  310. */
  311. static requiresEncryptionInfoInAllInitSegments() {
  312. const Platform = shaka.util.Platform;
  313. return Platform.isTizen() || Platform.isXboxOne();
  314. }
  315. /**
  316. * Returns true if MediaKeys is polyfilled
  317. *
  318. * @return {boolean}
  319. */
  320. static isMediaKeysPolyfilled() {
  321. if (window.shakaMediaKeysPolyfill) {
  322. return true;
  323. }
  324. return false;
  325. }
  326. };
  327. /** @private {shaka.util.Timer} */
  328. shaka.util.Platform.cacheExpirationTimer_ = null;
  329. /** @private {HTMLMediaElement} */
  330. shaka.util.Platform.cachedMediaElement_ = null;