Source: lib/polyfill/media_capabilities.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.polyfill.MediaCapabilities');
  7. goog.require('shaka.log');
  8. goog.require('shaka.polyfill');
  9. goog.require('shaka.util.Platform');
  10. /**
  11. * @summary A polyfill to provide navigator.mediaCapabilities on all browsers.
  12. * This is necessary for Tizen 3, Xbox One and possibly others we have yet to
  13. * discover.
  14. * @export
  15. */
  16. shaka.polyfill.MediaCapabilities = class {
  17. /**
  18. * Install the polyfill if needed.
  19. * @suppress {const}
  20. * @export
  21. */
  22. static install() {
  23. // Since MediaCapabilities is not fully supported on Chromecast yet, we
  24. // should always install polyfill for Chromecast.
  25. // TODO: re-evaluate MediaCapabilities in the future versions of Chromecast.
  26. // Since MediaCapabilities implementation is buggy in Apple browsers, we
  27. // should always install polyfill for Apple browsers.
  28. // See: https://github.com/shaka-project/shaka-player/issues/3530
  29. // TODO: re-evaluate MediaCapabilities in the future versions of Apple
  30. // Browsers.
  31. // Since MediaCapabilities implementation is buggy in PS5 browsers, we
  32. // should always install polyfill for PS5 browsers.
  33. // See: https://github.com/shaka-project/shaka-player/issues/3582
  34. // TODO: re-evaluate MediaCapabilities in the future versions of PS5
  35. // Browsers.
  36. if (!shaka.util.Platform.isChromecast() &&
  37. !shaka.util.Platform.isApple() &&
  38. !shaka.util.Platform.isPS4() &&
  39. !shaka.util.Platform.isPS5() &&
  40. !shaka.util.Platform.isWebOS() &&
  41. !shaka.util.Platform.isTizen() &&
  42. !shaka.util.Platform.isEOS() &&
  43. !shaka.util.Platform.isHisense() &&
  44. navigator.mediaCapabilities) {
  45. shaka.log.info(
  46. 'MediaCapabilities: Native mediaCapabilities support found.');
  47. return;
  48. }
  49. shaka.log.info('MediaCapabilities: install');
  50. if (!navigator.mediaCapabilities) {
  51. navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
  52. }
  53. // Keep the patched MediaCapabilities object from being garbage-collected in
  54. // Safari.
  55. // See https://github.com/shaka-project/shaka-player/issues/3696#issuecomment-1009472718
  56. shaka.polyfill.MediaCapabilities.originalMcap =
  57. navigator.mediaCapabilities;
  58. navigator.mediaCapabilities.decodingInfo =
  59. shaka.polyfill.MediaCapabilities.decodingInfo_;
  60. }
  61. /**
  62. * @param {!MediaDecodingConfiguration} mediaDecodingConfig
  63. * @return {!Promise.<!MediaCapabilitiesDecodingInfo>}
  64. * @private
  65. */
  66. static async decodingInfo_(mediaDecodingConfig) {
  67. const res = {
  68. supported: false,
  69. powerEfficient: true,
  70. smooth: true,
  71. keySystemAccess: null,
  72. configuration: mediaDecodingConfig,
  73. };
  74. if (!mediaDecodingConfig) {
  75. return res;
  76. }
  77. const videoConfig = mediaDecodingConfig['video'];
  78. const audioConfig = mediaDecodingConfig['audio'];
  79. if (mediaDecodingConfig.type == 'media-source') {
  80. if (!shaka.util.Platform.supportsMediaSource()) {
  81. return res;
  82. }
  83. // Use 'MediaSource.isTypeSupported' to check if the stream is supported.
  84. // Cast platforms will additionally check canDisplayType(), which
  85. // accepts extended MIME type parameters.
  86. // See: https://github.com/shaka-project/shaka-player/issues/4726
  87. if (videoConfig) {
  88. let isSupported;
  89. if (shaka.util.Platform.isChromecast()) {
  90. isSupported =
  91. shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
  92. } else if (shaka.util.Platform.isTizen()) {
  93. let extendedType = videoConfig.contentType;
  94. if (videoConfig.width && videoConfig.height) {
  95. extendedType += `; width=${videoConfig.width}`;
  96. extendedType += `; height=${videoConfig.height}`;
  97. }
  98. if (videoConfig.framerate) {
  99. extendedType += `; framerate=${videoConfig.framerate}`;
  100. }
  101. if (videoConfig.bitrate) {
  102. extendedType += `; bitrate=${videoConfig.bitrate}`;
  103. }
  104. isSupported = MediaSource.isTypeSupported(extendedType);
  105. } else {
  106. isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
  107. }
  108. if (!isSupported) {
  109. return res;
  110. }
  111. }
  112. if (audioConfig) {
  113. const contentType = audioConfig.contentType;
  114. const isSupported = MediaSource.isTypeSupported(contentType);
  115. if (!isSupported) {
  116. return res;
  117. }
  118. }
  119. } else if (mediaDecodingConfig.type == 'file') {
  120. if (videoConfig) {
  121. const contentType = videoConfig.contentType;
  122. const isSupported = shaka.util.Platform.supportsMediaType(contentType);
  123. if (!isSupported) {
  124. return res;
  125. }
  126. }
  127. if (audioConfig) {
  128. const contentType = audioConfig.contentType;
  129. const isSupported = shaka.util.Platform.supportsMediaType(contentType);
  130. if (!isSupported) {
  131. return res;
  132. }
  133. }
  134. } else {
  135. // Otherwise not supported.
  136. return res;
  137. }
  138. if (!mediaDecodingConfig.keySystemConfiguration) {
  139. // The variant is supported if it's unencrypted.
  140. res.supported = true;
  141. return Promise.resolve(res);
  142. } else {
  143. // Get the MediaKeySystemAccess for the key system.
  144. // Convert the MediaDecodingConfiguration object to a
  145. // MediaKeySystemConfiguration object.
  146. /** @type {MediaCapabilitiesKeySystemConfiguration} */
  147. const mediaCapkeySystemConfig =
  148. mediaDecodingConfig.keySystemConfiguration;
  149. const audioCapabilities = [];
  150. const videoCapabilities = [];
  151. if (mediaCapkeySystemConfig.audio) {
  152. const capability = {
  153. robustness: mediaCapkeySystemConfig.audio.robustness || '',
  154. contentType: mediaDecodingConfig.audio.contentType,
  155. };
  156. audioCapabilities.push(capability);
  157. }
  158. if (mediaCapkeySystemConfig.video) {
  159. const capability = {
  160. robustness: mediaCapkeySystemConfig.video.robustness || '',
  161. contentType: mediaDecodingConfig.video.contentType,
  162. };
  163. videoCapabilities.push(capability);
  164. }
  165. /** @type {MediaKeySystemConfiguration} */
  166. const mediaKeySystemConfig = {
  167. initDataTypes: [mediaCapkeySystemConfig.initDataType],
  168. distinctiveIdentifier: mediaCapkeySystemConfig.distinctiveIdentifier,
  169. persistentState: mediaCapkeySystemConfig.persistentState,
  170. sessionTypes: mediaCapkeySystemConfig.sessionTypes,
  171. };
  172. // Only add the audio video capabilities if they have valid data.
  173. // Otherwise the query will fail.
  174. if (audioCapabilities.length) {
  175. mediaKeySystemConfig.audioCapabilities = audioCapabilities;
  176. }
  177. if (videoCapabilities.length) {
  178. mediaKeySystemConfig.videoCapabilities = videoCapabilities;
  179. }
  180. let keySystemAccess;
  181. try {
  182. keySystemAccess = await navigator.requestMediaKeySystemAccess(
  183. mediaCapkeySystemConfig.keySystem, [mediaKeySystemConfig]);
  184. } catch (e) {
  185. shaka.log.info('navigator.requestMediaKeySystemAccess failed.');
  186. }
  187. if (keySystemAccess) {
  188. res.supported = true;
  189. res.keySystemAccess = keySystemAccess;
  190. }
  191. }
  192. return res;
  193. }
  194. /**
  195. * Checks if the given media parameters of the video or audio streams are
  196. * supported by the Cast platform.
  197. * @param {!VideoConfiguration} videoConfig The 'video' field of the
  198. * MediaDecodingConfiguration.
  199. * @return {boolean} `true` when the stream can be displayed on a Cast device.
  200. * @private
  201. */
  202. static canCastDisplayType_(videoConfig) {
  203. if (!(window.cast &&
  204. cast.__platform__ && cast.__platform__.canDisplayType)) {
  205. shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
  206. 'MediaSource.isTypeSupported() for type support.');
  207. return MediaSource.isTypeSupported(videoConfig.contentType);
  208. }
  209. let displayType = videoConfig.contentType;
  210. if (videoConfig.width && videoConfig.height) {
  211. displayType +=
  212. `; width=${videoConfig.width}; height=${videoConfig.height}`;
  213. }
  214. if (videoConfig.framerate) {
  215. displayType += `; framerate=${videoConfig.framerate}`;
  216. }
  217. if (videoConfig.transferFunction === 'pq') {
  218. // A "PQ" transfer function indicates this is an HDR-capable stream;
  219. // "smpte2084" is the published standard. We need to inform the platform
  220. // this query is specifically for HDR.
  221. displayType += '; eotf=smpte2084';
  222. }
  223. return cast.__platform__.canDisplayType(displayType);
  224. }
  225. };
  226. /**
  227. * A copy of the MediaCapabilities instance, to prevent Safari from
  228. * garbage-collecting the polyfilled method on it. We make it public and export
  229. * it to ensure that it is not stripped out by the compiler.
  230. *
  231. * @type {MediaCapabilities}
  232. * @export
  233. */
  234. shaka.polyfill.MediaCapabilities.originalMcap = null;
  235. // Install at a lower priority than MediaSource polyfill, so that we have
  236. // MediaSource available first.
  237. shaka.polyfill.register(shaka.polyfill.MediaCapabilities.install, -1);