设置video.srcObject后WebRTC无法播放远端对等方流的问题
家庭监控系统WebRTC流播放问题
项目背景
正在开发一个简易家庭监控系统,支持以推流端或消费端身份进入应用。
技术栈
- socket.io - 用于推流端与对等方之间的信令传输
- 基础Vite应用(React)
- WebRTC - 用于在两个对等方之间创建流通道
开发仅使用localhost,客户端和服务器均在Windows的WSL终端运行,通过Windows机器访问应用。
当前表现
访问http://localhost/?stream后启动推流,等待WebSocket消息以与新对等方启动信令交互。
当消费端访问http://localhost时,WebRTC连接建立,接收来自推流端的轨道。
消费端的peerConnection.ontrack事件已触发,但媒体流似乎仅包含1条静音的视频轨道。
推流端调用peerConnection.addTrack时添加了2条轨道,分别为视频和音频轨道。
问题
消费端接收推流端的轨道/流并设置到video.srcObject属性后,无任何反应,视频和音频均无法播放。
预期表现
消费端设置video.srcObject后,应能播放推流端的视频和音频。
已尝试方案
查阅相关问题并尝试了多种建议方案,但均未解决问题:
- 将video元素的
autoPlay、playsInline和muted设为true - 设置远端
video.srcObject后通过代码调用视频播放 - 使用STUN服务器 - 但认为本地环境无需该服务
- 消费端点击按钮后异步连接对等方 - 避免无用户交互时播放视频
核心代码
信令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(视频标签Hook)
import { useRef } from "react"; export default function useVideo() { const videoRef = useRef<HTMLVideoElement>(null); const element = <video ref={videoRef} autoPlay />; return { videoRef, element }; }
内容的提问来源于stack exchange,提问作者Jesús González
相关产品推荐
相关产品推荐

