webrtcエンドツーエンドのオーディオおよびビデオ接続

WebRTCのフルネームは次のとおりです。WebReal - TimeCommunicationこれは、オーディオとビデオをキャプチャするWeb端末の機能を解決し、ピアツーピア(つまり、ブラウザ間)のビデオインタラクションを提供することです。実際、内訳は3つの部分で構成されているように見えます。

MediaStream:オーディオおよびビデオストリームの
キャプチャRTCPeerConnection:オーディオおよびビデオストリームの送信(通常はピアツーピアシナリオで
使用RTCDataChannel:オーディオおよびビデオバイナリデータのアップロードに使用(通常はストリームのアップロードに使用)

MediaStreamは、カメラ、マイク、または画面の画像を取得するために使用されます。

var constraints = {
    
    
        audio: true,
        video: true
    }
    navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(console.log())

これは非同期メソッドです。コールバック関数handleSuccessは、デバイスが正常に取得された後に実行されます。この関数のパラメーターは、オーディオおよびビデオストリームです。オーディオおよびビデオストリームは、コールバック関数で再生するためにビデオタグに入れられます。

function handleSuccess(stream) {
    
    
        document.getElementById("video").srcObject = stream
        }

これは、ブラウザで再生するためのオーディオおよびビデオ画像を取得するための最も基本的な方法です。

RTCPeerConnectionは、UDPオーディオおよびビデオ送信の接続を確立するために使用されます(STUNサーバーの場合)。STUNファイアウォールに障害が発生した場合は、TCP送信が必要です(転送のリレーとしてTURNサーバーが必要です)。もちろん、これらの操作は必須ではありません。自分で実行してください。webtrcのAPIが実装されています。つまりICEです。
ICE:率直に言って、ICEは、サポートできる送信方法の選択を支援します(複数ある場合があり、すべてが反対側に送信され、反対側が送信に応じて最適な送信方法を選択します)サポートされている方法。ネットワークネゴシエーション)。ただし、ICEのSTUNサーバーアドレスとTURNのサーバーアドレスを構成する必要があります。

ネットワークネゴシエーションに加えて、メディアネゴシエーションSDPもあります。メディアネゴシエーションは、サポートされているビデオコーデック機能、メディア形式、およびその他の情報について両当事者に通知することです。このプロセスのwebrtcAPIもカプセル化されています。例としてポイントツーポイント送信を取り上げます。パーティAは独自のSDP情報を取得し、独自のローカルSDPを設定してから、エンドB(このプロセスではエンドA)に送信します。イニシエーターとして機能し、オファーを送信します)、エンドBはエンドAからSPD(オファー)を受信し、エンドBは反対側のSPDを保存し、エンドBは独自のSDPを取得し、独自のローカルSPDを保存し、エンドBエンドAのオファーに応答します(つまり、エンドB自身のSDPこの応答が回答であるとエンドAに通知します。これは実際にはSDPです)。エンドAがエンドBから応答を受信すると、エンドAはエンドBのSDPを保存します。
これまでのところ、メディアの交渉は完了しています。

上記の2つのプロセスは順不同ですが、どちらもダイアログ送信の中間サーバーとしてWebSocketを必要とします。実装する方法はたくさんあります。Pythonを使用してdjango + channelsを実装してwebsocketを実装します。

フロントエンドコードは次のとおりです(ロジックコントロールを書きすぎないでください。そのため、最初にレシーバーを開いて、WebSocketがイニシエーターの情報を送信するときに部屋にレシーバーがあることを確認する必要があります)。

私のwebsocketサービスは(チャットルームで使用される)独自のメッセージを自分自身に転送するため、ここに判断が追加されます。判断が行われない場合、webrtcは自分自身とネゴシエートします。

受信終了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>接收端</title>
</head>
<body>
<video id="video" autoplay style="height: 600px;width: 800px" muted></video>
<video id="video2" autoplay style="height: 600px;width: 800px"></video>
<script>

    //生成唯一id,用于websocket判断是否是自己
    //生成唯一ID
    function guid() {
    
    
        return Number(Math.random().toString().substr(3, 3) + Date.now()).toString(36);
    }

    var id = guid();
    //首先建立websocket连接
    socket = new WebSocket("ws://这里就用自己的websocket服务器地址/");
    socket.onopen = onOpen;
    socket.onclose = onClose;

    // 连接成功
    function onOpen() {
    
    
        console.log("websocket连接成功")
    }

    function onClose() {
    
    
        console.log("websocket已经断开")
    }

    function sendCandidate(ICE) {
    
    
        socket.send(JSON.stringify({
    
    
            'message': {
    
    'ICE': ICE, 'ID': id}
        }))
        console.log("B端发送ICE到服务器")
    }

    function sendAnswer(answer) {
    
    
        socket.send(JSON.stringify({
    
    
            'message': {
    
    'SDP': answer, 'ID': id}
        }))
        console.log("B端收到A端offer(SDP),回发answer(SDP)成功")
    }

    socket.onmessage = function (e) {
    
    
        const data = JSON.parse(e.data).message;
        if (data.ID != id) {
    
    
            try {
    
    
                var ice = data.ICE
                remoteConnection.addIceCandidate(new RTCIceCandidate(ice))
                console.log("收到远端ICE候选,并添加")
            } catch (e) {
    
    
                var sdp = data.SDP
                let desc = new RTCSessionDescription(sdp)
                remoteConnection.setRemoteDescription(desc).then(function () {
    
    
                    remoteConnection.createAnswer().then((answer) => {
    
    
                        remoteConnection.setLocalDescription(answer).then(sendAnswer(answer))
                    })
                })
            }
        }
    }
    var iceServer = {
    
    
        iceServer: [
            {
    
    url: "stun:stun.l.google.com:19302"},//谷歌的公共服务器
        ]
    }
    var remoteConnection
    var constraints = {
    
    
        audio: true,
        video: true
    }
    navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(console.log())

    function handleSuccess(stream) {
    
    
        document.getElementById("video").srcObject = stream
        remoteConnection = new RTCPeerConnection(iceServer)
        remoteConnection.addStream(stream)
        remoteConnection.onaddstream = function (e) {
    
    
            console.log('获得发起方的视频流' + e.stream)
            document.getElementById("video2").srcObject = e.stream
        }
        remoteConnection.onicecandidate = function (event) {
    
    
            if (event.candidate) {
    
    
                sendCandidate(event.candidate)
            }
        }
    }
</script>
</body>
</html>

イニシエータ

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>发起端</title>
</head>
<body>
<video id="video" autoplay style="height: 600px;width: 800px" muted></video>
<video id="video2" autoplay style="height: 600px;width: 800px"></video>
<script>
    //生成唯一id,用于websocket判断是否是自己
    //生成唯一ID
    function guid() {
    
    
        return Number(Math.random().toString().substr(3, 3) + Date.now()).toString(36);
    }
    var id = guid();
    //首先建立websocket连接
    socket = new WebSocket("ws://这里就用自己的websocket服务器地址/");
    socket.onopen = onOpen;
    socket.onclose = onClose;

    // 连接成功
    function onOpen() {
    
    
        console.log("websocket连接成功")
    }

    // 关闭连接
    function onClose() {
    
    
        console.log("websocket已经断开")
    }

    //发送ICE
    function sendCandidate(ICE) {
    
    
        //获取到本地ice之后发送到websocket
        socket.send(JSON.stringify({
    
    
            'message': {
    
    'ICE': ICE, 'ID': id}
        }))
        console.log("A端发送ICE到服务器")
    }

    //发送SDP
    function sendOffer(offer) {
    
    
        //设置完本地的SDP之后发送到websocket
        socket.send(JSON.stringify({
    
    
            'message': {
    
    'SDP': offer, 'ID': id}
        }))
        console.log("A端发送offer(SDP)到服务器")
    }

    // 收到消息,判断消息类型是ICE还是SDP
    socket.onmessage = function (e) {
    
    
        const data = JSON.parse(e.data).message;
        if (data.ID != id) {
    
    
            try {
    
    
                var ice = data.ICE
                localConnection.addIceCandidate(new RTCIceCandidate(ice))
            } catch (e) {
    
    
                var sdp = data.SDP
                let desc = new RTCSessionDescription(sdp) //构建SDP对象
                localConnection.setRemoteDescription(desc).then(() => {
    
     //设置远端SDP
                    console.log('端对端连接成功')
                })
            }
        }
    }

    var iceServer = {
    
    
        iceServer: [
            {
    
    url: "stun:stun.l.google.com:19302"},//谷歌的公共服务器
        ]
    }
    var localConnection
    var constraints = {
    
    
        audio: true,
        video: true
    }
    navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(console.log())

    function handleSuccess(stream) {
    
    
        document.getElementById("video").srcObject = stream
        localConnection = new RTCPeerConnection(iceServer) //获取连接对象 ,应填写stun服务器
        localConnection.addStream(stream) //将本地流添加进连接对象
        localConnection.onaddstream = function (e) {
    
     //获得对方流触发函数
            console.log('获得应答方的视频流' + e.stream)
            document.getElementById("video2").srcObject = e.stream
        }
        localConnection.onicecandidate = function (event) {
    
      // 用来寻找合适的ICE,获得合适的ICE时触发
            if (event.candidate) {
    
    
                sendCandidate(event.candidate) // 通过websocket将ICE发送给端,自己实现的函数
                console.log(event.candidate)
            }
        }
        localConnection.createOffer().then((offer) => {
    
     //创建offer 成功之后更改与对端关联的本地描述(SDP),包括本端属性包括媒体格式
            localConnection.setLocalDescription(offer).then(sendOffer(offer)) // 之后发送SDP信息到对端,sendOffer自己实现
        })
    }
</script>
</body>
</html>

このプロセスは、写真で説明するのに非常に直感的です。
ここに画像の説明を挿入
私が使用した例では、STUNサーバーは無料です。TURNサーバーが必要な場合は、coturnを使用して実装できます(coturnにはSTUNとTURNの実装が含まれます)。

おすすめ

転載: blog.csdn.net/weixin_44784018/article/details/105729994