首页 > 解决方案 > WebRTC onicecandidate 不会在 chrome for android 上触发,但适用于所有其他浏览器,包括适用于 android 的 firefox

问题描述

这是代码。

sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;

localConnection.ondatachannel = receiveChannelCallback;
localConnection.onicecandidate = e => {
    console.log('candidates found');
    e.candidate && offerCandidates.add(e.candidate.toJSON());
};



var offerDescription = await localConnection.createOffer();
await localConnection.setLocalDescription(offerDescription);

我已经确认它适用于所有桌面浏览器和 android 上的 firefox,但从未在 chrome for android 或本机 webview 上调用 onicecandidate。

我还更新了 chrome、webview 和 android 本身,问题仍然存在。

编辑:我在另一部运行 chrome 版本 84.0.4147.89 的手机上尝试过,它运行良好。有问题的版本是 94.0.4606.85。

我将 chrome 降级到 87.0.4280.141 版本,现在它可以工作了,但遗憾的是,降级 webview 并没有帮助,这是最终用例。

我的理论是这是新版本的错误或安全问题。无论如何,这里是完整的代码,只是为了确保。

import './firebase/firebase.js';


const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
};


if (!firebase.apps.length) {
    firebase.initializeApp(firebaseConfig);
}

var connectButton = null;
var disconnectButton = null;
var sendButton = null;
var messageInputBox = null;
var receiveBox = null;

const servers = {
    iceServers: [
        {
            urls: ['stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302'],
        },
    ],
    iceCandidatePoolSize: 10,
};
const localConnection = new RTCPeerConnection(servers);
var calldoc;
var offerCandidates;
var answerCandidates;
var sendChannel = null;       // RTCDataChannel for the local (sender)
var receiveChannel = null;    // RTCDataChannel for the remote (receiver)
var answerInput = null;
var answerButton = null;
var connected = false;
var id = null;
var dataConstraint;

function startup() {
    connectButton = document.getElementById('connectButton');
    disconnectButton = document.getElementById('disconnectButton');
    sendButton = document.getElementById('sendButton');
    messageInputBox = document.getElementById('message');
    receiveBox = document.getElementById('receivebox');
    answerInput = document.getElementById('answerID');
    answerButton = document.getElementById('answerButton');
    // Set event listeners for user interface widgets
    answerButton.addEventListener('click', listenForConnection, false);
    connectButton.addEventListener('click', connectPeers, false);
    disconnectButton.addEventListener('click', disconnectPeers, false);
    sendButton.addEventListener('click', sendMessage, false);
}

function onicecandidate (e) {
    console.log('candidates found');
    e.candidate && offerCandidates.add(e.candidate.toJSON());
};

export async function connectPeers() {
    // Create the local connection and its event listeners
    
    calldoc = firebase.firestore().collection('calls').doc();
    
    // Create the data channel and establish its event listeners
    dataConstraint = null;
    sendChannel = localConnection.createDataChannel("sendChannel", dataConstraint);
    sendChannel.onopen = handleSendChannelStatusChange;
    sendChannel.onclose = handleSendChannelStatusChange;
    localConnection.ondatachannel = receiveChannelCallback;
    localConnection.onicecandidate = onicecandidate;
    
    id = calldoc.id;
    offerCandidates = calldoc.collection('offerCandidates');
    answerCandidates = calldoc.collection('answerCandidates');
   
    var offerDescription = await localConnection.createOffer();
    await localConnection.setLocalDescription(offerDescription);

    
    
    const offer = {
        sdp: offerDescription.sdp,
        type: offerDescription.type,
    };
    

    await calldoc.set({offer});

    calldoc.onSnapshot((snapshot) => {
        const data = snapshot.data();
        if (data !== null) {
            if (!localConnection.currentRemoteDescription && data.answer) {
                const answerDescription = new RTCSessionDescription(data.answer);
                localConnection.setRemoteDescription(answerDescription);

            }
        }
    });

    answerCandidates.onSnapshot(snapshot => {
        snapshot.docChanges().forEach((change) => {
            if (change.type === 'added') {
                const candidate = new RTCIceCandidate(change.doc.data());
                localConnection.addIceCandidate(candidate);
                console.log("found answer");
                connected = true;
            }
        });
    });

}

