RTCRemoteOutboundRtpStreamStats: localId 属性

可用性有限

此特性不是基线特性,因为它在一些最广泛使用的浏览器中不起作用。

RTCRemoteOutboundRtpStreamStats 字典中的 localId 属性是一个字符串,可用于标识 RTCInboundRtpStreamStats 对象,该对象的 remoteId 与此值匹配。

这两个对象共同提供了关于同一同步源 (SSRC) 的入站和出站两侧的统计信息。

一个字符串,可以与 RTCInboundRtpStreamStats 对象的 remoteId 属性值进行比较,以确定这两个对象是否代表本地对等方接收的同一组数据的两侧统计信息。

用法说明

您可以将同一 RTP 流的本地视图和远程视图视为成对,每对都包含指向另一方的引用。因此,如果 RTCStatsReport 包含一个 remote-outbound-rtp 统计信息对象(类型为 RTCRemoteOutboundRtpStreamStats),它还应该有一个对应的 inbound-rtp 对象。这两者都提供了关于从远程对等方传输到本地设备的数据包集的信息。

区别在于,remote-outbound-rtp 从远程对等方的角度描述了其发送数据的统计信息,而 inbound-rtp 则从本地对等方的角度提供了传入数据的统计信息。

示例

在此示例中,我们有一对函数:第一个函数 networkTestStart() 捕获初始报告,第二个函数 networkTestStop() 捕获第二个报告。第二个函数使用这两个报告输出来显示一些有关网络状况的信息。

networkTestStart()

此函数调用 RTCPeerConnection 方法 getStats() 来请求 RTCStatsReport 并将其存储在变量 startReport 中。

js
let startReport;

async function networkTestStart(pc) {
  if (pc) {
    startReport = await pc.getStats();
  }
}

给定一个 RTCPeerConnection 实例 pc,它会调用其 getStats() 方法来获取一个统计报告对象,并将其存储在 startReport 中,供 networkTestStop() 收集完测试结束数据后使用。

networkTestStop()

networkTestStop() 函数获取第二个报告 endReport,然后计算并输出结果。

查找配对的统计信息

类型为 remote-outbound-rtp(描述远程对等方发送数据给本地对等方的统计信息)的每个统计记录都有一个对应的类型为 inbound-rtp 的记录,该记录描述了本地对等方对在两个对等方之间移动的相同数据的看法。让我们创建一个实用函数来帮助我们查找配对统计信息对象中的键值。

下面显示的 findReportEntry() 函数会检查一个 RTCStatsReport,并返回包含指定 keyRTCStatsReport 基础统计记录——并且该键具有指定的 value。如果未找到匹配项,或者统计报告没有与 key 指示的统计类别相对应的记录。

js
function findReportEntry(report, key, value) {
  for (const stats of report.values()) {
    if (stats[key] === value) {
      return stats;
    }
  }
  return null;
}

由于 RTCStatsReport 是一个 JavaScript Map,我们可以迭代 Map 的 values() 来检查报告中的每个 RTCStats 基础统计记录,直到找到一个具有指定 valuekey 属性的记录。找到匹配项时,将返回统计对象。

如果未找到匹配项,则函数返回 null

主函数 networkTestStop()

现在我们来看看 networkTestStop() 函数本身。它以正在测试的 RTCPeerConnection 作为输入,调用 getStats() 获取包含当前统计信息的新的 RTCStatsReport,然后计算它正在查找的结果,并将这些结果通过追加适当的 HTML 到类名为 stats-box<div> 元素的内容中,适当地显示给用户。

