使用 Media Capabilities API

Baseline 广泛可用 *

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2020 年 1 月⁩ 起,所有主流浏览器均已支持。

* 此特性的某些部分可能存在不同级别的支持。

媒体能力 API(Media Capabilities API)提供了几个关键功能,可以帮助您更好地决定如何处理媒体,以及实时确定媒体的处理效果。

这些功能包括:

  • 能够查询浏览器,以确定在给定一组特定的编码参数(可能包括编解码器、分辨率、比特率、帧率等)的情况下,浏览器编码或解码媒体的能力。借助媒体能力 API,您不仅可以确定浏览器是否支持某种格式,还可以确定它是否能高效、流畅地支持。
  • 更精细、更详细的显示器属性信息,以便在为用户设备选择最佳播放格式时做出明智的决策。例如,您可以使用该 API 来确保您不会尝试在标准动态范围(SDR)屏幕上播放高动态范围(HDR)内容。
  • 支持获取媒体播放的实时反馈,以便您的代码可以根据此信息调整流的质量或其他设置,从而管理用户感知到的媒体性能和质量。其中一项功能是能够检测设备何时切换 GPU,以便您可以根据新 GPU 的能力进行相应调整。

注意: 上述第三点中提到的显示器能力功能尚未在任何浏览器中实现。一旦可用,它们将成为该 API 的有用功能,但在此功能在浏览器中实现之前,很可能会发生很大变化。

MediaCapabilities 接口

可以使用 `navigator` 对象和 `WorkerNavigator` 对象提供的 `mediaCapabilities` 属性来访问 MediaCapabilities。换句话说,媒体能力 API 既可以在主线程中使用,也可以在 worker 中使用。

如果该对象存在,则媒体能力 API 可用。因此,您可以像这样测试 API 的存在性:

js
if ("mediaCapabilities" in navigator) {
  // mediaCapabilities is available
} else {
  // mediaCapabilities IS NOT available
}

以视频为例,要获取有关视频解码能力的信息,您需要创建一个视频解码配置,并将其作为参数传递给 MediaCapabilities.decodingInfo() 方法。该方法返回一个 Promise,该 Promise 会解析出关于媒体能力的以下信息:视频是否可以解码,以及解码是否会流畅且节能。您也可以测试音频解码以及视频和音频编码。

创建视频解码配置

MediaCapabilities.decodingInfo() 方法接受一个媒体解码配置作为参数。

在我们的示例中,我们正在测试视频配置的解码能力。该配置需要被测试媒体的类型——例如,一个普通的 `file` 或 `MediaSource` —以及一个视频配置对象,该对象包含 `contentType`、`width`、`height`、`bitrate` 和 `framerate` 等值。

  • `contentType` 必须是一个指定 有效视频 MIME 类型的字符串。
  • `width` 和 `height` 是视频的水平和垂直尺寸;它们也用于确定 纵横比
  • `bitrate` 是编码一秒钟视频所使用的比特数。
  • `framerate` 是播放视频时每秒钟播放的帧数。
js
const videoConfiguration = {
  type: "file",
  video: {
    contentType: "video/webm;codecs=vp8",
    width: 800,
    height: 600,
    bitrate: 10000,
    framerate: 15,
  },
};

如果我们查询的是音频文件的可解码性,我们将创建一个包含声道数和采样率的音频配置,并省略仅适用于视频的属性——即尺寸和帧率。

js
const audioConfiguration = {
  type: "file",
  audio: {
    contentType: "audio/ogg",
    channels: 2,
    bitrate: 132700,
    samplerate: 5200,
  },
};

如果我们测试的是编码能力,我们将创建一个略有不同的配置。在这种情况下,被测试媒体的类型是 `record`(用于录制媒体,即 MediaRecorder 对象)或 `transmission`(用于通过电子方式传输的媒体,如 RTCPeerConnection)—再加上一个音频或视频配置,如上所述。

查询浏览器关于解码能力的信息

现在我们已经创建了一个视频解码配置,我们可以将其作为参数传递给 decodingInfo() 方法,以确定是否可以解码符合此配置的视频,以及播放是否流畅且节能。

js
let promise = navigator.mediaCapabilities.decodingInfo(videoConfiguration);

`decodingInfo()` 和 `encodingInfo()` 方法都返回 Promise。一旦 Promise 状态 fulfilled(已完成),您就可以从返回的对象中访问 `supported`、`smooth` 和 `powerEfficient` 属性。

