You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebRTC视频流传输问题:如何实现视频流发送?

WebRTC视频流传输问题修复

核心错误点

你当前代码的最大问题是误用DataChannel传输媒体流:WebRTC的DataChannel仅用于传输文本、二进制小数据,无法直接发送MediaStream对象。媒体流必须通过PeerConnection的媒体轨道机制来传输,这是WebRTC专门设计的媒体传输通道。

此外代码还有以下关键问题:

  • 未将本地媒体流的轨道添加到PeerConnection
  • 未监听远端媒体流的ontrack事件来接收视频
  • PeerConnection实例重复创建,导致连接状态混乱
  • 用setTimeout替代setLocalDescription的Promise回调,逻辑不可靠

修复方案与代码调整

1. 正确处理媒体流传输逻辑

获取到本地媒体流后,需要将流中的音视频轨道添加到PeerConnection,而非通过DataChannel发送。远端通过监听peerConnection.ontrack事件接收媒体流。

2. 修复PeerConnection的SDP处理逻辑

使用setLocalDescription的Promise回调替代setTimeout,确保SDP生成完成后再发送给信令服务器。

3. 完整修复代码

<button id='start'>1</button><br>
<button id='connect'>2</button><br>
<form onsubmit="return false;">
    <input id='iprts' type="text" placeholder="Msg" size="40">
    <input id='enter' type="submit" value="Send">
</form>
<video autoplay id="vid1" width="400" height="300" controls></video>
<video autoplay id="vid2" width="400" height="300" controls></video>
<script src="client/socket.js"></script><!--v4.5.2 -->
<script>
var socket = io();

var isStarter = false
var peerConnection = null
var dataChannel = null
var localStream = null

// 初始化媒体流
navigator.mediaDevices.getDisplayMedia({
  video: true,
  audio: true,
}).then(stream => {
  localStream = stream
  vid1.srcObject = stream
})

// 发起方逻辑
start.onclick = async function() {
  isStarter = true
  // 创建PeerConnection
  peerConnection = new RTCPeerConnection();
  
  // 添加本地媒体轨道到PeerConnection
  localStream.getTracks().forEach(track => {
    peerConnection.addTrack(track, localStream)
  })
  
  // 创建DataChannel用于文本通信
  dataChannel = peerConnection.createDataChannel('test');
  setupDataChannel(dataChannel)
  
  // 监听ICE候选
  peerConnection.onicecandidate = (event) => {
    if (event.candidate) return;
    // 候选收集完成后发送SDP
    socket.emit('toserv', peerConnection.localDescription)
  }
  
  // 监听远端媒体流
  peerConnection.ontrack = (event) => {
    vid2.srcObject = event.streams[0]
  }
  
  // 创建并设置本地Offer
  const offer = await peerConnection.createOffer();
  await peerConnection.setLocalDescription(offer);
}

// 接收方逻辑
connect.onclick = async function() {
  peerConnection = new RTCPeerConnection();
  
  // 添加本地媒体轨道到PeerConnection
  localStream.getTracks().forEach(track => {
    peerConnection.addTrack(track, localStream)
  })
  
  // 监听远端DataChannel
  peerConnection.ondatachannel = (event) => {
    dataChannel = event.channel
    setupDataChannel(dataChannel)
  }
  
  // 监听ICE候选
  peerConnection.onicecandidate = (event) => {
    if (event.candidate) return;
    socket.emit('toserv2', peerConnection.localDescription)
  }
  
  // 监听远端媒体流
  peerConnection.ontrack = (event) => {
    vid2.srcObject = event.streams[0]
  }
  
  // 设置远端Offer并创建Answer
  await peerConnection.setRemoteDescription(window.offer2);
  const answer = await peerConnection.createAnswer();
  await peerConnection.setLocalDescription(answer);
}

// 信令消息监听
socket.on('toclnt', (inf) => {
  window.offer2 = inf
})

socket.on('toclnt2', (ing) => {
  if (isStarter && peerConnection) {
    peerConnection.setRemoteDescription(ing)
  }
})

// 文本消息发送
enter.onclick = function() {
  if (dataChannel && dataChannel.readyState === 'open') {
    dataChannel.send(iprts.value)
  }
}

// 通用DataChannel配置
function setupDataChannel(channel) {
  channel.onopen = () => console.log('channel_open!');
  channel.onmessage = (event) => console.log('收到消息:', event.data);
}
</script>

关键修复说明

  • 媒体流传输:通过peerConnection.addTrack将本地轨道添加到连接,远端通过ontrack事件获取流并赋值给video元素
  • SDP处理:使用async/await处理createOffer、setLocalDescription等异步操作,替代不可靠的setTimeout
  • DataChannel职责:仅用于文本消息传输,不再处理媒体流
  • PeerConnection实例管理:避免重复创建,确保连接状态一致

内容的提问来源于stack exchange,提问作者Renvo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:45:01