WebRTC connectionState 停留在“new” - 仅适用于 Safari,适用于 Chrome 和 FF

Posted

技术标签:

【中文标题】WebRTC connectionState 停留在“new” - 仅适用于 Safari,适用于 Chrome 和 FF【英文标题】:WebRTC connectionState stuck at "new" - Safari only, works in Chrome and FF 【发布时间】:2021-05-14 04:29:49 【问题描述】:

我无法通过 WebRTC 视频和音频远程连接到本地对等点。此问题仅在桌面和 ios 上的 Safari 中发生。在 Chrome 和 Firefox 上,该问题不存在。

我假设它与 Safari 中的事实有关,它总是询问您是否要允许音频/视频,但我不确定。这只是我可以在浏览器之间做出的唯一区别。即使选择“允许”后,问题仍然存在。

复制步骤:

在 Chrome 中,打开带有音频/视频的初始本地连接 在 Safari 中,打开远程连接并选择启用音频/视频

结果:

本地连接永远不会提供,远程 (Safari) 的 connectionState 卡住为new。请参阅以下 RTCPeerConnection 对象:

这是通过完全相同的步骤完成的完全相同的对象,但在 Chrome 或 Firefox 中:

编辑:

经过更多测试,我发现了以下内容:

以下格式:(第一次连接)>(第二次连接)

Chrome > Chrome: 作品

Chrome > 火狐:作品

Chrome > Safari:不工作

Safari > Chrome:工作

Safari > Safari:作品

在连接的两端都使用 Safari 时似乎不存在此问题...仅当 Safari 用作辅助连接时。

这是我的代码:

import h from './helpers.js';

document.getElementById('close-chat').addEventListener('click', (e) => 
    document.querySelector('#right').style.display = "none";
);

document.getElementById('open-chat').addEventListener('click', (e) => 
    document.querySelector('#right').style.display = "flex";
);

