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

WebRTC项目中ICE Candidates无法生成的问题求助

WebRTC ICE候选生成失败问题修复

自建socket.io信令服务器实现WebRTC视频通话,目前offer与answer可正常创建,但接收方无法生成ICE候选,尝试添加transceiver属性后仅发起呼叫的设备能生成候选,接收方问题依旧。

原代码

const call = document.getElementById("call")
const endcall = document.getElementById("endcall")
const videosection = document.getElementById('rightvideo')
const user1 = document.getElementById('user1') // first video pannel which shows client video
const user2 = document.getElementById('user2') //second video pannel which shows remote videos
const servers = {
    iceServers:[
        {
            urls:['stun:stun1.l.google.com:19302','stun:stun2.l.google.com:19302']
        }
    ]
}

const socket = io();
let localstream;
let remotestream;
let poffer;
let peerconnection;
let recieverid = undefined;

let recieversidermoteoffer;
let recieversidelocalanswer;
let sendersideoffer;
let sendersideanswer;

// sender side
socket.on('answer',async (data)=>{
    data = JSON.parse(data);
    console.log('answer recieved  sent by ',recieverid,' ',data.ans)
    sendersideanswer = data.ans;
    await peerconnection.setLocalDescription(sendersideoffer);
    if(!peerconnection.currentRemoteDescription)
    await peerconnection.setRemoteDescription(sendersideanswer);
})

//reciever side
socket.on('offer',(data)=>{
    data = JSON.parse(data);
    videosection.style.display = 'flex';
    recieversidermoteoffer = data.offer;
    settingsteam();
    createanswer();
})

//common
socket.on('candidate',async (data)=>{
    data = JSON.parse(data);
    await peerconnection.addIceCandidate(data.icecandidate)
})
call.onclick = ()=>{
    if(recieverid)
    {
        callinit()
        videosection.style.display = 'flex';
    }
}
endcall.onclick = ()=>{
    if(localstream){
    localstream.getTracks().forEach((track)=>{
        track.stop()
    })}
    videosection.style.display = 'none';
}


//function area

//sender side
let callinit = async ()=>{
    settingsteam();
    createpeerconnection();
    // creating offer
    poffer = await peerconnection.createOffer()
    sendersideoffer = poffer;
    socket.emit('offer',JSON.stringify({reciever:recieverid,offer:poffer}))
}

//reciever side
let createanswer = async ()=>{
    createpeerconnection();
    await peerconnection.setLocalDescription(recieversidelocalanswer)
    await peerconnection.setRemoteDescription(recieversidermoteoffer);
    let answer = await peerconnection.createAnswer();
    recieversidelocalanswer = answer;
    socket.emit('answer',JSON.stringify({reciever:recieverid,ans:answer}))
}
//common
let settingsteam =async ()=>{
    localstream = await navigator.mediaDevices.getUserMedia({video:true,audio:false});
    remotestream = new MediaStream();
    user1.srcObject = localstream;
    user2.srcObject = remotestream;
}
let createpeerconnection =async ()=>{
    peerconnection = new RTCPeerConnection(servers);
     peerconnection.onicecandidate = (e)=>{
     if(e.candidate)
     {
       socket.emit('candidate',JSON.stringify({reciever:recieverid,icecandidate:e.candidate}))
     }
    }
    if(!localstream)
    {
        localstream = await navigator.mediaDevices.getUserMedia({video:true,audio:false});
        user1.srcObject = localstream
    }
    localstream.getTracks().forEach((track)=>{
        peerconnection.addTrack(track)
    })

    peerconnection.ontrack = (event)=>{
        event.streams[0].getTracks = (tracks)=>{
            remotestream.addTrack(tracks)
        }
    }
}

核心问题点

  1. 接收方SDP设置顺序完全错误
    在createanswer函数中,先执行setLocalDescription(此时recieversidelocalanswer未赋值,为undefined),再设置远程SDP、创建answer,违背WebRTC标准流程:必须先设置远程offer的SDP,再创建answer,最后设置本地answer的SDP。

  2. ontrack事件处理逻辑错误
    错误重写event.streams[0].getTracks方法,导致远程流轨道无法正确添加到本地流中。

  3. 异步执行未等待
    socket.on('offer')中调用的异步函数settingsteam未等待完成就执行createanswer,可能导致流未准备好就创建PeerConnection。


修复后的关键代码

修复接收方createanswer函数

//reciever side
let createanswer = async ()=>{
    await settingsteam(); // 等待流准备完成
    createpeerconnection();
    // 先设置远程offer的SDP
    await peerconnection.setRemoteDescription(recieversidermoteoffer);
    // 创建answer
    let answer = await peerconnection.createAnswer();
    recieversidelocalanswer = answer;
    // 再设置本地answer的SDP
    await peerconnection.setLocalDescription(recieversidelocalanswer);
    socket.emit('answer',JSON.stringify({reciever:recieverid,ans:answer}))
}

修复ontrack事件处理

peerconnection.ontrack = (event)=>{
    // 直接遍历远程轨道添加到remotestream
    event.streams[0].getTracks().forEach(track => {
        remotestream.addTrack(track);
    });
}

修复发起方answer处理逻辑

// sender side
socket.on('answer',async (data)=>{
    data = JSON.parse(data);
    console.log('answer recieved  sent by ',recieverid,' ',data.ans)
    sendersideanswer = data.ans;
    // 本地offer的SDP在createOffer后已设置,只需设置远程answer的SDP
    if(!peerconnection.currentRemoteDescription) {
        await peerconnection.setRemoteDescription(sendersideanswer);
    }
})

额外优化点

  • 确保recieverid已正确赋值,否则信令消息无法发送到目标用户
  • 在createpeerconnection中避免重复调用getUserMedia,可通过判断localstream是否存在跳过
  • 建议用try/catch包裹所有异步操作,便于排查隐藏错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:40:54