WebRTC iceConnectionState 一直是 'checking' 状态; (使用 coturn)

WebRTC iceConnectionState has always been 'checking' state; (use coturn)

我想做一个简单的视频网络聊天工具,使用webrtc和weosocket。
使用 coturn 作为眩晕和转向服务器。
在局域网内可以正常使用,但是在public网络上会是iceConnectionState一直在检测

下面是实现代码,基本来自https://github.com/webrtc/samples/blob/gh-pages/src/content/peerconnection/pc1/js/main.js caller.js:

'use strict';
const Key = 123123;
var socket = new WebSocket("wss://xxxx/webrtc");
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');

startButton.addEventListener('click', start);
hangupButton.addEventListener('click', hangup);

let startTime;
const iceConfiguration = {
    iceServers: [{
        urls: "stun:xxxx:3478"
    }, {
        urls: "turn:xxxx:3478",
        username: "xxxx",
        credential: "xxxx"
    }]
};

let localStream;
let pc;
const offerOptions = {
    offerToReceiveAudio: 1,
    offerToReceiveVideo: 1
};

socket.onopen = function () {
    socket.send(JSON.stringify({ event: '_x_serverkey', data: Key, isCaller: true }));
    console.log('send key');
};

socket.onmessage = async function (event) {
    var json = JSON.parse(event.data);
    if (json.event === "_ice_candidate") {
        pc.addIceCandidate(new RTCIceCandidate(json.data.candidate));
    } else {
        if (json.event === "_answer") {
            console.log('get_answer');
            pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
        }
    }
};

async function start() {
    console.log('Requesting local stream');
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
        console.log('Received local stream');
        localVideo.srcObject = stream;
        localStream = stream;
    } catch (e) {
        alert(`getUserMedia() error: ${e.name}`);
    }
    startTime = window.performance.now();
    pc = new RTCPeerConnection(iceConfiguration);
    pc.addEventListener('icecandidate', onIceCandidate);
    pc.addEventListener('track', gotRemoteStream);

    localStream.getTracks().forEach(track => pc.addTrack(track, localStream));

    try {
        console.log('pc createOffer start');
        const offer = await pc.createOffer(offerOptions);
        await onCreateOfferSuccess(offer);
    } catch (e) {
        console.log(`Failed to create session description: ${e.toString()}`);
    }
}

async function onCreateOfferSuccess(desc) {
    console.log('setLocalDescription start');
    try {
        await pc.setLocalDescription(desc);
    } catch (e) {
        console.log(e);
    }
    socket.send(JSON.stringify({
        event: "_offer",
        data: {
            sdp: pc.localDescription
        }
    }));
}

async function onIceCandidate(event) {
    if (event.candidate !== null) {
        socket.send(JSON.stringify({
            event: "_ice_candidate",
            data: {
                candidate: event.candidate
            }
        }));
    }
}

function gotRemoteStream(e) {
    if (remoteVideo.srcObject !== e.streams[0]) {
        remoteVideo.srcObject = e.streams[0];
        console.log('received remote stream');
    }
}

server.js:

'use strict';
const Key = 123123;
var socket = new WebSocket("wss://xxxx/webrtc");
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');

startButton.addEventListener('click', start);
hangupButton.addEventListener('click', hangup);

let startTime;
const iceConfiguration = {
    iceServers: [{
        urls: "stun:xxxx:3478"
    }, {
        urls: "turn:xxxx:3478",
        username: "xxxx",
        credential: "xxxx"
    }]
};

let localStream;
let pc;

socket.onopen = function () {
    socket.send(JSON.stringify({ event: '_x_serverkey', data: Key, isCaller: false }));
    console.log('send key');
};

socket.onmessage = async function (event) {
    var json = JSON.parse(event.data);
    if (json.event === "_ice_candidate") {
        pc.addIceCandidate(new RTCIceCandidate(json.data.candidate));
    } else {
        console.log(json.data.sdp);
        pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
        if (json.event === "_offer") {
            console.log('get offer');
            console.log(json);
            const answer = await pc.createAnswer();
            await onCreateAnswerSuccess(answer);
        }
    }
};

async function start() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
        localVideo.srcObject = stream;
        localStream = stream;
    } catch (e) {
        alert(`getUserMedia() error: ${e.name}`);
    }
    startTime = window.performance.now();
    pc = new RTCPeerConnection(iceConfiguration);
    pc.addEventListener('icecandidate', onIceCandidate);
    pc.addEventListener('track', gotRemoteStream);

    localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
}

async function onIceCandidate(event) {
    try {
        await (pc.addIceCandidate(event.candidate));
    } catch (e) {
        console.log(`failed to add ICE Candidate: ${error.toString()}`);
    }
}


function gotRemoteStream(e) {
    if (remoteVideo.srcObject !== e.streams[0]) {
        remoteVideo.srcObject = e.streams[0];
        console.log('received remote stream');
    }
}

async function onCreateAnswerSuccess(desc) {
    await pc.setLocalDescription(desc);
    console.log('setLocalDescription start');
    socket.send(JSON.stringify({
        event: "_answer",
        data: {
            sdp: pc.localDescription
        }
    }));
}

轮流配置:

listening-port=3478
tls-listening-port=5349
listening-ip=0.0.0.0
external-ip=**.**.**.**
min-port=8000
max-port=9000
fingerprint
lt-cred-mech
server-name=xxxx.com
user=xxx:xxxx
userdb=/var/lib/coturn/turndb
realm=xxxx.com
cert=xxx
pkey=xxx
no-cli

问题的原因是server.js的IceCandidate没有发送到caller.js

修改server.js

async function onIceCandidate(event) {
    if (event.candidate !== null) {
        socket.send(JSON.stringify({
            event: "_ice_candidate",
            data: {
                candidate: event.candidate
            }
        }));
    }
}