简体   繁体   中英

WebRTC adapter seems to not work on Firefox

Hi I'm coding a WEBRTC voice chat using javascript + node. When I test my App using Chrome (version 69.0.3497.100) it works fine, but when I try to use firefox (version 62.0.3) it gives me 2 errors:

1 - InvalidStateError: Cannot set local offer or answer in state have-local-offer DOMException: "Cannot set local offer or answer in state have-local-offer".

2 - ICE failed, add a STUN server and see about:webrtc for more details.

Here's the code:

HTML:

<!DOCTYPE html>
<html>

<head><meta charset="utf-8" http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Realtime communication with WebRTC</title>
    <link rel="stylesheet" href="/css/main.css" />
</head>
<body>
    <h1>Realtime communication with WebRTC</h1>
    <div id="videoCanvas">
        <video id="camera" autoplay playsinline></video>
        <video id="remoteVideo" autoplay playsinline></video>
        <canvas id="photo"></canvas>
    </div>

    <div id="Buttons2">
        <button id="help">Help</button>
    </div>
    <div id="buttons">
        <button id="snap">Snap</button><span> then </span><button id="send">Send</button><br />
        <button id="join">Join</button>
        <span> or </span>
        <button id="snapAndSend">Snap &amp; Send</button>
    </div>
    <div id="incoming">
        <h2>Incoming photos</h2>
        <div id="trail"></div>
    </div>
    <script src=" /socket.io/socket.io.js"></script>
    <script src="http://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

JAVASCRIPT:

'use strict';

/****************************************************************************
* Initial setup
****************************************************************************/

// var configuration = {
//   'iceServers': [{
//     'urls': 'stun:stun.l.google.com:19302'
//   }]
// };

var configuration = null;
var localStream;                                                //Stream local camera
var remoteStream;                                               //Stream remote camera
var helpBtn = document.getElementById('help');
var camera = document.querySelector('#camera');
var remoteVideo = document.querySelector('#remoteVideo');
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');
var trail = document.getElementById('trail');                   //Incoming image holder
var snapBtn = document.getElementById('snap');
var sendBtn = document.getElementById('send');
var snapAndSendBtn = document.getElementById('snapAndSend');
var joinBtn = document.getElementById('join');

var photoContextW;
var photoContextH;

// Attach event handlers
snapBtn.addEventListener('click', snapPhoto);
sendBtn.addEventListener('click', sendPhoto);
snapAndSendBtn.addEventListener('click', snapAndSend);
helpBtn.addEventListener('click', createRoom);
joinBtn.addEventListener('click', joinRoom);

// Disable send buttons by default.
sendBtn.disabled = true;
snapAndSendBtn.disabled = true;

// Create a random room if not already present in the URL.
var isInitiator;                                                //creator tag

function createRoom() {
    var room = window.location.hash = randomToken();
    //grabWebCamVideo();
    socket.emit('create or join', room);
}



/****************************************************************************
* Signaling server
****************************************************************************/

// Connect to the signaling server
var socket = io.connect();
var rj;                     //RoomJoin


socket.on('ipaddr', function (ipaddr) {
    console.log('Server IP address is: ' + ipaddr);
    // updateRoomURL(ipaddr);
});
//Room created
socket.on('created', function (room, clientId) {
    console.log('Created room', room, '- my client ID is', clientId);
    isInitiator = true;
    // grabWebCamVideo();
    if (room) {
        sendMessage({
            type: 'roomId',
            id: room
        });
    }
});
//Guest joins room
socket.on('joined', function (room, clientId) {
    console.log('This peer has joined room', room, 'with client ID', clientId);
    isInitiator = false;
    createPeerConnection(isInitiator, configuration);
    //grabWebCamVideo();
});
//Room is full
socket.on('full', function (room) {
    alert('Room ' + room + ' is full. We will create a new room for you.');
    window.location.hash = '';
    window.location.reload();
});
//Room is ready to create PeerConnection
socket.on('ready', function () {
    console.log('Socket is ready');
    createPeerConnection(isInitiator, configuration);
});

socket.on('log', function (array) {
    console.log.apply(console, array);
});


socket.on('message', function (message) {
    console.log('Client received message:', message);
    signalingMessageCallback(message);
});

if (location.hostname.match(/localhost|127\.0\.0/)) {
    socket.emit('ipaddr');
}

// Leaving rooms and disconnecting from peers
socket.on('disconnect', function (reason) {
    console.log(`Disconnected: ${reason}.`);
    sendBtn.disabled = true;
    snapAndSendBtn.disabled = true;
});

socket.on('bye', function (room) {
    console.log(`Peer leaving room ${room}.`);
    sendBtn.disabled = true;
    snapAndSendBtn.disabled = true;
    // If peer did not create the room, re-enter to be creator
    if (!isInitiator) {
        window.location.reload();
    }
});

window.addEventListener('unload', function () {
    console.log(`Unloading window. Notifying peers in ${room}.`);
    socket.emit('bye', room);
});


