无法触发 WebRTC 跟踪事件

Posted

技术标签:

【中文标题】无法触发 WebRTC 跟踪事件【英文标题】:Can not get WebRTC track event triggered 【发布时间】:2020-05-21 04:01:57 【问题描述】:

我是 WebRTC 的初学者,并试图在两个浏览器窗口之间建立对等连接。我在本地运行的 nodejs 中实现了简单的 websocket 服务器。在成为候选人之前,一切似乎都很好。交换候选人后,远程流未启动。我搜索并运行了几个示例,但找不到我的工作。

我的 UI 有一些用于交流的输入。这是我正在做的调用:

    在第一个窗口输入用户名user1,在第一个窗口输入远程用户名user2 在第二个窗口输入用户名user2,在第二个窗口输入远程用户名user1 在每两个窗口中单击Connect to server 按钮。 (此后,信令服务器会知道它们)。 在第一个窗口中选择我的第一个摄像头设备,然后在第二个窗口中选择我的第二个摄像头设备。 (相机将在此之后启动) 在第一个窗口中点击调用

问题是:远程流显示在被调用者窗口(第二个窗口)上,但从调用者窗口(第一个窗口)的开发控制台我看不到 onTrack 函数运行。所以远程流不会显示在调用者身上。但我看到了候选日志。

所以调用者看不到被调用者,但被调用者看到了调用者。

