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.log');
  8. goog.require('shaka.util.DrmUtils');
  9. goog.require('shaka.util.Timer');
  10. /**
  11. * A wrapper for platform-specific functions.
  12. *
  13. * @final
  14. */
  15. shaka.util.Platform = class {
  16. /**
  17. * Check if the current platform supports media source. We assume that if
  18. * the current platform supports media source, then we can use media source
  19. * as per its design.
  20. *
  21. * @return {boolean}
  22. */
  23. static supportsMediaSource() {
  24. const mediaSource = window.ManagedMediaSource || window.MediaSource;
  25. // Browsers that lack a media source implementation will have no reference
  26. // to |window.MediaSource|. Platforms that we see having problematic media
  27. // source implementations will have this reference removed via a polyfill.
  28. if (!mediaSource) {
  29. return false;
  30. }
  31. // Some very old MediaSource implementations didn't have isTypeSupported.
  32. if (!mediaSource.isTypeSupported) {
  33. return false;
  34. }
  35. return true;
  36. }
  37. /**
  38. * Returns true if the media type is supported natively by the platform.
  39. *
  40. * @param {string} mimeType
  41. * @return {boolean}
  42. */
  43. static supportsMediaType(mimeType) {
  44. const video = shaka.util.Platform.anyMediaElement();
  45. return video.canPlayType(mimeType) != '';
  46. }
  47. /**
  48. * Check if the current platform is MS Edge.
  49. *
  50. * @return {boolean}
  51. */
  52. static isEdge() {
  53. // Legacy Edge contains "Edge/version".
  54. // Chromium-based Edge contains "Edg/version" (no "e").
  55. if (navigator.userAgent.match(/Edge?\//)) {
  56. return true;
  57. }
  58. return false;
  59. }
  60. /**
  61. * Check if the current platform is Legacy Edge.
  62. *
  63. * @return {boolean}
  64. */
  65. static isLegacyEdge() {
  66. // Legacy Edge contains "Edge/version".
  67. // Chromium-based Edge contains "Edg/version" (no "e").
  68. if (navigator.userAgent.match(/Edge\//)) {
  69. return true;
  70. }
  71. return false;
  72. }
  73. /**
  74. * Check if the current platform is MS IE.
  75. *
  76. * @return {boolean}
  77. */
  78. static isIE() {
  79. return shaka.util.Platform.userAgentContains_('Trident/');
  80. }
  81. /**
  82. * Check if the current platform is an Xbox One.
  83. *
  84. * @return {boolean}
  85. */
  86. static isXboxOne() {
  87. return shaka.util.Platform.userAgentContains_('Xbox One');
  88. }
  89. /**
  90. * Check if the current platform is a Tizen TV.
  91. *
  92. * @return {boolean}
  93. */
  94. static isTizen() {
  95. return shaka.util.Platform.userAgentContains_('Tizen');
  96. }
  97. /**
  98. * Check if the current platform is a Tizen 6 TV.
  99. *
  100. * @return {boolean}
  101. */
  102. static isTizen6() {
  103. return shaka.util.Platform.userAgentContains_('Tizen 6');
  104. }
  105. /**
  106. * Check if the current platform is a Tizen 5.0 TV.
  107. *
  108. * @return {boolean}
  109. */
  110. static isTizen5_0() {
  111. return shaka.util.Platform.userAgentContains_('Tizen 5.0');
  112. }
  113. /**
  114. * Check if the current platform is a Tizen 5 TV.
  115. *
  116. * @return {boolean}
  117. */
  118. static isTizen5() {
  119. return shaka.util.Platform.userAgentContains_('Tizen 5');
  120. }
  121. /**
  122. * Check if the current platform is a Tizen 4 TV.
  123. *
  124. * @return {boolean}
  125. */
  126. static isTizen4() {
  127. return shaka.util.Platform.userAgentContains_('Tizen 4');
  128. }
  129. /**
  130. * Check if the current platform is a Tizen 3 TV.
  131. *
  132. * @return {boolean}
  133. */
  134. static isTizen3() {
  135. return shaka.util.Platform.userAgentContains_('Tizen 3');
  136. }
  137. /**
  138. * Check if the current platform is a Tizen 2 TV.
  139. *
  140. * @return {boolean}
  141. */
  142. static isTizen2() {
  143. return shaka.util.Platform.userAgentContains_('Tizen 2');
  144. }
  145. /**
  146. * Check if the current platform is a WebOS.
  147. *
  148. * @return {boolean}
  149. */
  150. static isWebOS() {
  151. return shaka.util.Platform.userAgentContains_('Web0S');
  152. }
  153. /**
  154. * Check if the current platform is a WebOS 3.
  155. *
  156. * @return {boolean}
  157. */
  158. static isWebOS3() {
  159. // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
  160. return shaka.util.Platform.isWebOS() &&
  161. shaka.util.Platform.chromeVersion() === 38;
  162. }
  163. /**
  164. * Check if the current platform is a WebOS 4.
  165. *
  166. * @return {boolean}
  167. */
  168. static isWebOS4() {
  169. // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
  170. return shaka.util.Platform.isWebOS() &&
  171. shaka.util.Platform.chromeVersion() === 53;
  172. }
  173. /**
  174. * Check if the current platform is a WebOS 5.
  175. *
  176. * @return {boolean}
  177. */
  178. static isWebOS5() {
  179. // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
  180. return shaka.util.Platform.isWebOS() &&
  181. shaka.util.Platform.chromeVersion() === 68;
  182. }
  183. /**
  184. * Check if the current platform is a Google Chromecast.
  185. *
  186. * @return {boolean}
  187. */
  188. static isChromecast() {
  189. return shaka.util.Platform.userAgentContains_('CrKey');
  190. }
  191. /**
  192. * Check if the current platform is a Google Chromecast with Android
  193. * (i.e. Chromecast with GoogleTV).
  194. *
  195. * @return {boolean}
  196. */
  197. static isAndroidCastDevice() {
  198. const Platform = shaka.util.Platform;
  199. return Platform.isChromecast() && Platform.isAndroid();
  200. }
  201. /**
  202. * Check if the current platform is a Google Chromecast with Fuchsia
  203. * (i.e. Google Nest Hub).
  204. *
  205. * @return {boolean}
  206. */
  207. static isFuchsiaCastDevice() {
  208. const Platform = shaka.util.Platform;
  209. return Platform.isChromecast() && Platform.isFuchsia();
  210. }
  211. /**
  212. * Returns a major version number for Chrome, or Chromium-based browsers.
  213. *
  214. * For example:
  215. * - Chrome 106.0.5249.61 returns 106.
  216. * - Edge 106.0.1370.34 returns 106 (since this is based on Chromium).
  217. * - Safari returns null (since this is independent of Chromium).
  218. *
  219. * @return {?number} A major version number or null if not Chromium-based.
  220. */
  221. static chromeVersion() {
  222. if (!shaka.util.Platform.isChrome()) {
  223. return null;
  224. }
  225. // Looking for something like "Chrome/106.0.0.0".
  226. const match = navigator.userAgent.match(/Chrome\/(\d+)/);
  227. if (match) {
  228. return parseInt(match[1], /* base= */ 10);
  229. }
  230. return null;
  231. }
  232. /**
  233. * Check if the current platform is Google Chrome.
  234. *
  235. * @return {boolean}
  236. */
  237. static isChrome() {
  238. // The Edge Legacy user agent will also contain the "Chrome" keyword, so we
  239. // need to make sure this is not Edge Legacy.
  240. return shaka.util.Platform.userAgentContains_('Chrome') &&
  241. !shaka.util.Platform.isLegacyEdge();
  242. }
  243. /**
  244. * Check if the current platform is Firefox.
  245. *
  246. * @return {boolean}
  247. */
  248. static isFirefox() {
  249. return shaka.util.Platform.userAgentContains_('Firefox');
  250. }
  251. /**
  252. * Check if the current platform is from Apple.
  253. *
  254. * Returns true on all iOS browsers and on desktop Safari.
  255. *
  256. * Returns false for non-Safari browsers on macOS, which are independent of
  257. * Apple.
  258. *
  259. * @return {boolean}
  260. */
  261. static isApple() {
  262. return !!navigator.vendor && navigator.vendor.includes('Apple') &&
  263. !shaka.util.Platform.isTizen() &&
  264. !shaka.util.Platform.isEOS() &&
  265. !shaka.util.Platform.isAPL() &&
  266. !shaka.util.Platform.isVirginMedia() &&
  267. !shaka.util.Platform.isOrange() &&
  268. !shaka.util.Platform.isPS4() &&
  269. !shaka.util.Platform.isAmazonFireTV() &&
  270. !shaka.util.Platform.isComcastX1() &&
  271. !shaka.util.Platform.isZenterio() &&
  272. !shaka.util.Platform.isSkyQ();
  273. }
  274. /**
  275. * Check if the current platform is Playstation 5.
  276. *
  277. * Returns true on Playstation 5 browsers.
  278. *
  279. * Returns false for Playstation 5 browsers
  280. *
  281. * @return {boolean}
  282. */
  283. static isPS5() {
  284. return shaka.util.Platform.userAgentContains_('PlayStation 5');
  285. }
  286. /**
  287. * Check if the current platform is Playstation 4.
  288. */
  289. static isPS4() {
  290. return shaka.util.Platform.userAgentContains_('PlayStation 4');
  291. }
  292. /**
  293. * Check if the current platform is Hisense.
  294. */
  295. static isHisense() {
  296. return shaka.util.Platform.userAgentContains_('Hisense') ||
  297. shaka.util.Platform.userAgentContains_('VIDAA');
  298. }
  299. /**
  300. * Check if the current platform is Virgin Media device.
  301. */
  302. static isVirginMedia() {
  303. return shaka.util.Platform.userAgentContains_('VirginMedia');
  304. }
  305. /**
  306. * Check if the current platform is Orange.
  307. */
  308. static isOrange() {
  309. return shaka.util.Platform.userAgentContains_('SOPOpenBrowser');
  310. }
  311. /**
  312. * Check if the current platform is SkyQ STB.
  313. */
  314. static isSkyQ() {
  315. return shaka.util.Platform.userAgentContains_('Sky_STB');
  316. }
  317. /**
  318. * Check if the current platform is Amazon Fire TV.
  319. * https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html
  320. *
  321. * @return {boolean}
  322. */
  323. static isAmazonFireTV() {
  324. return shaka.util.Platform.userAgentContains_('AFT');
  325. }
  326. /**
  327. * Check if the current platform is Comcast X1.
  328. * @return {boolean}
  329. */
  330. static isComcastX1() {
  331. return shaka.util.Platform.userAgentContains_('WPE');
  332. }
  333. /**
  334. * Check if the current platform is Deutsche Telecom Zenterio STB.
  335. * @return {boolean}
  336. */
  337. static isZenterio() {
  338. return shaka.util.Platform.userAgentContains_('DT_STB_BCM');
  339. }
  340. /**
  341. * Returns a major version number for Safari, or Safari-based iOS browsers.
  342. *
  343. * For example:
  344. * - Safari 13.0.4 on macOS returns 13.
  345. * - Safari on iOS 13.3.1 returns 13.
  346. * - Chrome on iOS 13.3.1 returns 13 (since this is based on Safari/WebKit).
  347. * - Chrome on macOS returns null (since this is independent of Apple).
  348. *
  349. * Returns null on Firefox on iOS, where this version information is not
  350. * available.
  351. *
  352. * @return {?number} A major version number or null if not iOS.
  353. */
  354. static safariVersion() {
  355. // All iOS browsers and desktop Safari will return true for isApple().
  356. if (!shaka.util.Platform.isApple()) {
  357. return null;
  358. }
  359. // This works for iOS Safari and desktop Safari, which contain something
  360. // like "Version/13.0" indicating the major Safari or iOS version.
  361. let match = navigator.userAgent.match(/Version\/(\d+)/);
  362. if (match) {
  363. return parseInt(match[1], /* base= */ 10);
  364. }
  365. // This works for all other browsers on iOS, which contain something like
  366. // "OS 13_3" indicating the major & minor iOS version.
  367. match = navigator.userAgent.match(/OS (\d+)(?:_\d+)?/);
  368. if (match) {
  369. return parseInt(match[1], /* base= */ 10);
  370. }
  371. return null;
  372. }
  373. /**
  374. * Check if the current platform is Apple Safari
  375. * or Safari-based iOS browsers.
  376. *
  377. * @return {boolean}
  378. */
  379. static isSafari() {
  380. return !!shaka.util.Platform.safariVersion();
  381. }
  382. /**
  383. * Check if the current platform is an EOS set-top box.
  384. *
  385. * @return {boolean}
  386. */
  387. static isEOS() {
  388. return shaka.util.Platform.userAgentContains_('PC=EOS');
  389. }
  390. /**
  391. * Check if the current platform is an APL set-top box.
  392. *
  393. * @return {boolean}
  394. */
  395. static isAPL() {
  396. return shaka.util.Platform.userAgentContains_('PC=APL');
  397. }
  398. /**
  399. * Guesses if the platform is a mobile one (iOS or Android).
  400. *
  401. * @return {boolean}
  402. */
  403. static isMobile() {
  404. if (/(?:iPhone|iPad|iPod|Android)/.test(navigator.userAgent)) {
  405. // This is Android, iOS, or iPad < 13.
  406. return true;
  407. }
  408. // Starting with iOS 13 on iPad, the user agent string no longer has the
  409. // word "iPad" in it. It looks very similar to desktop Safari. This seems
  410. // to be intentional on Apple's part.
  411. // See: https://forums.developer.apple.com/thread/119186
  412. //
  413. // So if it's an Apple device with multi-touch support, assume it's a mobile
  414. // device. If some future iOS version starts masking their user agent on
  415. // both iPhone & iPad, this clause should still work. If a future
  416. // multi-touch desktop Mac is released, this will need some adjustment.
  417. //
  418. // As of January 2020, this is mainly used to adjust the default UI config
  419. // for mobile devices, so it's low risk if something changes to break this
  420. // detection.
  421. return shaka.util.Platform.isApple() && navigator.maxTouchPoints > 1;
  422. }
  423. /**
  424. * Return true if the platform is a Mac, regardless of the browser.
  425. *
  426. * @return {boolean}
  427. */
  428. static isMac() {
  429. // Try the newer standard first.
  430. if (navigator.userAgentData && navigator.userAgentData.platform) {
  431. return navigator.userAgentData.platform.toLowerCase() == 'macos';
  432. }
  433. // Fall back to the old API, with less strict matching.
  434. if (!navigator.platform) {
  435. return false;
  436. }
  437. return navigator.platform.toLowerCase().includes('mac');
  438. }
  439. /**
  440. * Return true if the platform is a VisionOS.
  441. *
  442. * @return {boolean}
  443. */
  444. static isVisionOS() {
  445. if (!shaka.util.Platform.isMac()) {
  446. return false;
  447. }
  448. if (!('xr' in navigator)) {
  449. return false;
  450. }
  451. return true;
  452. }
  453. /**
  454. * Return true if the platform is a Windows, regardless of the browser.
  455. *
  456. * @return {boolean}
  457. */
  458. static isWindows() {
  459. // Try the newer standard first.
  460. if (navigator.userAgentData && navigator.userAgentData.platform) {
  461. return navigator.userAgentData.platform.toLowerCase() == 'windows';
  462. }
  463. // Fall back to the old API, with less strict matching.
  464. if (!navigator.platform) {
  465. return false;
  466. }
  467. return navigator.platform.toLowerCase().includes('win32');
  468. }
  469. /**
  470. * Return true if the platform is a Android, regardless of the browser.
  471. *
  472. * @return {boolean}
  473. */
  474. static isAndroid() {
  475. return shaka.util.Platform.userAgentContains_('Android');
  476. }
  477. /**
  478. * Return true if the platform is a Fuchsia, regardless of the browser.
  479. *
  480. * @return {boolean}
  481. */
  482. static isFuchsia() {
  483. return shaka.util.Platform.userAgentContains_('Fuchsia');
  484. }
  485. /**
  486. * Return true if the platform is controlled by a remote control.
  487. *
  488. * @return {boolean}
  489. */
  490. static isSmartTV() {
  491. const Platform = shaka.util.Platform;
  492. if (Platform.isTizen() || Platform.isWebOS() ||
  493. Platform.isXboxOne() || Platform.isPS4() ||
  494. Platform.isPS5() || Platform.isAmazonFireTV() ||
  495. Platform.isEOS() || Platform.isAPL() ||
  496. Platform.isVirginMedia() || Platform.isOrange() ||
  497. Platform.isComcastX1() || Platform.isChromecast() ||
  498. Platform.isHisense() || Platform.isZenterio()) {
  499. return true;
  500. }
  501. return false;
  502. }
  503. /**
  504. * Check if the user agent contains a key. This is the best way we know of
  505. * right now to detect platforms. If there is a better way, please send a
  506. * PR.
  507. *
  508. * @param {string} key
  509. * @return {boolean}
  510. * @private
  511. */
  512. static userAgentContains_(key) {
  513. const userAgent = navigator.userAgent || '';
  514. return userAgent.includes(key);
  515. }
  516. /**
  517. * For canPlayType queries, we just need any instance.
  518. *
  519. * First, use a cached element from a previous query.
  520. * Second, search the page for one.
  521. * Third, create a temporary one.
  522. *
  523. * Cached elements expire in one second so that they can be GC'd or removed.
  524. *
  525. * @return {!HTMLMediaElement}
  526. */
  527. static anyMediaElement() {
  528. const Platform = shaka.util.Platform;
  529. if (Platform.cachedMediaElement_) {
  530. return Platform.cachedMediaElement_;
  531. }
  532. if (!Platform.cacheExpirationTimer_) {
  533. Platform.cacheExpirationTimer_ = new shaka.util.Timer(() => {
  534. Platform.cachedMediaElement_ = null;
  535. });
  536. }
  537. Platform.cachedMediaElement_ = /** @type {HTMLMediaElement} */(
  538. document.getElementsByTagName('video')[0] ||
  539. document.getElementsByTagName('audio')[0]);
  540. if (!Platform.cachedMediaElement_) {
  541. Platform.cachedMediaElement_ = /** @type {!HTMLMediaElement} */(
  542. document.createElement('video'));
  543. }
  544. Platform.cacheExpirationTimer_.tickAfter(/* seconds= */ 1);
  545. return Platform.cachedMediaElement_;
  546. }
  547. /**
  548. * Returns true if the platform requires encryption information in all init
  549. * segments. For such platforms, MediaSourceEngine will attempt to work
  550. * around a lack of such info by inserting fake encryption information into
  551. * initialization segments.
  552. *
  553. * @param {?string} keySystem
  554. * @return {boolean}
  555. * @see https://github.com/shaka-project/shaka-player/issues/2759
  556. */
  557. static requiresEncryptionInfoInAllInitSegments(keySystem) {
  558. const Platform = shaka.util.Platform;
  559. const isPlayReady = shaka.util.DrmUtils.isPlayReadyKeySystem(keySystem);
  560. return Platform.isTizen() || Platform.isXboxOne() || Platform.isOrange() ||
  561. (Platform.isEdge() && Platform.isWindows() && isPlayReady);
  562. }
  563. /**
  564. * Returns true if the platform supports SourceBuffer "sequence mode".
  565. *
  566. * @return {boolean}
  567. */
  568. static supportsSequenceMode() {
  569. const Platform = shaka.util.Platform;
  570. if (Platform.isTizen3() || Platform.isTizen2() ||
  571. Platform.isWebOS3() || Platform.isPS4()) {
  572. return false;
  573. }
  574. return true;
  575. }
  576. /**
  577. * Returns if codec switching SMOOTH is known reliable device support.
  578. *
  579. * Some devices are known not to support `MediaSource.changeType`
  580. * well. These devices should use the reload strategy. If a device
  581. * reports that it supports `changeType` but support it unreliably
  582. * it should be added to this list.
  583. *
  584. * @return {boolean}
  585. */
  586. static supportsSmoothCodecSwitching() {
  587. const Platform = shaka.util.Platform;
  588. if (Platform.isTizen2() || Platform.isTizen3() || Platform.isTizen4() ||
  589. Platform.isTizen5() || Platform.isTizen6() || Platform.isWebOS3() ||
  590. Platform.isWebOS4() || Platform.isWebOS5() || Platform.isPS4() ||
  591. Platform.isPS5()) {
  592. return false;
  593. }
  594. // Older chromecasts without GoogleTV seem to not support SMOOTH properly.
  595. if (Platform.isChromecast() && !Platform.isAndroidCastDevice() &&
  596. !Platform.isFuchsiaCastDevice()) {
  597. return false;
  598. }
  599. // See: https://chromium-review.googlesource.com/c/chromium/src/+/4577759
  600. if (Platform.isWindows() && Platform.isEdge()) {
  601. return false;
  602. }
  603. return true;
  604. }
  605. /**
  606. * On some platforms, such as v1 Chromecasts, the act of seeking can take a
  607. * significant amount of time.
  608. *
  609. * @return {boolean}
  610. */
  611. static isSeekingSlow() {
  612. const Platform = shaka.util.Platform;
  613. if (Platform.isChromecast()) {
  614. if (Platform.isAndroidCastDevice()) {
  615. // Android-based Chromecasts are new enough to not be a problem.
  616. return false;
  617. } else {
  618. return true;
  619. }
  620. }
  621. return false;
  622. }
  623. /**
  624. * Returns true if MediaKeys is polyfilled
  625. *
  626. * @param {string=} polyfillType
  627. * @return {boolean}
  628. */
  629. static isMediaKeysPolyfilled(polyfillType) {
  630. if (polyfillType) {
  631. return polyfillType === window.shakaMediaKeysPolyfill;
  632. }
  633. return !!window.shakaMediaKeysPolyfill;
  634. }
  635. /**
  636. * Detect the maximum resolution that the platform's hardware can handle.
  637. *
  638. * @return {!Promise.<shaka.extern.Resolution>}
  639. */
  640. static async detectMaxHardwareResolution() {
  641. const Platform = shaka.util.Platform;
  642. /** @type {shaka.extern.Resolution} */
  643. const maxResolution = {
  644. width: Infinity,
  645. height: Infinity,
  646. };
  647. if (Platform.isChromecast()) {
  648. // In our tests, the original Chromecast seems to have trouble decoding
  649. // above 1080p. It would be a waste to select a higher res anyway, given
  650. // that the device only outputs 1080p to begin with.
  651. // Chromecast has an extension to query the device/display's resolution.
  652. const hasCanDisplayType = window.cast && cast.__platform__ &&
  653. cast.__platform__.canDisplayType;
  654. // Some hub devices can only do 720p. Default to that.
  655. maxResolution.width = 1280;
  656. maxResolution.height = 720;
  657. try {
  658. if (hasCanDisplayType && await cast.__platform__.canDisplayType(
  659. 'video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
  660. // The device and display can both do 4k. Assume a 4k limit.
  661. maxResolution.width = 3840;
  662. maxResolution.height = 2160;
  663. } else if (hasCanDisplayType && await cast.__platform__.canDisplayType(
  664. 'video/mp4; codecs="avc1.640028"; width=1920; height=1080')) {
  665. // Most Chromecasts can do 1080p.
  666. maxResolution.width = 1920;
  667. maxResolution.height = 1080;
  668. }
  669. } catch (error) {
  670. // This shouldn't generally happen. Log the error.
  671. shaka.log.alwaysError('Failed to check canDisplayType:', error);
  672. // Now ignore the error and let the 720p default stand.
  673. }
  674. } else if (Platform.isTizen()) {
  675. maxResolution.width = 1920;
  676. maxResolution.height = 1080;
  677. try {
  678. if (webapis.systeminfo && webapis.systeminfo.getMaxVideoResolution) {
  679. const maxVideoResolution =
  680. webapis.systeminfo.getMaxVideoResolution();
  681. maxResolution.width = maxVideoResolution.width;
  682. maxResolution.height = maxVideoResolution.height;
  683. } else {
  684. if (webapis.productinfo.is8KPanelSupported &&
  685. webapis.productinfo.is8KPanelSupported()) {
  686. maxResolution.width = 7680;
  687. maxResolution.height = 4320;
  688. } else if (webapis.productinfo.isUdPanelSupported &&
  689. webapis.productinfo.isUdPanelSupported()) {
  690. maxResolution.width = 3840;
  691. maxResolution.height = 2160;
  692. }
  693. }
  694. } catch (e) {
  695. shaka.log.alwaysWarn('Tizen: Error detecting screen size, default ' +
  696. 'screen size 1920x1080.');
  697. }
  698. } else if (Platform.isXboxOne()) {
  699. maxResolution.width = 1920;
  700. maxResolution.height = 1080;
  701. try {
  702. let winRT = undefined;
  703. // Try to access to WinRT for WebView, if it's not defined,
  704. // try to access to WinRT for WebView2, if it's not defined either,
  705. // let it throw.
  706. if (typeof Windows !== 'undefined') {
  707. winRT = Windows;
  708. } else {
  709. winRT = chrome.webview.hostObjects.sync.Windows;
  710. }
  711. const protectionCapabilities =
  712. new winRT.Media.Protection.ProtectionCapabilities();
  713. const protectionResult =
  714. winRT.Media.Protection.ProtectionCapabilityResult;
  715. // isTypeSupported may return "maybe", which means the operation is not
  716. // completed. This means we need to retry
  717. // https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilityresult?view=winrt-22621
  718. let result = null;
  719. const type =
  720. 'video/mp4;codecs="hvc1,mp4a";features="decode-res-x=3840,' +
  721. 'decode-res-y=2160,decode-bitrate=20000,decode-fps=30,' +
  722. 'decode-bpc=10,display-res-x=3840,display-res-y=2160,' +
  723. 'display-bpc=8"';
  724. const keySystem = 'com.microsoft.playready.recommendation';
  725. do {
  726. result = protectionCapabilities.isTypeSupported(type, keySystem);
  727. } while (result === protectionResult.maybe);
  728. if (result === protectionResult.probably) {
  729. maxResolution.width = 3840;
  730. maxResolution.height = 2160;
  731. }
  732. } catch (e) {
  733. shaka.log.alwaysWarn('Xbox: Error detecting screen size, default ' +
  734. 'screen size 1920x1080.');
  735. }
  736. } else if (Platform.isWebOS()) {
  737. try {
  738. const deviceInfo =
  739. /** @type {{screenWidth: number, screenHeight: number}} */(
  740. JSON.parse(window.PalmSystem.deviceInfo));
  741. // WebOS has always been able to do 1080p. Assume a 1080p limit.
  742. maxResolution.width = Math.max(1920, deviceInfo['screenWidth']);
  743. maxResolution.height = Math.max(1080, deviceInfo['screenHeight']);
  744. } catch (e) {
  745. shaka.log.alwaysWarn('WebOS: Error detecting screen size, default ' +
  746. 'screen size 1920x1080.');
  747. maxResolution.width = 1920;
  748. maxResolution.height = 1080;
  749. }
  750. } else if (Platform.isHisense()) {
  751. // eslint-disable-next-line new-cap
  752. if (window.Hisense_Get4KSupportState &&
  753. // eslint-disable-next-line new-cap
  754. window.Hisense_Get4KSupportState()) {
  755. maxResolution.width = 3840;
  756. maxResolution.height = 2160;
  757. } else {
  758. maxResolution.width = 1920;
  759. maxResolution.height = 1080;
  760. }
  761. } else if (Platform.isPS4() || Platform.isPS5()) {
  762. let supports4K = false;
  763. try {
  764. const result = await window.msdk.device.getDisplayInfo();
  765. supports4K = result.resolution === '4K';
  766. } catch (e) {
  767. try {
  768. const result = await window.msdk.device.getDisplayInfoImmediate();
  769. supports4K = result.resolution === '4K';
  770. } catch (e) {
  771. shaka.log.alwaysWarn(
  772. 'PlayStation: Failed to get the display info:', e);
  773. }
  774. }
  775. if (supports4K) {
  776. maxResolution.width = 3840;
  777. maxResolution.height = 2160;
  778. } else {
  779. maxResolution.width = 1920;
  780. maxResolution.height = 1080;
  781. }
  782. }
  783. return maxResolution;
  784. }
  785. };
  786. /** @private {shaka.util.Timer} */
  787. shaka.util.Platform.cacheExpirationTimer_ = null;
  788. /** @private {HTMLMediaElement} */
  789. shaka.util.Platform.cachedMediaElement_ = null;