window.addEventListener('load', () => 
    sessionStorage.setItem('connected', 'false');

    const room = h.getParam('room');
    const user = h.getParam('user');

    sessionStorage.setItem('username', user);

    const username = sessionStorage.getItem('username');

    if (!room) 
        document.querySelector('#room-create').attributes.removeNamedItem('hidden');
    

    else if (!username) 
        document.querySelector('#username-set').attributes.removeNamedItem('hidden');
    

    else 
        let commElem = document.getElementsByClassName('room-comm');

        for (let i = 0; i < commElem.length; i++) 
            commElem[i].attributes.removeNamedItem('hidden');
        

        var pc = [];

        let socket = io('/stream');

        var socketId = '';
        var myStream = '';
        var screen = '';

        // Get user video by default
        getAndSetUserStream();

        socket.on('connect', () => 
            console.log('Connected');

            sessionStorage.setItem('remoteConnected', 'false');
            h.connectedChat();
            setTimeout(h.establishingChat, 3000);
            setTimeout(h.oneMinChat, 60000);
            setTimeout(h.twoMinChat, 120000);
            setTimeout(h.threeMinChat, 180000);
            setTimeout(h.fourMinChat, 240000);
            setTimeout(h.fiveMinChat, 300000);

            // Set socketId
            socketId = socket.io.engine.id;

            socket.emit('subscribe', 
                room: room,
                socketId: socketId
            );

            socket.on('new user', (data) => 
                // OG user gets log when new user joins here.
                console.log('New User');
                console.log(data);

                socket.emit('newUserStart',  to: data.socketId, sender: socketId );
                pc.push(data.socketId);
                init(true, data.socketId);
            );

            socket.on('newUserStart', (data) => 
                console.log('New User Start');
                console.log(data);

                pc.push(data.sender);
                init(false, data.sender);
            );

            socket.on('ice candidates', async (data) => 
                console.log('Ice Candidates:');
                console.log(data);

                data.candidate ? await pc[data.sender].addIceCandidate(new RTCIceCandidate(data.candidate)) : '';
            );

            socket.on('sdp', async (data) => 
                console.log('SDP:');
                console.log(data);

                if (data.description.type === 'offer') 
                    data.description ? await pc[data.sender].setRemoteDescription(new RTCSessionDescription(data.description)) : '';

                    h.getUserFullMedia().then(async (stream) => 
                        if (!document.getElementById('local').srcObject) 
                            h.setLocalStream(stream);
                        

                        // Save my stream
                        myStream = stream;

                        stream.getTracks().forEach((track) => 
                            pc[data.sender].addTrack(track, stream);
                        );

                        let answer = await pc[data.sender].createAnswer();

                        await pc[data.sender].setLocalDescription(answer);

                        socket.emit('sdp',  description: pc[data.sender].localDescription, to: data.sender, sender: socketId );
                    ).catch((e) => 
                        console.error(e);
                    );
                

                else if (data.description.type === 'answer') 
                    await pc[data.sender].setRemoteDescription(new RTCSessionDescription(data.description));
                
            );

            socket.on('chat', (data) => 
                h.addChat(data, 'remote');
            );
        );

        function getAndSetUserStream() 
            console.log('Get and set user stream.');

            h.getUserFullMedia( audio: true, video: true ).then((stream) => 
                // Save my stream
                myStream = stream;

                h.setLocalStream(stream);
            ).catch((e) => 
                console.error(`stream error: $e`);
            );
        

        function sendMsg(msg) 
            let data = 
                room: room,
                msg: msg,
                sender: username
            ;

            // Emit chat message
            socket.emit('chat', data);

            // Add localchat
            h.addChat(data, 'local');
        

        function init(createOffer, partnerName) 
            console.log('P1:');
            console.log(partnerName);

            pc[partnerName] = new RTCPeerConnection(h.getIceServer());

            console.log('P2:');
            console.log(pc[partnerName]);

            if (screen && screen.getTracks().length) 
                console.log('Screen:');
                console.log(screen);

                screen.getTracks().forEach((track) => 
                    pc[partnerName].addTrack(track, screen); // Should trigger negotiationneeded event
                );
            

            else if (myStream) 
                console.log('myStream:');
                console.log(myStream);

                myStream.getTracks().forEach((track) => 
                    pc[partnerName].addTrack(track, myStream); // Should trigger negotiationneeded event
                );
            

            else 
                h.getUserFullMedia().then((stream) => 
                    console.log('Stream:');
                    console.log(stream);

                    // Save my stream
                    myStream = stream;

                    stream.getTracks().forEach((track) => 
                        console.log('Tracks:');
                        console.log(track);

                        pc[partnerName].addTrack(track, stream); // Should trigger negotiationneeded event
                    );

                    h.setLocalStream(stream);
                ).catch((e) => 
                    console.error(`stream error: $e`);
                );
            

            // Create offer
            if (createOffer) 
                console.log('Create Offer');

                pc[partnerName].onnegotiationneeded = async () => 
                    let offer = await pc[partnerName].createOffer();

                    console.log('Offer:');
                    console.log(offer);

                    await pc[partnerName].setLocalDescription(offer);

                    console.log('Partner Details:');
                    console.log(pc[partnerName]);

                    socket.emit('sdp',  description: pc[partnerName].localDescription, to: partnerName, sender: socketId );
                ;
            

            // Send ice candidate to partnerNames
            pc[partnerName].onicecandidate = ( candidate ) => 
                console.log('Send ICE Candidates:');
                console.log(candidate);

                socket.emit('ice candidates',  candidate: candidate, to: partnerName, sender: socketId );
            ;

            // Add
            pc[partnerName].ontrack = (e) => 
                console.log('Adding partner video...');

                let str = e.streams[0];
                if (document.getElementById(`$partnerName-video`)) 
                    document.getElementById(`$partnerName-video`).srcObject = str;
                

                else 
                    // Video elem
                    let newVid = document.createElement('video');
                    newVid.id = `$partnerName-video`;
                    newVid.srcObject = str;
                    newVid.autoplay = true;
                    newVid.className = 'remote-video';
                    newVid.playsInline = true;
                    newVid.controls = true;

                    // Put div in main-section elem
                    document.getElementById('left').appendChild(newVid);

                    const video = document.getElementsByClassName('remote-video');
                
            ;

            pc[partnerName].onconnectionstatechange = (d) => 
                console.log('Connection State:');
                console.log(pc[partnerName].iceConnectionState);

                switch (pc[partnerName].iceConnectionState) 
                    case 'new':
                        console.log('New connection...!');
                        break;
                    case 'checking':
                        console.log('Checking connection...!');
                        break;
                    case 'connected':
                        console.log('Connected with dispensary!');
                        sessionStorage.setItem('remoteConnected', 'true');
                        h.establishedChat();
                        break;
                    case 'disconnected':
                        console.log('Disconnected');
                        sessionStorage.setItem('connected', 'false');
                        sessionStorage.setItem('remoteConnected', 'false');
                        h.disconnectedChat();
                        h.closeVideo(partnerName);
                        break;
                    case 'failed':
                        console.log('Failed');
                        sessionStorage.setItem('connected', 'false');
                        sessionStorage.setItem('remoteConnected', 'false');
                        h.disconnectedChat();
                        h.closeVideo(partnerName);
                        break;
                    case 'closed':
                        console.log('Closed');
                        sessionStorage.setItem('connected', 'false');
                        sessionStorage.setItem('remoteConnected', 'false');
                        h.disconnectedChat();
                        h.closeVideo(partnerName);
                        break;
                
            ;

            pc[partnerName].onsignalingstatechange = (d) => 
                switch (pc[partnerName].signalingState) 
                    case 'closed':
                        console.log("Signalling state is 'closed'");
                        h.closeVideo(partnerName);
                        break;
                
            ;
        

        // Chat textarea
        document.getElementById('chat-input').addEventListener('keypress', (e) => 
            if (e.which === 13 && (e.target.value.trim())) 
                e.preventDefault();

                sendMsg(e.target.value);

                setTimeout(() => 
                    e.target.value = '';
                , 50);
            
        );
    
);