js
async function networkTestStop(pc) {
  if (pc) {
    const statsBox = document.querySelector(".stats-box");
    const endReport = await pc.getStats();

    for (const endRemoteOutbound of endReport.values()) {
      if (endRemoteOutbound.type === "remote-outbound-rtp") {
        const startRemoteOutbound = startReport.get(endRemoteOutbound.id);

        if (startRemoteOutbound) {
          const startInboundStats = findReportEntry(
            startReport,
            "remoteId",
            startRemoteOutbound.id,
          );
          const endInboundStats = findReportEntry(
            endReport,
            "remoteId",
            endRemoteOutbound.id,
          );
          // Elapsed time in seconds
          const elapsedTime =
            (endRemoteOutbound.timestamp - startRemoteOutbound.timestamp) /
            1000;
          const packetsSent =
            endRemoteOutbound.packetsSent - startRemoteOutbound.packetsSent;
          const bytesSent =
            endRemoteOutbound.bytesSent - startRemoteOutbound.bytesSent;
          const framesDecoded =
            endInboundStats.framesDecoded - startInboundStats.framesDecoded;
          const frameRate = framesDecoded / elapsedTime;

          let timeString = "";
          if (!isNaN(elapsedTime)) {
            timeString = ` representing ${elapsedTime}s`;
          }

          let frameString = "";
          if (!isNaN(framesDecoded)) {
            frameString = `Decoded ${framesDecoded} frames for a frame rate of ${frameRate.toFixed(
              2,
            )} FPS.<br>`;
          }

          const logEntry =
            `<div class="stats-entry"><h2>Report ID: ${endRemoteOutbound.id}</h2>` +
            `Remote peer sent ${packetsSent} packets ${timeString}.<br>` +
            `${frameString}` +
            `Data size: ${bytesSent} bytes.</div>`;
          statsBox.innerHTML += logEntry;
        } else {
          statsBox.innerHTML += `<div class="stats-error">Unable to find initial statistics for ID ${endRemoteOutbound.id}.</div>`;
        }
      }

      statsBox.scrollTo(0, statsBox.scrollHeight);
    }
  }
}

以下是 networkTestStop() 函数中的操作:调用 RTCPeerConnection 方法 getStats() 获取连接的最新统计报告,并将其存储在 endReport 中。这是一个 RTCStatsReport 对象,它将字符串映射到相应的 RTCStatsReport 基础类型的对象。

现在我们可以开始处理结果,首先处理 endReport 中找到的结束统计信息。在这种情况下,我们正在寻找 typeremote-outbound-rtp 的统计记录,因此我们迭代统计报告中的条目,直到找到该类型的条目。此对象特定为 RTCRemoteOutboundRtpStreamStats 类型,它提供了关于从远程对等方视角的事物状态的详细统计信息。此统计记录存储在 endRemoteOutbound 中。

找到结束的 remote-outbound-rtp 记录后,我们使用其 id 属性来获取其 ID。有了 ID,我们就可以在起始统计记录(startReport)中查找 remote-outbound-rtp 记录,并将其存储到 startRemoteOutbound 中。

现在,我们通过查找其中 remoteId 属性的值等于 remote-outbound-rtp 记录 ID 的属性来获取与这两个 remote-outbound-rtp 记录相对应的 inbound-rtp 统计信息。我们为此使用了上一节中描述的 findReportEntry() 函数,并将找到的 inbound-rtp 记录存储在 startInboundStatsendInboundStats 中。

现在我们有了计算要显示的信息所需的所有原始统计数据,因此我们进行了计算。

  • 我们通过从 endReporttimestamp 减去 startReporttimestamp 来计算两个报告发送之间经过的时间—elapsedTime。然后除以 1000 将结果从毫秒转换为秒。
  • 我们通过减去两个报告的 packetsSent 属性值来计算此时间间隔内发送的数据包数量—packetsSent
  • 同样,此时间间隔内发送的字节数—bytesSent—是通过从结束统计对象的 bytesSent 属性中减去起始统计对象的 bytesSent 属性来计算的。
  • 此时间间隔内解码的帧数—framesDecoded—是通过从 endRecord.framesDecoded 中减去 startRecordframesDecoded 来确定的。
  • 最后,在整个时间跨度内的帧率是通过将 framesDecoded 除以 elapsedTime 来计算的。

networkTestStop() 函数的其余部分构建用于向用户显示收集和计算结果的 HTML,然后将其附加到我们用于向用户显示状态更新的 statsBox 元素。

根据示例中使用的样式,输出日志如下所示:

A screenshot of the example showing logged statistics taken from paired remote-outbound-rtp and inbound-rtp statistics records

在屏幕截图中,我们看到一个标题,后面跟着我们称之为 statsBox 的可滚动 <div>。该框包含许多日志条目,其中最后几个是可见的。每个条目代表大约一秒钟的时间(因为这是我们在调用 networkTestStart()networkTestStop() 之间等待的时间)。

规范

规范
WebRTC 统计 API 的标识符
# dom-rtcremoteoutboundrtpstreamstats-localid

浏览器兼容性