设置 video.srcObject 属性后,WebRTC 无法播放来自远程对等点的流

问题描述 投票:0回答:1

我目前正在创建一个简单的家庭监控系统,我可以作为流媒体或消费者进入应用程序。

技术

  • socket.io - 用于流媒体和连接的对等方之间的信号发送
  • 基本的vite应用程序-react
  • webrtc 在 2 个对等点之间创建流媒体通道

仅 localhost 用于开发目的。 我从 Windows 的 WSL 终端运行客户端和服务器。 该应用程序可从 Windows 计算机访问。

当前行为

访问 http://localhost/?stream 后,我开始流式传输并等待来自 websocket 的传入消息以开始向新的对等方发送信号。

当消费者访问 http://localhost 时,将执行 WebRTC 连接并从流媒体端接收曲目。

peerConnection.ontrack
消费者端的事件确实被触发了,但媒体流似乎只包含 1 个视频轨道,并且被静音。

在流媒体端做

peerConnection.addTrack
时,我实际上设置了2个轨道,一个用于视频,另一个用于音频。

问题

当消费者从流媒体接收到轨道/流时,它将其设置为

video.srcObject
属性,但什么也没有发生。没有视频或音频播放。

预期行为

来自流媒体的视频和音频应在接收到轨道/流并将其设置为消费者的

video.srcObject
属性后在消费者端播放。

我尝试过的

我已经浏览了 StackOverflow 上的各种相关问题,并尝试应用建议的各种修复程序,但没有一个能正常工作。

  • 将视频元素 autoPlay、playsInline 和 muted 设置为 true
  • 设置遥控器后以编程方式播放视频元素
    video.srcObject
  • 使用 STUN 服务器 - 但我相信这不是必需的,因为我使用的是本地主机并且没有连接广域网/互联网上的 2 个对等点。
  • 单击消费者端的按钮后异步连接对等点 - 不要尝试在没有用户交互的情况下播放视频。

代码

下面我将主要应用逻辑涉及到的应用文件贴出来:

包含整个应用程序的实际存储库可以在以下位置找到:https://github.com/Jesus-Gonzalez/home-watcher - 您可以使用

yarn start:client
yarn start:server
命令克隆并运行应用程序。然后,对于流媒体端,访问 http://localhost:5173/?stream 上的应用程序;对于消费者应用程序,访问 http://localhost:5173 上的应用程序。

别担心,这个存储库确实是问题的一个非常基本和最小的示例(它没有那么大)。

信令Websocket服务

import { Server } from "socket.io";

const io = new Server();

let streamer = null;
let users = new Set();

io.on("connect", (socket) => {
  if (users.has(socket.id)) {
    return;
  }

  console.log(
    `Socket ${socket.id} connected - Client IP Address: ${socket.handshake.address}`
  );

  socket.on("disconnect", (reason) => {
    console.log(`Socket: ${socket.id} disconnected - Reason: ${reason}`);
  });

  socket.on("begin-stream", () => {
    console.log("begin stream", socket.id);
    streamer = socket.id;
  });

  socket.on("request-start-stream", (data) => {
    console.log("request-start-stream");
    socket.to(streamer).emit("handle-request-start-stream", {
      to: socket.id,
      offer: data.offer,
    });
  });

  socket.on("response-start-stream", (data) => {
    console.log("response-start-stream");
    socket.to(data.to).emit("handle-response-start-stream", data.answer);
  });
});

io.listen(5432, { cors: true });

Streamer.tsx

import { useEffect } from "react";

import socket from "./socket";
import useVideo from "./useVideo";

export default function Streamer() {
  const { videoRef, element: videoElement } = useVideo();

  useEffect(() => {
    init();

    async function init() {
      if (!videoRef.current) return;

      const stream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true,
      });

      videoRef.current.srcObject = stream;

      socket.emit("begin-stream");

      socket.on("handle-request-start-stream", async ({ to, offer }) => {
        const peerConnection = new RTCPeerConnection();
        stream
          .getTracks()
          .forEach(
            (track) =>
              console.log("track", track) ||
              peerConnection.addTrack(track, stream)
          );

        await peerConnection.setRemoteDescription(
          new RTCSessionDescription(offer)
        );

        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(
          new RTCSessionDescription(answer)
        );
        socket.emit("response-start-stream", { to, answer });
      });
    }
  }, []);

  return videoElement;
}

Consumer.tsx

import { useEffect } from "react";

import socket from "./socket";
import useVideo from "./useVideo";

export default function Consumer() {
  const { videoRef, element: videoElement } = useVideo();

  useEffect(() => {
    init();

    async function init() {
      const peerConnection = new RTCPeerConnection();
      peerConnection.addEventListener("track", ({ streams: [stream] }) => {
        if (!videoRef.current) return;
        console.log('stream', stream)

        videoRef.current.srcObject = stream;
      });

      const offer = await peerConnection.createOffer({
        // workaround to receive the tracks
        // if this is not specified, I will never receive the tracks
        offerToReceiveAudio: true,
        offerToReceiveVideo: true,
      });
      await peerConnection.setLocalDescription(
        new RTCSessionDescription(offer)
      );
      socket.emit("request-start-stream", { offer });

      socket.on("handle-response-start-stream", async (answer) => {
        await peerConnection.setRemoteDescription(
          new RTCSessionDescription(answer)
        );
      });
    }
  }, []);

  return videoElement;
}

useVideo.tsx(视频标签挂钩)

import { useRef } from "react";

export default function useVideo() {
  const videoRef = useRef<HTMLVideoElement>(null);

  const element = <video ref={videoRef} autoPlay />;

  return { videoRef, element };
}
javascript webrtc video-streaming
1个回答
0
投票

终于找到bug原因了。

ICE Candidate 交换逻辑缺失,我只需添加它。

信令服务

socket.on("send-candidate", (data) => {
  socket.to(data.to).emit("handle-send-candidate", data.candidate);
});

Streamer.tsx

peerConnection.addEventListener("icecandidate", (event) => {
  if (event.candidate === null) {
    return;
  }

  socket.emit("send-candidate", {
    to,
    candidate: event.candidate,
  });
});

Consumer.tsx

socket.on("handle-send-candidate", (candidate) => {
  peerConnection.addIceCandidate(candidate);
});
© www.soinside.com 2019 - 2024. All rights reserved.