【问题讨论】:

看起来 setRemoteDescription 由于某种原因不起作用。如果您将相同的 SDP 静态传递给 Safari,也会发生同样的情况吗?如果是:SDP 是什么样的? 实际上,它似乎只有在 Safari 处于接收端时才会发生。如果我首先打开与 Safari 的连接,然后在 Chrome 中连接它,它工作正常。但反之亦然。 我认为 Safari 有一些不寻常的安全限制,至少在其他方面是这样。可能值得用谷歌搜索 Safari WebRTC 安全或类似的东西。 【参考方案1】:

从失败的(卡在“新”状态)Safari 运行中查看控制台日志会很有帮助。

一种可能性是 Safari 没有进行完整的冰候选人收集。正如 Phillip Hancke 所指出的,看到 SDP 将有助于确定这是否正在发生。就像看到控制台日志一样。过去,Safari 存在与候选人收集相关的各种怪癖和错误。

强制 Safari 收集候选人的一种方法是明确设置 offerToReceiveAudioofferToReceiveVideo

await pc[partnerName].createOffer( offerToReceiveAudio: true, offerToReceiveVideo: true )

【讨论】:

以上是关于WebRTC connectionState 停留在“new” - 仅适用于 Safari,适用于 Chrome 和 FF的主要内容,如果未能解决你的问题,请参考以下文章

AsyncSnapshot 状态始终为 connectionState.waiting

在打开 SqlConnection 之前处理不同的 ConnectionStates

Flutter 中的 StreamBuilder 卡在 ConnectionState.waiting 中并且只显示加载标记

Firestore ConnectionState 在颤振应用程序中永远处于等待状态

介绍开源的.net通信框架NetworkComms框架 源码分析ConnectionState

DB ConnectionState = Open 但 context.SaveChanges 抛出“连接中断”异常