/*
* Send message to signaling server
*/
function sendMessage(message) {
    console.log('Client sending message: ', message);
    socket.emit('message', message);
}

function joinRoom() {
    if (typeof rj === 'undefined') {
        alert('There are no rooms to join, please create one!');
    } else {
        socket.emit('create or join', rj);
    }
}
/**
* Updates URL on the page so that users can copy&paste it to their peers.
*/
// function updateRoomURL(ipaddr) {
//   var url;
//   if (!ipaddr) {
//     url = location.href;
//   } else {
//     url = location.protocol + '//' + ipaddr + ':2013/#' + room;
//   }
//   roomURL.innerHTML = url;
// }

/****************************************************************************
* User media (webcam)
****************************************************************************/
//User Webcam + Microphone permission
//Return MediaStream

//function grabWebCamVideo() {
console.log('Getting user media (video) ...');
navigator.mediaDevices.getUserMedia({
    audio: true,
    video: true
})

    .then(gotStream)
    .catch(function (e) {
        alert('getUserMedia() error: ' + e.name);
    });
 //}

//Set the user Stream 
function gotStream(stream) {
    console.log('getUserMedia video stream URL:', stream);
    localStream = stream;
    window.stream = localStream; // stream available to console
    camera.srcObject = localStream;
    console.log('GOT STREAM LOCALSTREAM ' + localStream);
    camera.onloadedmetadata = function () {
        photo.width = photoContextW = camera.videoWidth;
        photo.height = photoContextH = camera.videoHeight;
        console.log('gotStream with width and height:', photoContextW, photoContextH);
    };
    show(snapBtn);
}

/****************************************************************************
* WebRTC peer connection and data channel
****************************************************************************/

var peerConn;
var dataChannel;

function signalingMessageCallback(message) {
    if (message.type === 'offer') {

        console.log('Got offer. Sending answer to peer.');
        peerConn.setRemoteDescription(new RTCSessionDescription(message), function () { },
            logError);
        peerConn.createAnswer(onLocalSessionCreated, logError);

    } else if (message.type === 'answer') {
        console.log('Got answer.');
        peerConn.setRemoteDescription(new RTCSessionDescription(message), function () { },
            logError);

    } else if (message.type === 'candidate') {
        peerConn.addIceCandidate(new RTCIceCandidate({
            candidate: message.candidate
        }));

    } else if (message.type === 'roomId') {
        console.log("E' stata creata una Room per la VideoChat");
        rj = message.id;
        console.log("ID ROOM " + rj);

    }
}

//Create the Peer2Peer connection
function createPeerConnection(isInitiator, config) {
    console.log('Creating Peer connection as initiator?', isInitiator, 'config:',
        config);
    peerConn = new RTCPeerConnection(config);

    //Send any ice candidates to the other peer
    peerConn.onicecandidate = handleIceCandidate;
    //Stream received = set as remote stream
    peerConn.ontrack = handleRemoteStreamAdded;
    peerConn.onremovestream = handleRemoteStreamRemoved;

    //I'm the initiator, I create the dataChannel
    if (isInitiator) {
        console.log('Creating Data Channel');
        dataChannel = peerConn.createDataChannel('photos');
        onDataChannelCreated(dataChannel);

        console.log('SETTO LOCAL STREAMMMMMMMMMMMMMMMMMM ' + localStream);
        peerConn.addStream(localStream);

        console.log('Creating an offer');
        peerConn.createOffer(onLocalSessionCreated, logError);
    } else {
        //I'm the guest, I join the dataChannel
        peerConn.addStream(localStream);
        peerConn.ondatachannel = function (event) {
            console.log('ondatachannel:', event.channel);
            dataChannel = event.channel;
            onDataChannelCreated(dataChannel);
        };
    }
}
//IceCandidate
function handleIceCandidate(event) {
    console.log('icecandidate event: ', event);
    if (event.candidate) {
        sendMessage({
            type: 'candidate',
            label: event.candidate.sdpMLineIndex,
            id: event.candidate.sdpMid,
            candidate: event.candidate.candidate
        });
    } else {
        console.log('End of candidates.');
    }
}
//Remote Stream handler
function handleRemoteStreamAdded(event) {
    console.log('Remote stream added.');
    window.stream = event.stream;
    remoteStream = event.stream;
    remoteVideo.srcObject = remoteStream;
}

function handleRemoteStreamRemoved(event) {
    console.log('Remote stream removed. Event: ', event);
}

function onLocalSessionCreated(desc) {
    console.log('local session created:', desc);
    peerConn.setLocalDescription(desc, function () {
        console.log('sending local desc:', peerConn.localDescription);
        sendMessage(peerConn.localDescription);
    }, logError);
}

function onDataChannelCreated(channel) {
    console.log('onDataChannelCreated:', channel);

    channel.onopen = function () {
        console.log('CHANNEL opened!!!');
        sendBtn.disabled = false;
        snapAndSendBtn.disabled = false;
    };

    channel.onclose = function () {
        console.log('Channel closed.');
        sendBtn.disabled = true;
        snapAndSendBtn.disabled = true;
    };

    channel.onmessage = (adapter.browserDetails.browser === 'firefox') ?
        receiveDataFirefoxFactory() : receiveDataChromeFactory();
}