index.html 我在两个浏览器窗口中打开这个文件两次(作为 file://...)。

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Webrtc Test</title>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
  </head>
  <body>
    <table border="2">
      <tr>
        <td>
          <span>Signaling Server addres:</span>
          <input type="text" id="serverAddress" value="localhost:3001" onload="window.serverAddress = this;" />
        </td>
        <td>
          <span>Username:</span>
          <input type="text" id="username" value="user1" onload="window.username = this;" />
        </td>
        <td>
          <span>Target username:</span>
          <input type="text" id="remoteUsername" value="user2" onload="window.remoteUsername = this;" />
        </td>
        <td>Devices</td>
      </tr>
      <tr>
        <td>
          <textarea
            name="log"
            id="log"
            cols="50"
            rows="10"
            style="width: 100%; resize: vertical;"
          ></textarea>
        </td>
        <td>
          <video
            id="selfVideo"
            autoplay
            playsinline
            muted
            onload="window.selfVideo = this;"
          ></video>
        </td>
        <td>
          <video id="remoteVideo" autoplay playsinline onload="window.remoteVideo = this;"></video deo>
        </td>
        <td rowspan="2">
          <div>
            <div>Video:<input id="videoCheck" type="checkbox" onload="window.videoCheck = this;" checked /></div>
            <select
              id="videoDevices"
              size="5"
              onload="window.videoDevices = this;"
              onchange="startSelf();"
            ></select>
          </div>
          <div>
            <div>Audio:<input id="audioCheck" type="checkbox" onload="window.audioCheck = this;" /></div>
            <select
              id="audioDevices"
              size="5"
              onload="window.audioDevices = this;"
              onchange="startSelf();"
            ></select>
          </div>
        </td>
      </tr>
      <tr>
        <td colspan="3">
          <div
            style="width: 100%; display: flex; justify-content: space-evenly;"
          >
            <button id="connect" onclick="connect();">Connect to server</button>
            <button id="call" onclick="call();">Call</button>
            <button id="hangup" onclick="hangup();">Hang up</button>
          </div>
        </td>
      </tr>
    </table>

    <!-- <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> -->
    <script src="adapter.js"></script>
    <script src="setup.js"></script>
    <script src="socket.js"></script>
    <script src="rtc.js"></script>
  </body>
</html>

setup.js 用于检测输入设备并在页面加载时创建选项。

const detectDevices = (deviceInfos) => 
  for (let i = 0; i !== deviceInfos.length; ++i) 
    const deviceInfo = deviceInfos[i];
    const element = document.createElement("option");
    element.value = deviceInfo.deviceId;
    if (deviceInfo.kind === "videoinput") 
      element.innerText =
        deviceInfo.label || `camera $videoDevices.length + 1`;
      videoDevices.add(element);
     else if (deviceInfo.kind === "audioinput") 
      element.innerText =
        deviceInfo.label || `microphone $audioDevices.length + 1`;
      audioDevices.add(element);
    
  
;

navigator.mediaDevices
  .getUserMedia( audio: true, video: true )
  .then((stream) => 
    window.localStream = stream;
    return navigator.mediaDevices.enumerateDevices();
  )
  .then(detectDevices)
  .then(() => 
    localStream.getTracks().forEach((t) => t.stop());
    delete localStream;
  )
  .catch((error) => console.log("Error detecting devices", error));

const startSelf = async () => 
  if (videoCheck.checked && !videoDevices.value) 
    videoDevices.selectedIndex = 0;
  

  if (audioCheck.checked && !audioDevices.value) 
    audioDevices.selectedIndex = 0;
  
  const vDevId = videoDevices.value;
  const aDevId = audioDevices.value;
  // const constraints = 
  //   audio:  deviceId: aDevId ?  exact: aDevId  : undefined ,
  //   video:  deviceId: vDevId ?  exact: vDevId  : undefined 
  // ;
  const constraints = ;
  if (videoCheck.checked) 
    constraints.video =  deviceId: vDevId ?  exact: vDevId  : undefined ;
  
  if (audioCheck.checked) 
    constraints.audio =  deviceId: aDevId ?  exact: aDevId  : undefined ;
  
  await navigator.mediaDevices
    .getUserMedia(constraints)
    .then((stream) => 
      window.localStream = stream;
      selfVideo.srcObject = stream;
    )
    .catch((error) => console.log("Error start self", error));
;

socket.js

let ws;

const send = (obj) => 
  const message = 
    from: username.value,
    to: remoteUsername.value
  ;

  message.data = btoa(JSON.stringify(obj));
  ws.send(JSON.stringify(message));
;

const connect = () => 
  ws = new WebSocket("ws://" + serverAddress.value);
  registerEvents();
;

const registerEvents = () => 
  ws.onopen = () => 
    console.log("websocket open");
    ws.send(JSON.stringify( from: username.value, data: "merheba" ));
  ;

  ws.onmessage = (m) => 
    // parse message
    const message = JSON.parse(m.data);
    if (message.data === "siye de merheba") 
      console.log("connected to server");
      return;
    
    const data = JSON.parse(atob(message.data));
    message.data = data;
    console.log("message: ", message);
    switch (data.type) 
      case "offer":
        onReceiveOffer(data);
        break;
      case "answer":
        onReceiveAnswer(data);
        break;
      case "candidate":
        console.log("received ice candidate", data);
        pc.addIceCandidate(new RTCIceCandidate(data.candidate));
        break;
    
  ;
;


rtc.js 用于 webrtc 调用函数

let pc;

const initSelf = async () => 
  pc = new RTCPeerConnection();
  pc.onicecandidate = onIceCandidate;
  pc.ontrack = onTrack;
  await startSelf();
  pc.addStream(localStream);
;

const call = async () => 
  if (!window.localStream || !pc) 
    await initSelf();
  
  // send offer
  pc.createOffer().then((offer) => 
    pc.setLocalDescription(offer);
    console.log("sending offer");
    send(offer);
  );
;

const onReceiveOffer = async (receivedOffer) => 
  console.log("offer receive", receivedOffer);
  if (!window.localStream || !pc) 
    initSelf();
  
  pc.setRemoteDescription(new RTCSessionDescription(receivedOffer));
  log.value = JSON.stringify(receivedOffer);
  // answer
  await pc.createAnswer().then((answer) => 
    pc.setLocalDescription(answer);
    console.log("answer created: ", answer);
    send(answer);
  );
;

const onReceiveAnswer = async (receivedAnswer) => 
  console.log("answer receive", receivedAnswer);
  pc.setRemoteDescription(new RTCSessionDescription(receivedAnswer));
  log.value = JSON.stringify(receivedAnswer);
;

const onTrack = async (event) => 
  console.log("Add track");
  remoteVideo.srcObject = event.streams[0];
;

const onIceCandidate = async (event) => 
  if (event.candidate) 
    console.log("ICE candidate");
    send(
      type: "candidate",
      candidate: event.candidate
    );
  
;

const hangup = () => 
  if (pc) 
    pc.close();
    pc = null;
  
  localStream.getTracks().forEach((t) => t.stop());
  delete localStream;
;

信令服务器wsServer.jsnode wsServer.js一起运行

const WebSocket = require("ws");

const wsserver = new WebSocket.Server( port: 3001 , () => 
  console.log("server started");
);

let clients = [];

wsserver.on("connection", (socket) => 
  socket.on("message", (message) => 
    console.log("Message: %s", message);

    let data;
    try 
      data = JSON.parse(message);
     catch (error) 
      console.log("Invalid JSON");
      data = ;
      return;
    

    if (!data.from || data.from === "") 
      console.log("unknown sender");
      return;
    

    if (!clients[data.from]) 
      console.log("client add: ", data.from);
      clients[data.from] = socket;
    

    if (data.data === "merheba") 
      socket.send(JSON.stringify( data: "siye de merheba" ));
      clients[data.from] = socket;
      return;
    

    if (data.to) 
      const target = clients[data.to];
      if (target) 
        console.log("forwarding to " + data.to);
        // console.log(target);
        target.send(message);
      
    
  );

  // socket.on("close", () => 
  //   if (socket.username) 
  //     delete clients[socket.from];
  //   
  // );
);

【问题讨论】:

【参考方案1】:

这是语法:

RTCPeerConnection.ontrack = eventHandler;

所以看看你的代码应该是这样的:

self.ontrack = onTrack;

你为 onececandidate 做的方式

【讨论】:

感谢您的回复。我做了一些改变,包括这个。但问题部分解决了。现在远程视频显示在第二个窗口上,但仍然不显示在拨打电话的第一个窗口上。我更新了问题。收到答案时可能出了什么问题? @user12043 您需要将该部分单独包含在 localVideo 中。完美的流程是..1) 建立对等连接 2) 在应答请求视频上(仅一次)从呼叫者到被呼叫者的第一个请求视频启动协商,一旦完成将再次发回答案.. 在该应答上,被呼叫者可以请求来自第二面的视频...github.com/smitkpatel16/rsvp_vidcall/blob/master/sources/js/… 参考此文件.. 这是为多方设计的【参考方案2】:

我放了一些window.localStream 并将initSelf() 的内容移动到startSelf() 中。我不知道为什么,但问题解决了。

const startSelf = async () => 
  // creating pc object first
  if (!pc) 
    pc = new RTCPeerConnection();
    pc.onicecandidate = onIceCandidate;
    pc.ontrack = onTrack;
  

  // some code ...

  await navigator.mediaDevices
    .getUserMedia(constraints)
    .then((stream) => 
      window.localStream = stream;
      pc.addStream(window.localStream); // adding the stream before showing
      selfVideo.srcObject = window.localStream;
    )
    .catch((error) => console.log("Error start self", error));
;

此处的最终内容:https://github.com/user12043/webrtc-try

【讨论】:

以上是关于无法触发 WebRTC 跟踪事件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Instruments 上跟踪 UI 事件

对讲跟踪事件自动发送自动消息

在另一个事件中跟踪一个事件

Google Analytics 无法在 iPhone 上正确跟踪 HTML5 移动应用中的事件

Flowplayer 视频进度跟踪?

Flash/Actionscript2 - 无法触发组合框“更改”事件