处理响应

我们可以将 Promise 返回的值输出到控制台,而不是将 Promise 分配给一个变量:

js
navigator.mediaCapabilities.decodingInfo(videoConfiguration).then((result) => {
  console.log(
    `This configuration is ${result.supported ? "" : "not "}supported,`,
  );
  console.log(`${result.smooth ? "" : "not "}smooth, and`);
  console.log(`${result.powerEfficient ? "" : "not "}power efficient.`);
});

处理错误

在我们的视频解码示例中,如果传递给 decodingInfo() 方法的媒体配置无效,将会引发 `TypeError`。发生错误可能有几个原因,包括:

  • 指定的 `type` 不是两个允许值之一:`file` 或 `media-source`。
  • 提供的 `contentType` 是:

错误可能是由于 `type` 不是两个可能值之一,`contentType` 不是有效的编解码器 MIME 类型,或者视频配置对象中缺少无效或省略的定义。

js
navigator.mediaCapabilities
  .decodingInfo(videoConfiguration)
  .then(() => console.log("It worked"))
  .catch((error) => console.error(`It failed: ${error}`));

媒体能力实时示例

CSS

css
li {
  margin: 1em;
}

HTML

html
<form>
  <p>
    Select your video configuration and find out if this browser supports the
    codec, and whether decoding will be smooth and power efficient:
  </p>
  <ul>
    <li>
      <label for="codec">Select a codec</label>
      <select id="codec">
        <option>video/webm; codecs=vp8</option>
        <option>video/webm; codecs=vp9</option>
        <option>video/mp4; codecs=avc1</option>
        <option>video/mp4; codecs=avc1.420034</option>
        <option>invalid</option>
      </select>
    </li>
    <li>
      <label for="size">Select a size</label>
      <select id="size">
        <option>7680x4320</option>
        <option>3840x2160</option>
        <option>2560x1440</option>
        <option>1920x1080</option>
        <option>1280x720</option>
        <option selected>800x600</option>
        <option>640x480</option>
        <option>320x240</option>
        <option value=" x ">none</option>
      </select>
    </li>
    <li>
      <label for="framerate">Select a framerate</label>
      <select id="framerate">
        <option>60</option>
        <option>50</option>
        <option>30</option>
        <option>24</option>
        <option selected>15</option>
      </select>
    </li>
    <li>
      <label for="bitrate">Select a bitrate</label>
      <select id="bitrate">
        <option>4000</option>
        <option>2500</option>
        <option>800</option>
      </select>
    </li>
  </ul>
  <p>
    <input type="button" value="Test this Video Configuration" id="try-it" />
  </p>
</form>

<ul id="results"></ul>

JavaScript

js
let mc = {
  videoConfiguration: {},

  tryIt() {
    mc.createConfiguration();
    mc.testIt();
  },

  createConfiguration() {
    const size = document.getElementById("size").value.split("x");
    mc.videoConfiguration = {
      type: "file",
      video: {
        contentType: document.getElementById("codec").value,
        width: size[0],
        height: size[1],
        bitrate: document.getElementById("bitrate").value,
        framerate: document.getElementById("framerate").value,
      },
    };
  },

  testIt() {
    let content = "";
    navigator.mediaCapabilities
      .decodingInfo(mc.videoConfiguration)
      .then((result) => {
        const li = document.createElement("li"),
          mcv = mc.videoConfiguration.video;
        content = `A ${mcv.width}x${mcv.height}, ${mcv.contentType} at ${
          mcv.framerate
        }fps and ${mcv.bitrate} bps video ${
          result.supported ? " IS " : "IS NOT "
        } supported,`;
        content += `${result.smooth ? " IS " : " is NOT "} smooth, and`;
        content += `${
          result.powerEfficient ? " IS " : " IS NOT "
        }power efficient.`;
        const ul = document.getElementById("results");
        li.textContent = content;
        ul.appendChild(li);
      })
      .catch((error) => {
        const li = document.createElement("li"),
          ul = document.getElementById("results");
        li.textContent = `Codec ${mc.videoConfiguration.video.contentType} threw an error: ${error}`;
        ul.appendChild(li);
      });
  },
};

document.getElementById("try-it").addEventListener("click", mc.tryIt);

实时结果

浏览器兼容性

另见