【问题标题】:How do I get information about the type of connection of a WebRTC PeerConnection?如何获取有关 WebRTC PeerConnection 连接类型的信息?
【发布时间】:2023-03-03 17:31:01
【问题描述】:

有没有办法以编程方式获取有关 WebRTC 中使用的连接类型的信息?

例如,在我的应用中,我使用本地连接以及 STUN 和 TURN。如果候选人的类型是主机或中继,我可以从 ICE 候选人中收集,并且在服务器上我可以查看是否通过 STUN(连接启动)或 TURN(连接期间的稳定流)尝试连接。

到目前为止,我无法找到一种方法来访问有关浏览器中最终使用的连接类型的信息。有候选人,浏览器停止收集,然后有一个工作连接。浏览事件,但我找不到任何信息。

我知道 Chrome 在 peerconnection 上支持 getStats(),这使我可以访问 chrome://webrtc-internals 中的大部分信息,但我也没有在其中找到这些信息。

有没有办法从 javascript 访问这些信息?

非常感谢。

【问题讨论】:

  • 这是个好问题。我非常仔细地查看了规范,但没有找到确定连接类型的方法。我想这是不可能的,但我不能确定。
  • 规范非常缺乏。可以使用 getStats() 在 Chrome 上找出它,使用 googActiveConnection === "true" 查找 googCandidatePair 结果。本地和远程地址将告诉您选择了哪个候选人,并使用它来查找候选人的类型(假设您保留所有本地和远程候选人类型)。
  • 感谢您的提示,但不幸的是我找不到从 javascript 访问 googActiveConnection 的方法。根据 2014 年 9 月 (link) 的规范,在第 2031 期 (link) 中似乎有一些工作正在进行中,所以可能正在寻求帮助
  • 看看这个库:github.com/muaz-khan/getStats

标签: javascript browser webrtc


【解决方案1】:

根据the specification(目前在 Firefox 中实现,但在 Chrome 中没有实现),您确实可以从候选对的可用统计数据中找出活跃的候选对象,它们是:

dictionary RTCIceCandidatePairStats : RTCStats {
    DOMString                     transportId;
    DOMString                     localCandidateId;
    DOMString                     remoteCandidateId;
    RTCStatsIceCandidatePairState state;
    unsigned long long            priority;
    boolean                       nominated;
    boolean                       writable;
    boolean                       readable;
    unsigned long long            bytesSent;
    unsigned long long            bytesReceived;
    double                        roundTripTime;
    double                        availableOutgoingBitrate;
    double                        availableIncomingBitrate;
};

结合个人候选人的统计数据:

dictionary RTCIceCandidateAttributes : RTCStats {
    DOMString                ipAddress;
    long                     portNumber;
    DOMString                transport;
    RTCStatsIceCandidateType candidateType;
    long                     priority;
    DOMString                addressSourceUrl;
};

使用peerConnection.getStats() 查找既已获得提名又已成功的ice 候选对:

pc.getStats(null))
.then(function(stats) {
  return Object.keys(stats).forEach(function(key) {
    if (stats[key].type == "candidatepair" &&
        stats[key].nominated && stats[key].state == "succeeded") {
      var remote = stats[stats[key].remoteCandidateId];
      console.log("Connected to: " + remote.ipAddress +":"+
                  remote.portNumber +" "+ remote.transport +
                  " "+ remote.candidateType);
    }
  });
})
.catch(function(e) { console.log(e.name); });

这可能会输出如下内容:

Connected to: 192.168.1.2:49190 udp host

您可以针对LAN range 进行测试。如果相反,它返回如下内容:

Connected to: 24.57.143.7:61102 udp relayed

那么你就会有一个 TURN 连接。

这里有一个 jsfiddle 显示了这一点(出于其他原因需要 Firefox 开发者版)。

【讨论】:

  • 如果您观察 succeeded 候选对的状态,这在 Chromium 中不起作用。它每隔几秒钟切换回in-progress。因此,以这种方式迭代 stats 对象时,您不会得到任何候选对的可能性很小。相反,您必须查找类型为transport 的统计信息。请看下面我的答案以获得更好的解释。
【解决方案2】:

jib 2015 年 3 月的回答非常有帮助,但不适用于 2019 年 3 月的 Firefox v65 或 Chrome v72(在 Windows 上)。需要两个更新:

1) “stats”值现在在两个浏览器中都具有 RTCStatsReport 类型,并且它是一个没有键的可迭代对象。因此,使用 forEach(report => {...}) 对其进行迭代,“report”将是一个对象,其键类似于 jib 为“stats”显示的键。

2) "candidatepair" 不是 report.type 的有效值,但 "candidate-pair" 是。

