WebRTC onicecandidate 不会在 chrome for android 上触发,但适用于所有其他浏览器,包括适用于 android 的 firefox

Posted

技术标签:

【中文标题】WebRTC onicecandidate 不会在 chrome for android 上触发,但适用于所有其他浏览器,包括适用于 android 的 firefox【英文标题】:WebRTC onicecandidate is not triggered on chrome for android but works on all other browsers including firefox for android 【发布时间】:2021-12-06 11:30:55 【问题描述】:

这里是代码。

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);

【问题讨论】:

请看这里***.com/questions/58429603/… 如果您发布当前正在运行的 Firefox 和 chrome 版本会更有帮助。此外,您可能已经这样做了,但是,请尝试禁用您已安装的所有扩展。 感谢@MarceloLacerda 我测试过的手机仅用于开发,因此没有扩展。 @PRAJINPRAKASH 我已经查看了该链接,但这是一个不同的问题。他们从不设置本地描述,所以它在任何地方都不起作用。 您的 WebRTC 实现已更新,您可以参考以下链接来更新您的 WebRTC 实现。 developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/… 【参考方案1】:

找了很久才找到答案是新版chrome的bug,解决办法是为Android 10而不是11构建应用。

【讨论】:

这为我解决了。谢谢! 我们还有其他方法可以解决这个问题吗?看起来我们不能再将我们的 targetSdk 设置为 29(或更低)以进行 google play 上传。因此,如果您需要发布到 google play,此解决方案将不起作用。有任何想法吗?我试过去 GeckoView,但工作量很大:( 我没有发现任何东西,但这绝对是一个错误,谷歌已经知道了,所以他们应该在未来修复它。

以上是关于WebRTC onicecandidate 不会在 chrome for android 上触发,但适用于所有其他浏览器,包括适用于 android 的 firefox的主要内容,如果未能解决你的问题,请参考以下文章

为啥“onecandidate”不起作用?

如何在 Chrome 中为 WebRTC 调用者设置远程描述而不会出错?

使用WebRTC在远程浏览器中查看视频,本地浏览器永远不会得到ontrack事件

用于实时扩展的 WebRTC

WebRTC有前途吗?

为 webRTC 使用特定端口