function receiveDataChromeFactory() {
    var buf, count;

    return function onmessage(event) {
        if (typeof event.data === 'string') {
            buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
            count = 0;
            console.log('Expecting a total of ' + buf.byteLength + ' bytes');
            return;
        }

        var data = new Uint8ClampedArray(event.data);
        buf.set(data, count);

        count += data.byteLength;
        console.log('count: ' + count);

        if (count === buf.byteLength) {
            // we're done: all data chunks have been received
            console.log('Done. Rendering photo.');
            renderPhoto(buf);
        }
    };
}

function receiveDataFirefoxFactory() {
    var count, total, parts;

    return function onmessage(event) {
        if (typeof event.data === 'string') {
            total = parseInt(event.data);
            parts = [];
            count = 0;
            console.log('Expecting a total of ' + total + ' bytes');
            return;
        }

        parts.push(event.data);
        count += event.data.size;
        console.log('Got ' + event.data.size + ' byte(s), ' + (total - count) +
            ' to go.');

        if (count === total) {
            console.log('Assembling payload');
            var buf = new Uint8ClampedArray(total);
            var compose = function (i, pos) {
                var reader = new FileReader();
                reader.onload = function () {
                    buf.set(new Uint8ClampedArray(this.result), pos);
                    if (i + 1 === parts.length) {
                        console.log('Done. Rendering photo.');
                        renderPhoto(buf);
                    } else {
                        compose(i + 1, pos + this.result.byteLength);
                    }
                };
                reader.readAsArrayBuffer(parts[i]);
            };
            compose(0, 0);
        }
    };
}


/****************************************************************************
* Aux functions, mostly UI-related
****************************************************************************/

function snapPhoto() {
    photoContext.drawImage(camera, 0, 0, photo.width, photo.height);
    show(photo, sendBtn);
}

function sendPhoto() {
    // Split data channel message in chunks of this byte length
    var CHUNK_LEN = 64000;
    console.log('width and height ', photoContextW, photoContextH);
    var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
        len = img.data.byteLength,
        n = len / CHUNK_LEN | 0;

    console.log('Sending a total of ' + len + ' byte(s)');

    if (!dataChannel) {
        logError('Connection has not been initiated. ' +
            'Get two peers in the same room first');
        return;
    } else if (dataChannel.readyState === 'closed') {
        logError('Connection was lost. Peer closed the connection.');
        return;
    }

    dataChannel.send(len);

    //Split the photo and send in chunks of about 64KB
    for (var i = 0; i < n; i++) {
        var start = i * CHUNK_LEN,
            end = (i + 1) * CHUNK_LEN;
        console.log(start + ' - ' + (end - 1));
        dataChannel.send(img.data.subarray(start, end));
    }

    //Send the reminder, if any
    if (len % CHUNK_LEN) {
        console.log('last ' + len % CHUNK_LEN + ' byte(s)');
        dataChannel.send(img.data.subarray(n * CHUNK_LEN));
    }
}

function snapAndSend() {
    snapPhoto();
    sendPhoto();
}

function renderPhoto(data) {
    var canvas = document.createElement('canvas');
    canvas.width = photoContextW;
    canvas.height = photoContextH;
    canvas.classList.add('incomingPhoto');
    //Trail is the element holding the incoming images
    trail.insertBefore(canvas, trail.firstChild);

    var context = canvas.getContext('2d');
    var img = context.createImageData(photoContextW, photoContextH);
    img.data.set(data);
    context.putImageData(img, 0, 0);
}

function show() {
    Array.prototype.forEach.call(arguments, function (elem) {
        elem.style.display = null;
    });
}

function hide() {
    Array.prototype.forEach.call(arguments, function (elem) {
        elem.style.display = 'none';
    });
}

function randomToken() {
    return Math.floor((1 + Math.random()) * 1e16).toString(16).substring(1);
}

function logError(err) {
    if (!err) return;
    if (typeof err === 'string') {
        console.warn(err);
    } else {
        console.warn(err.toString(), err);
    }
}

Even if i got ADAPTER in the html, Firefox keeps giving me those errors and sometimes it says that on addstream is deprecated and to use ontrack. I have to use chrome and firefox so the problem MUST be the ADAPTER version imho. What do you think? Which version of firefox + adapter should I use to let it work on Firegox + Chrome?

From the information provided by you, I think the following will solve your issues.

1) I think you ran into this issue . Please read the comments on that question. Just to summarize the solution, you might be relaying the offer of the first participant to himself.

2) I think it's the same for IceCandidates. You might be sending the candidates generated by a participant to himself or the IceCandidate generation hasn't been started on the other end(because of the error 1).

Please check your server logic again to see if they're being relayed to everyone in the room or to everyone except the sender.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM