无法设置远程应答 sdp:在错误状态下调用:稳定 [英] Failed to set remote answer sdp: Called in wrong state: stable

查看:105
本文介绍了无法设置远程应答 sdp:在错误状态下调用:稳定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 socket.io 编写一个 WebRTC 应用程序.

I am trying to write a WebRTC application using socket.io.

信令服务器是用python编写的,看起来像这样.

The signalling server is written in python and looks like this.

import socketio
import uvicorn
from starlette.applications import Starlette

ROOM = 'room'


sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
star_app = Starlette(debug=True)
app = socketio.ASGIApp(sio, star_app)


@sio.event
async def connect(sid, environ):
    await sio.emit('ready', room=ROOM, skip_sid=sid)
    sio.enter_room(sid, ROOM)


@sio.event
async def data(sid, data):
    await sio.emit('data', data, room=ROOM, skip_sid=sid)


@sio.event
async def disconnect(sid):
    sio.leave_room(sid, ROOM)


if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8003)

客户端看起来像这样

<script>
    const SIGNALING_SERVER_URL = 'http://127.0.0.1:8003?session_id=1';
    // WebRTC config: you don't have to change this for the example to work
    // If you are testing on localhost, you can just use PC_CONFIG = {}
    const PC_CONFIG = {};

    // Signaling methods
    let socket = io(SIGNALING_SERVER_URL, {autoConnect: false});

    socket.on('data', (data) => {
        console.log('Data received: ', data);
        handleSignalingData(data);
    });

    socket.on('ready', () => {
        console.log('Ready');
        // Connection with signaling server is ready, and so is local stream
        createPeerConnection();
        sendOffer();
    });

    let sendData = (data) => {
        socket.emit('data', data);
    };

    // WebRTC methods
    let pc;
    let localStream;
    let remoteStreamElement = document.querySelector('#remoteStream');

    let getLocalStream = () => {
        navigator.mediaDevices.getUserMedia({audio: true, video: true})
            .then((stream) => {
                console.log('Stream found');
                localStream = stream;
                // Connect after making sure that local stream is availble
                socket.connect();
            })
            .catch(error => {
                console.error('Stream not found: ', error);
            });
    }

    let createPeerConnection = () => {
        try {
            pc = new RTCPeerConnection(PC_CONFIG);
            pc.onicecandidate = onIceCandidate;
            pc.onaddstream = onAddStream;
            pc.addStream(localStream);
            console.log('PeerConnection created');
        } catch (error) {
            console.error('PeerConnection failed: ', error);
        }
    };

    let sendOffer = () => {
        console.log('Send offer');
        pc.createOffer().then(
            setAndSendLocalDescription,
            (error) => {
                console.error('Send offer failed: ', error);
            }
        );
    };

    let sendAnswer = () => {
        console.log('Send answer');
        pc.createAnswer().then(
            setAndSendLocalDescription,
            (error) => {
                console.error('Send answer failed: ', error);
            }
        );
    };

    let setAndSendLocalDescription = (sessionDescription) => {
        pc.setLocalDescription(sessionDescription);
        console.log('Local description set');
        sendData(sessionDescription);
    };

    let onIceCandidate = (event) => {
        if (event.candidate) {
            console.log('ICE candidate');
            sendData({
                type: 'candidate',
                candidate: event.candidate
            });
        }
    };

    let onAddStream = (event) => {
        console.log('Add stream');
        remoteStreamElement.srcObject = event.stream;
    };

    let handleSignalingData = (data) => {
        // let msg = JSON.parse(data);
        switch (data.type) {
            case 'offer':
                createPeerConnection();
                pc.setRemoteDescription(new RTCSessionDescription(data));
                sendAnswer();
                break;
            case 'answer':
                pc.setRemoteDescription(new RTCSessionDescription(data));
                break;
            case 'candidate':
                pc.addIceCandidate(new RTCIceCandidate(data.candidate));
                break;
        }
    };

    // Start connection
    getLocalStream();
</script>

我也将此代码用于客户端作为 socket.io

Also i use this code for client as socket.io

https://github.com/socketio/socket.io/blob/master/client-dist/socket.io.js

当两个人联系在一起时,一切都很好.但是一旦第三个用户尝试连接到他们,流媒体就会停止并出现错误

When two people are in the connection, everything works great. But as soon as a third user tries to connect to them, the streaming stops with an error

未捕获(承诺)DOMException:无法执行RTCPeerConnection"上的setRemoteDescription":无法设置远程回答 sdp:在错误状态下调用:稳定

Uncaught (in promise) DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: stable

我对 javascript 了解不多,所以我需要你的帮助.谢谢.

I don't have much knowledge of javascript, so I need your help. Thanks.

P.S.我在所有浏览器中都看到此错误.

查看这个仓库

https://github.com/pfertyk/webrtc-working-example

查看此说明

https://pfertyk.me/2020/03/webrtc-一个工作示例/

推荐答案

我已经在上面详细回答了这个问题,为什么您会遇到这个问题.但似乎您真正要寻找的是一些关于如何修复它的示例工作代码......所以你去:

I've answered this question above in details as to why you are having this issue. But seems like what you are really looking for is some sample working code on how to fix it... so here you go:

index.html:稍微更新 HTML 页面,所以现在我们有一个 div,我们将附加传入的远程视频.

index.html: Slightly update the HTML page, so now we have a div that we will append incoming remote videos.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebRTC working example</title>
</head>
<body>
    <div id="remoteStreams"></div>
    <script src="socket.io.js"></script>
    <script src="main.js"></script>