【讨论】:

    【解决方案3】:

    我花了很长时间才做到这一点,所以希望这对某人有所帮助。

    新方式

    您现在可以从 RTCPeerConnection 中获取选定的候选对,而无需 stats api:

    const pair = rtcConnection.sctp.transport.iceTransport.getSelectedCandidatePair();
    console.log(pair.remote.type);
    

    在撰写本文时(2020 年 10 月 2 日),这仅适用于 Chromium。 您仍然可以将 stats api 用于其他浏览器。 另请注意jib 下面的评论,这仅在您有 DataChannel 时才有效。

    对于不支持 getSelectedCandidatePair() 的浏览器

    根据https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats (在页面底部的selected 属性。

    确定所选候选对的符合规范的方法是查找传输类型的统计对象,它是一个 RTCTransportStats 对象。该对象的 selectedCandidatePairId 属性指示指定的传输是否是正在使用的传输。

    因此尝试使用stat.nominated && stats.state == "succeeded" 查找选定的对并不是正确的方法。

    相反,您可以通过查看transport 统计数据中的选定对来获取它。 Firefox 不支持这一点,但幸运的是,Firefox 的候选对中有一个非标准的 selected 属性。

    const stats = await rtcConnection.getStats();
    if(stats){
        let selectedPairId = null;
        for(const [key, stat] of stats){
            if(stat.type == "transport"){
                selectedPairId = stat.selectedCandidatePairId;
                break;
            }
        }
        let candidatePair = stats.get(selectedPairId);
        if(!candidatePair){
            for(const [key, stat] of stats){
                if(stat.type == "candidate-pair" && stat.selected){
                    candidatePair = stat;
                    break;
                }
            }
        }
    
        if(candidatePair){
            for(const [key, stat] of stats){
                if(key == candidatePair.remoteCandidateId){
                    return stat.candidateType;
                }
            }
        }
    }
    

    【讨论】:

    • 很好的答案!注意key == stat.id,所以你可以直接查找candidatePair wo/2nd for 循环:candidatePair = stats.get(stat.selectedCandidatePairId)。此外,remoteId 似乎未使用。
    • 还可能值得指出的是 sctp 将是 null 除非您已经协商了数据通道,因此如果您只做媒体,您可能必须在收发器上找到传输.
    • @jib 啊,我不知道stats.get() 我总是觉得很奇怪你必须遍历对象才能获得密钥哈哈。还要感谢关于 sctp 为空的提示。我只使用数据通道(根本没有媒体),所以这个值对我来说从来都不是空的。
    【解决方案4】:

    感谢@DavidP 和more in-depth answer,我编写了下面的代码来获取 ICE Candidates 类型。

    更新代码: 使用 conncectionStats 获取 ICE 候选人

        function getCandidateIds(stats) {
            let ids = {}
            stats.forEach(report => {
                if (report.type == "candidate-pair" && report.nominated && report.state == "succeeded") {
                    //console.log("Found IDs")
                    ids = {
                        localId: report.localCandidateId,
                        remoteId: report.remoteCandidateId
                    }
                }
            });
            return ids
        }
    
        function getCandidateInfo(stats, candidateId) {
            let info = null
            stats.forEach(report => {
                if (report.id == candidateId) {
                    console.log("Found Candidate")
                    info = report
                }
            })
            return info
        }
    
        async function conncectionStats() {
            const stats = await this.pc.getStats(null)
            const candidates = await this.getCandidateIds(stats)
            console.log("candidates: ", candidates)
            if (candidates !== {}) {
                const localCadidate = await this.getCandidateInfo(stats, candidates.localId)
                const remoteCadidate = await this.getCandidateInfo(stats, candidates.remoteId)
                if (localCadidate !== null && remoteCadidate !== null) {
                    return [localCadidate, remoteCadidate]
                }
            }
            // we did not find the candidates for whatever reeason
            return [null, null]
        }
    

    读取IP:

      let myAddress = ""
      let peerAddress = ""
      if (localCadidate.hasOwnProperty("ip")){
        myAddress = localCadidate.ip
        peerAddress = remoteCadidate.ip
      } else {
        myAddress = localCadidate.address
        peerAddress = remoteCadidate.address
      }
    

    旧版本:

    function getConnectionDetails(pc){
      pc.getStats(null)
      .then(function(stats) {
            stats.forEach(report => {
              if (report.type == "candidate-pair" 
                  && report.nominated 
                  && report.state == "succeeded")
              {
                console.log( "Local ICE:", report.localCandidateId)
                console.log( "Remote ICE:",report.remoteCandidateId)
                getCandidates(pc, report.localCandidateId, report.remoteCandidateId)
              }
          });
      })
      .catch(function(e) { console.log(e.name); });
    };
    
    function getCandidates(pc, localId, remoteId){
      //console.log("looking for candidates")
      pc.getStats(null)
      .then(function(stats) {
            stats.forEach(report => {
              if (report.id == localId) {
                  console.log("Local: Type:", report.candidateType," IP:", report.ip)
              } else if (report.id == remoteId){
                  console.log("Remote: Type:", report.candidateType," IP:", report.ip)
              }
          })
      })
      .catch(function(e) { console.log(e.name); });
    }

    根据应提取的信息,您可能不需要两个候选人。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 2015-03-05
    • 1970-01-01
    • 1970-01-01
    • 2011-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多