Source: lib/util/platform.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.util.Platform');
  18. goog.require('shaka.util.Timer');
  19. /**
  20. * A wrapper for platform-specific functions.
  21. *
  22. * @final
  23. */
  24. shaka.util.Platform = class {
  25. /**
  26. * Check if the current platform supports media source. We assume that if
  27. * the current platform supports media source, then we can use media source
  28. * as per its design.
  29. *
  30. * @return {boolean}
  31. */
  32. static supportsMediaSource() {
  33. // Browsers that lack a media source implementation will have no reference
  34. // to |window.MediaSource|. Platforms that we see having problematic media
  35. // source implementations will have this reference removed via a polyfill.
  36. if (!window.MediaSource) {
  37. return false;
  38. }
  39. // Some very old MediaSource implementations didn't have isTypeSupported.
  40. if (!MediaSource.isTypeSupported) {
  41. return false;
  42. }
  43. return true;
  44. }
  45. /**
  46. * Returns true if the media type is supported natively by the platform.
  47. *
  48. * @param {string} mimeType
  49. * @return {boolean}
  50. */
  51. static supportsMediaType(mimeType) {
  52. const video = shaka.util.Platform.anyMediaElement();
  53. return video.canPlayType(mimeType) != '';
  54. }
  55. /**
  56. * Check if the current platform is MS Edge.
  57. *
  58. * @return {boolean}
  59. */
  60. static isEdge() {
  61. // Legacy Edge contains "Edge/version".
  62. // Chromium-based Edge contains "Edg/version" (no "e").
  63. if (navigator.userAgent.match(/Edge?\//)) {
  64. return true;
  65. }
  66. return false;
  67. }
  68. /**
  69. * Check if the current platform is Legacy Edge.
  70. *
  71. * @return {boolean}
  72. */
  73. static isLegacyEdge() {
  74. // Legacy Edge contains "Edge/version".
  75. // Chromium-based Edge contains "Edg/version" (no "e").
  76. if (navigator.userAgent.match(/Edge\//)) {
  77. return true;
  78. }
  79. return false;
  80. }
  81. /**
  82. * Check if the current platform is MS IE.
  83. *
  84. * @return {boolean}
  85. */
  86. static isIE() {
  87. return shaka.util.Platform.userAgentContains_('Trident/');
  88. }
  89. /**
  90. * Check if the current platform is an Xbox One.
  91. *
  92. * @return {boolean}
  93. */
  94. static isXboxOne() {
  95. return shaka.util.Platform.userAgentContains_('Xbox One');
  96. }
  97. /**
  98. * Check if the current platform is a Tizen TV.
  99. *
  100. * @return {boolean}
  101. */
  102. static isTizen() {
  103. return shaka.util.Platform.userAgentContains_('Tizen');
  104. }
  105. /**
  106. * Check if the current platform is a Tizen 4 TV.
  107. *
  108. * @return {boolean}
  109. */
  110. static isTizen4() {
  111. return shaka.util.Platform.userAgentContains_('Tizen 4');
  112. }
  113. /**
  114. * Check if the current platform is a Tizen 3 TV.
  115. *
  116. * @return {boolean}
  117. */
  118. static isTizen3() {
  119. return shaka.util.Platform.userAgentContains_('Tizen 3');
  120. }
  121. /**
  122. * Check if the current platform is a Tizen 2 TV.
  123. *
  124. * @return {boolean}
  125. */
  126. static isTizen2() {
  127. return shaka.util.Platform.userAgentContains_('Tizen 2');
  128. }
  129. /**
  130. * Check if the current platform is a WebOS.
  131. *
  132. * @return {boolean}
  133. */
  134. static isWebOS() {
  135. return shaka.util.Platform.userAgentContains_('Web0S');
  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 Google Chrome.
  147. *
  148. * @return {boolean}
  149. */
  150. static isChrome() {
  151. // The Edge user agent will also contain the "Chrome" keyword, so we need
  152. // to make sure this is not Edge.
  153. return shaka.util.Platform.userAgentContains_('Chrome') &&
  154. !shaka.util.Platform.isEdge();
  155. }
  156. /**
  157. * Check if the current platform is from Apple.
  158. *
  159. * Returns true on all iOS browsers and on desktop Safari.
  160. *
  161. * Returns false for non-Safari browsers on macOS, which are independent of
  162. * Apple.
  163. *
  164. * @return {boolean}
  165. */
  166. static isApple() {
  167. return !!navigator.vendor && navigator.vendor.includes('Apple')
  168. && !shaka.util.Platform.isTizen();
  169. }
  170. /**
  171. * Returns a major version number for Safari, or Safari-based iOS browsers.
  172. *
  173. * For example:
  174. * - Safari 13.0.4 on macOS returns 13.
  175. * - Safari on iOS 13.3.1 returns 13.
  176. * - Chrome on iOS 13.3.1 returns 13 (since this is based on Safari/WebKit).
  177. * - Chrome on macOS returns null (since this is independent of Apple).
  178. *
  179. * Returns null on Firefox on iOS, where this version information is not
  180. * available.
  181. *
  182. * @return {?number} A major version number or null if not iOS.
  183. */
  184. static safariVersion() {
  185. // All iOS browsers and desktop Safari will return true for isApple().
  186. if (!shaka.util.Platform.isApple()) {
  187. return null;
  188. }
  189. // This works for iOS Safari and desktop Safari, which contain something
  190. // like "Version/13.0" indicating the major Safari or iOS version.
  191. let match = navigator.userAgent.match(/Version\/(\d+)/);
  192. if (match) {
  193. return parseInt(match[1], /* base= */ 10);
  194. }
  195. // This works for all other browsers on iOS, which contain something like
  196. // "OS 13_3" indicating the major & minor iOS version.
  197. match = navigator.userAgent.match(/OS (\d+)(?:_\d+)?/);
  198. if (match) {
  199. return parseInt(match[1], /* base= */ 10);
  200. }
  201. return null;
  202. }
  203. /**
  204. * Guesses if the platform is a mobile one (iOS or Android).
  205. *
  206. * @return {boolean}
  207. */
  208. static isMobile() {
  209. if (/(?:iPhone|iPad|iPod|Android)/.test(navigator.userAgent)) {
  210. // This is Android, iOS, or iPad < 13.
  211. return true;
  212. }
  213. // Starting with iOS 13 on iPad, the user agent string no longer has the
  214. // word "iPad" in it. It looks very similar to desktop Safari. This seems
  215. // to be intentional on Apple's part.
  216. // See: https://forums.developer.apple.com/thread/119186
  217. //
  218. // So if it's an Apple device with multi-touch support, assume it's a mobile
  219. // device. If some future iOS version starts masking their user agent on
  220. // both iPhone & iPad, this clause should still work. If a future
  221. // multi-touch desktop Mac is released, this will need some adjustment.
  222. //
  223. // As of January 2020, this is mainly used to adjust the default UI config
  224. // for mobile devices, so it's low risk if something changes to break this
  225. // detection.
  226. return shaka.util.Platform.isApple() && navigator.maxTouchPoints > 1;
  227. }
  228. /**
  229. * Check if the user agent contains a key. This is the best way we know of
  230. * right now to detect platforms. If there is a better way, please send a
  231. * PR.
  232. *
  233. * @param {string} key
  234. * @return {boolean}
  235. * @private
  236. */
  237. static userAgentContains_(key) {
  238. const userAgent = navigator.userAgent || '';
  239. return userAgent.includes(key);
  240. }
  241. /**
  242. * For canPlayType queries, we just need any instance.
  243. *
  244. * First, use a cached element from a previous query.
  245. * Second, search the page for one.
  246. * Third, create a temporary one.
  247. *
  248. * Cached elements expire in one second so that they can be GC'd or removed.
  249. *
  250. * @return {!HTMLMediaElement}
  251. */
  252. static anyMediaElement() {
  253. const Platform = shaka.util.Platform;
  254. if (Platform.cachedMediaElement_) {
  255. return Platform.cachedMediaElement_;
  256. }
  257. if (!Platform.cacheExpirationTimer_) {
  258. Platform.cacheExpirationTimer_ = new shaka.util.Timer(() => {
  259. Platform.cachedMediaElement_ = null;
  260. });
  261. }
  262. Platform.cachedMediaElement_ = /** @type {HTMLMediaElement} */(
  263. document.getElementsByTagName('video')[0] ||
  264. document.getElementsByTagName('audio')[0]);
  265. if (!Platform.cachedMediaElement_) {
  266. Platform.cachedMediaElement_ = /** @type {!HTMLMediaElement} */(
  267. document.createElement('video'));
  268. }
  269. Platform.cacheExpirationTimer_.tickAfter(/* seconds= */ 1);
  270. return Platform.cachedMediaElement_;
  271. }
  272. };
  273. /** @private {shaka.util.Timer} */
  274. shaka.util.Platform.cacheExpirationTimer_ = null;
  275. /** @private {HTMLMediaElement} */
  276. shaka.util.Platform.cachedMediaElement_ = null;