</body>
</html>

app.py:更新了一些数据和就绪事件处理程序,以便我们正确地将套接字 ID 发送给其他对等方.

app.py: updated data and ready event handlers a bit so that we emit the socket id to other peers correctly.

import socketio
import uvicorn
from starlette.applications import Starlette

ROOM = 'room'

sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
star_app = Starlette(debug=True)
app = socketio.ASGIApp(sio, star_app)


@sio.event
async def connect(sid, environ):
    await sio.emit('ready', {'sid': sid}, room=ROOM, skip_sid=sid)
    sio.enter_room(sid, ROOM)


@sio.event
async def data(sid, data):
    peerToSend = None
    if 'sid' in data:
      peerToSend = data['sid']
    data['sid'] = sid
    await sio.emit('data', data, room=peerToSend if peerToSend else ROOM, skip_sid=sid)


@sio.event
async def disconnect(sid):
    sio.leave_room(sid, ROOM)


if __name__ == '__main__':
    uvicorn.run(app, host='localhost', port=8003)

main.js:创建这个 peers 对象以将套接字 ID 映射到 RTCPeerConnections 并更新一些函数以使用它而不是 pc 变量.

main.js: Created this peers object to map socket ids to RTCPeerConnections and updated some of the functions to use that instead of the pc variable.

const SIGNALING_SERVER_URL = 'ws://127.0.0.1:8003';
// WebRTC config: you don't have to change this for the example to work
// If you are testing on localhost, you can just use PC_CONFIG = {}
const PC_CONFIG = {};

// Signaling methods
let socket = io(SIGNALING_SERVER_URL, {autoConnect: false});

socket.on('data', (data) => {
    console.log('Data received: ', data);
    handleSignalingData(data);
});

socket.on('ready', (msg) => {
    console.log('Ready');
    // Connection with signaling server is ready, and so is local stream
    peers[msg.sid] = createPeerConnection();
    sendOffer(msg.sid);
    addPendingCandidates(msg.sid);
});

let sendData = (data) => {
    socket.emit('data', data);
};

// WebRTC methods
let peers = {}
let pendingCandidates = {}
let localStream;

let getLocalStream = () => {
    navigator.mediaDevices.getUserMedia({audio: true, video: true})
        .then((stream) => {
            console.log('Stream found');
            localStream = stream;
            // Connect after making sure thzat local stream is availble
            socket.connect();
        })
        .catch(error => {
            console.error('Stream not found: ', error);
        });
}

let createPeerConnection = () => {
    const pc = new RTCPeerConnection(PC_CONFIG);
    pc.onicecandidate = onIceCandidate;
    pc.onaddstream = onAddStream;
    pc.addStream(localStream);
    console.log('PeerConnection created');
    return pc;
};

let sendOffer = (sid) => {
    console.log('Send offer');
    peers[sid].createOffer().then(
        (sdp) => setAndSendLocalDescription(sid, sdp),
        (error) => {
            console.error('Send offer failed: ', error);
        }
    );
};

let sendAnswer = (sid) => {
    console.log('Send answer');
    peers[sid].createAnswer().then(
        (sdp) => setAndSendLocalDescription(sid, sdp),
        (error) => {
            console.error('Send answer failed: ', error);
        }
    );
};

let setAndSendLocalDescription = (sid, sessionDescription) => {
    peers[sid].setLocalDescription(sessionDescription);
    console.log('Local description set');
    sendData({sid, type: sessionDescription.type, sdp: sessionDescription.sdp});
};

let onIceCandidate = (event) => {
    if (event.candidate) {
        console.log('ICE candidate');
        sendData({
            type: 'candidate',
            candidate: event.candidate
        });
    }
};

let onAddStream = (event) => {
    console.log('Add stream');
    const newRemoteStreamElem = document.createElement('video');
    newRemoteStreamElem.autoplay = true;
    newRemoteStreamElem.srcObject = event.stream;
    document.querySelector('#remoteStreams').appendChild(newRemoteStreamElem);
};

let addPendingCandidates = (sid) => {
    if (sid in pendingCandidates) {
        pendingCandidates[sid].forEach(candidate => {
            peers[sid].addIceCandidate(new RTCIceCandidate(candidate))
        });
    }
}

let handleSignalingData = (data) => {
    // let msg = JSON.parse(data);
    console.log(data)
    const sid = data.sid;
    delete data.sid;
    switch (data.type) {
        case 'offer':
            peers[sid] = createPeerConnection();
            peers[sid].setRemoteDescription(new RTCSessionDescription(data));
            sendAnswer(sid);
            addPendingCandidates(sid);
            break;
        case 'answer':
            peers[sid].setRemoteDescription(new RTCSessionDescription(data));
            break;
        case 'candidate':
            if (sid in peers) {
                peers[sid].addIceCandidate(new RTCIceCandidate(data.candidate));
            } else {
                if (!(sid in pendingCandidates)) {
                    pendingCandidates[sid] = [];
                }
                pendingCandidates[sid].push(data.candidate)
            }
            break;
    }
};

// Start connection
getLocalStream();

我尝试尽可能少地更改您的代码,因此您应该能够复制粘贴并使其正常工作.

I tried to change your code as little as possible, so you should be able to just copy-paste and have it working.

这是我的工作代码:https://github.com/lnogueir/webrtc-socketio

如果您在运行它时遇到任何问题,请告诉我或在那里打开一个问题,我会尽力提供帮助.

If you have any issues running it, let me know or open an issue there and I'll do my best to help.

这篇关于无法设置远程应答 sdp:在错误状态下调用:稳定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