async function listenForConnection() {

    calldoc = firebase.firestore().collection('calls').doc(answerInput.value);
    answerCandidates = calldoc.collection('answerCandidates');

    localConnection.onicecandidate = event => {
        event.candidate && answerCandidates.add(event.candidate.toJSON());
    };
    // Create the data channel and establish its event listeners
    sendChannel = localConnection.createDataChannel("receiveChannel");
    sendChannel.onopen = handleSendChannelStatusChange;
    sendChannel.onclose = handleSendChannelStatusChange;

    localConnection.ondatachannel = receiveChannelCallback;
    const cdata = (await calldoc.get()).data();
    const offerDescription = cdata.offer;
    await localConnection.setRemoteDescription(new 
          RTCSessionDescription(offerDescription));

    const answerDescription = await localConnection.createAnswer();
    await localConnection.setLocalDescription(answerDescription);

    const answer = {
        type: answerDescription.type,
        sdp: answerDescription.sdp,
    };

    await calldoc.update({ answer });

    offerCandidates.onSnapshot((snapshot) => {
        snapshot.docChanges().forEach((change) => {
            console.log(change)
            if (change.type === 'added') {
                let data = change.doc.data();
                localConnection.addIceCandidate(new RTCIceCandidate(data));
            }
        });
    });

}

// Handle errors attempting to create a description;

function handleCreateDescriptionError(error) {
    console.log("Unable to create an offer: " + error.toString());
}

// Handle successful addition of the ICE candidate
// on the "local" end of the connection.

function handleLocalAddCandidateSuccess() {
    connectButton.disabled = true;
}

// Handle successful addition of the ICE candidate
// on the "remote" end of the connection.

function handleRemoteAddCandidateSuccess() {
    disconnectButton.disabled = false;
}

// Handle an error that occurs during addition of ICE candidate.

function handleAddCandidateError() {
    console.log("Oh noes! addICECandidate failed!");
}

// Handles clicks on the "Send" button by transmitting


export function sendMessage() {
    if (connected === false) {
        return
    }
    var message = messageInputBox.value;
    sendChannel.send(message);

    

    messageInputBox.value = "";
    messageInputBox.focus();
}

// Handle status changes on the local end of the data


function handleSendChannelStatusChange(event) {
    console.log('on open fired???');
    if (sendChannel) {
        var state = sendChannel.readyState;

        if (state === "open") {
            messageInputBox.disabled = false;
            messageInputBox.focus();
            sendButton.disabled = false;
            disconnectButton.disabled = false;
            connectButton.disabled = true;
        } else {
            messageInputBox.disabled = true;
            sendButton.disabled = true;
            connectButton.disabled = false;
            disconnectButton.disabled = true;
        }
    }
}

// Called when the connection opens and the data
// channel is ready to be connected to the remote.

function receiveChannelCallback(event) {
    receiveChannel = event.channel;
    receiveChannel.onmessage = handleReceiveMessage;
    receiveChannel.onopen = handleReceiveChannelStatusChange;
    receiveChannel.onclose = handleReceiveChannelStatusChange;
}

// Handle onmessage events for the receiving channel.
// These are the data messages sent by the sending channel.

function handleReceiveMessage(event) {
    var el = document.createElement("p");
    var txtNode = document.createTextNode(event.data);

    el.appendChild(txtNode);
    receiveBox.appendChild(el);
}

// Handle status changes on the receiver's channel.

function handleReceiveChannelStatusChange(event) {
    if (receiveChannel) {
        console.log("Receive channel's status has changed to " +
            receiveChannel.readyState);
    }

    // Here you would do stuff that needs to be done
    // when the channel's status changes.
}

/

function disconnectPeers() {

    // Close the RTCDataChannels if they're open.

    sendChannel.close();
    receiveChannel.close();

    // Close the RTCPeerConnections

    localConnection.close();
    remoteConnection.close();

    sendChannel = null;
    receiveChannel = null;
    localConnection = null;
    remoteConnection = null;

    // Update user interface elements

    connectButton.disabled = false;
    disconnectButton.disabled = true;
    sendButton.disabled = true;

    messageInputBox.value = "";
    messageInputBox.disabled = true;
}



window.addEventListener('load', startup, false);

标签: javascriptwebrtcgoogle-chrome-android

解决方案


经过很长时间我找到答案是新chrome中的一个错误,解决方案是为Android 10而不是11构建应用程序。


推荐阅读