javascriptwebrtccoturn

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


I want to make a simple video web chat tool, using webrtc and weosocket.
Use coturn as a stun and turn server.
It can be used normally in the local area network, but it will be iceConnectionState has been checking on the public network.

The following is the implementation code, basically from 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
        }
    }));
}

coturn configure:

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

Solution

  • The cause of the problem is that the IceCandidate of server.js is not sent to caller.js

    Modify server.js

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