首页 > 解决方案 > 我的 webrtc 在我的 sdp 中发送 recvonly 方向

问题描述

我试图用 webrtc 进行视频通话,但在测试时我遇到了错误。我打算将此项目集成到我的 android 应用程序的 web 视图中。我使用 phone-pc 和 phone-phone 进行测试。

情况 A当 pc 初始化调用时一切正常。没有问题

情况 B当手机初始化对 pc 或其他手机的呼叫时,Receiver 在 SDp 中发回recvonly 信息。这是日志:

v=0\r\no=- 8723618501842184555 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=msid-semantic: WMS\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 0 8 105 13 110 113 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:dfKu\r\na=ice-pwd:XEe1Yy0kmjgMoiA0dVhymtDc\r\na=ice-options:trickle\r\na=fingerprint:sha-256 69:0D:95:A9:41:C7:C8:EF:57:0F:65:44:62:64:59:96:7C:40:6A:61:CE:62:0F:A3:E9:D7:1B:D0:F1:4C:13:BC\r\na=setup:active\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:113 telephone-event/16000\r\na=rtpmap:126 telephone-event/8000\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:dfKu\r\na=ice-pwd:XEe1Yy0kmjgMoiA0dVhymtDc\r\na=ice-options:trickle\r\na=fingerprint:sha-256 69:0D:95:A9:41:C7:C8:EF:57:0F:65:44:62:64:59:96:7C:40:6A:61:CE:62:0F:A3:E9:D7:1B:D0:F1:4C:13:BC\r\na=setup:active\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=recvonly\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 profile-id=0\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 red/90000\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:102 ulpfec/90000\r\n"

这是我的信令服务器

'use strict';
const HTTPS_PORT = 8443;

const fs = require('fs');
const https = require('https');
const WebSockets = require('ws');
const WebSocketServer = WebSockets.Server;

// Yes, TLS is required
const serverConfig = {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('cert.pem'),
    requestCert: false
};

// ----------------------------------------------------------------------------------------

// Create a server for the client html page
const handleRequest = function(request, response) {
  // Render the single client html file for any request the HTTP server receives
  console.log('request received: ' + request.url);

    switch(request.url) {
      case '/':
        response.writeHead(200, {'Content-Type': 'text/html'});
        response.end(fs.readFileSync('client/index.html'));
        break;
       
      case '/webrtc.js':
        response.writeHead(200, {'Content-Type': 'application/javascript'});
        response.end(fs.readFileSync('client/webrtc.js'));
        break;
       
      default:
        // code block
    }  
};

const httpsServer = https.createServer(serverConfig, handleRequest);
httpsServer.listen(HTTPS_PORT, '0.0.0.0');

// ----------------------------------------------------------------------------------------

// Create a server for handling websocket calls
const wss = new WebSocketServer({server: httpsServer});

//No Sql Database to Store Rooms
var rooms = {};



wss.on('connection', function(socket) {
     
   var socketId; 
   var socketRoomId;

  socket.on('message', function(message) { 
      const msg = JSON.parse(message);
      const action = msg.action;
      const userId = msg.uuid;
      const roomId = msg.roomId;
      socketId = userId;
      socketRoomId = roomId;
      console.log('************* \n\ Action : ' + action +" \n\ **************");
        switch (action) {
            case "join":
                const joined = joinRoom(socket, roomId, userId);
                if(joined){
                    console.log('************* \n\ The user joined successfully the room : ' + action +" \n\ **************");
                    msg.action = "ready";
                     
                    wss.broadcast(message);
                    console.log('************************ \n\ The Socket : ' + userId +" is READY \n\***************************");
                }else{
                    console.log('************* \n\ The Room : ' + action +" is full \n\ **************");
                    msg.action = "full";
                     
                    wss.broadcast(message);
                } 
                break;
            case "signal":  
                console.log('************* \n\ user '+userId+' Sent signlals: ' + message +" \n\ **************"); 
                 
                wss.broadcast(message);
                break;
            case "bye":  
                leave(roomId, userId);
                msg.action = "leave";
                // emit(roomId, JSON.stringify(msg));
                wss.broadcast(message);
                console.log(`************* \n\  Peer said bye on room ${roomId}. \n\ **************`);
                break;
                
            default:
                console.log(`************* \n\ No action was supplied \n\ **************`);
                break;
        }
    
  });
 
 
  socket.on("close", () => {
      if(socketId !== null) leave(socketRoomId, socketId);
      console.log(`************* \n\ Socket is closed \n\ **************`);
  });
 
});
 
//Remove the socket in the room
function leave(roomId, userId){
    // not present: do nothing
    if(!(roomId in rooms) || !(userId in rooms[roomId])) return;
    
     // if the one exiting is the last one, destroy the room
    if(Object.keys(rooms[roomId]).length === 1) delete rooms[roomId];
    // otherwise simply leave the room
    else delete rooms[roomId][userId];
}

 
function joinRoom(socket, room, userId){
    if(room in rooms){
        console.log('************* \n\ Room ' + room +" exists \n\ **************");
        var clients = Object.keys(rooms[room]).length;
        if(clients < 2){ 
           console.log('************* \n\ No body is in the room ' + room +" \n\ **************"); 
           if(!(userId in rooms[room])) rooms[room][userId] = socket;
           console.log('************* \n\ the Room ' + room +" now has "+Object.keys(rooms[room]).length+" \n\ **************");
           return true;
        }else{
            console.log('************* \n\ the room ' + room +" is full \n\ **************"); 
            return false;
        }
    }
    else{
        console.log('************* \n\ the Room ' + room +" does not exists yet \n\ **************");
        rooms[room] = {};
        rooms[room][userId] = socket;
        console.log('************* \n\ the Room ' + room +" now has "+Object.keys(rooms[room]).length+" \n\ **************");
        return true;
    }
}

//Send the message to everyone in a room
function emit(room, message){
    if(room in rooms)
    Object.entries(rooms[room]).forEach(([, sock]) => sock.send(message));
} 
//Send the message to everyone but me in a room
function emitButMe(ownerId, room, message){
    if(room in rooms)
    Object.entries(rooms[room]).forEach(([userid, sock]) => sends(ownerId, userid, sock, message));
} 
function sends(ownerId, userId, socket, message){
    if(ownerId !== userId) socket.send(message);
}


wss.broadcast = function(data) {
  this.clients.forEach(function(client) {
    if(client.readyState === WebSockets.OPEN) {
      client.send(data);
    }
  });
};
console.log('Server running. Visit https://localhost:' + HTTPS_PORT + ' in Firefox/Chrome.\n\n\
Some important notes:\n\
  * Note the HTTPS; there is no HTTP -> HTTPS redirect.\n\
  * You\'ll also need to accept the invalid TLS certificate.\n'
);

在客户端,我实现了以下 javascript 以便与服务器和对等方进行通信。

let localVideo;
let localStream;
let remoteVideo;
let peerConnection;
let uuid;
let serverConnection;

let peerConnectionConfig = {
    'iceServers': [
        { 'urls': 'stun:stun.stunprotocol.org:3478' },
        { 'urls': 'stun:stun.l.google.com:19302' },
    ]
};

window.onload = pageReady;
btnStart.onclick = _ => start(true);

async function pageReady() {
    uuid = (new MediaStream()).id;

    localVideo = document.getElementById('localVideo');
    remoteVideo = document.getElementById('remoteVideo');

    serverConnection = new WebSocket('wss://' + window.location.hostname + ':8443');
    serverConnection.onmessage = gotMessageFromServer;
    serverConnection.onopen = joinRoom;
    
    const constraints = {
        video: true,
        audio: true,
    };

    if (navigator.mediaDevices.getUserMedia) {
        try {
            localStream = await navigator.mediaDevices.getUserMedia(constraints);
            localVideo.srcObject = localStream;
        } catch (err) {
            errorHandler(err);
        }
    } else {
        alert('Your browser does not support getUserMedia API');
    }
}


//Join a Room
function joinRoom() {
    serverConnection.send(JSON.stringify({ action: 'join', roomId: 'salon', uuid }));
}

async function start(isCaller) {
    peerConnection = new RTCPeerConnection(peerConnectionConfig);
    peerConnection.onicecandidate = gotIceCandidate;
    peerConnection.ontrack = gotRemoteStream;
    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

    if (isCaller) {
        const offer = await peerConnection.createOffer();
        await createdDescription(offer);
    }
}

async function gotMessageFromServer(message) {
    if (!peerConnection) start(false);

    const signal = JSON.parse(message);

    // Ignore messages from ourself
    if (signal.uuid == uuid) return;

    try {
        if (signal.sdp) {
            await peerConnection.setRemoteDescription(signal.sdp);
            // Only create answers in response to offers
            if (signal.sdp.type == 'offer') {
                const answer = await peerConnection.createAnswer();
                await createdDescription(answer);
            }
        } else if (signal.ice) {
            peerConnection.addIceCandidate(signal.ice);
        }
    } catch (err) {
        errorHandler(err);
    }
}

function gotIceCandidate(event) {
    if (event.candidate != null) {
        serverConnection.send(JSON.stringify({ ice: event.candidate, uuid, action: 'signal', roomId: 'salon' }));
    }
}

async function createdDescription(description) {
    console.log('got description');
    try {
        await peerConnection.setLocalDescription(description);
        serverConnection.send(JSON.stringify({ sdp: peerConnection.localDescription, uuid, action: 'signal', roomId: 'salon' }));
    } catch (err) {
        errorHandler(err);
    }
}

function gotRemoteStream(event) {
    console.log('got remote stream');
    remoteVideo.srcObject = event.streams[0];
}

function errorHandler(error) {
    console.error(error);
}

Html 页面是这样的:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script src="webrtc.js"></script>
  </head>

  <body>
    <video id="localVideo" autoplay muted style="width:40%;"></video>
    <video id="remoteVideo" autoplay style="width:40%;"></video>

    <br />

    <input type="button" id="start" onclick="start(true)" value="Start Video"></input>

    <script type="text/javascript">
        pageReady();
    </script>
  </body>
</html>

标签: javascriptandroidhtmlnode.jswebrtc

解决方案


因为您正在使用then()of Promise,这是因为在peerConnection创建远程时,它会先创建并发送答案peerConnection.addStream()。如果您不使用then()并重写为async/await,它应该可以工作。同样peerConnection.addStream()是旧规范,已从新规范中删除。您应该更改为peerConnection.addTrack().

可执行示例
https://codepen.io/gtk2k/pen/JjGmZaz?editors=1010
(在两个选项卡中打开示例页面并单击开始按钮)

let localVideo;
let localStream;
let remoteVideo;
let peerConnection;
let uuid;
let serverConnection;

let peerConnectionConfig = {
    'iceServers': [
        { 'urls': 'stun:stun.stunprotocol.org:3478' },
        { 'urls': 'stun:stun.l.google.com:19302' },
    ]
};

window.onload = pageReady;
btnStart.onclick = _ => start(true);

async function pageReady() {
    uuid = (new MediaStream()).id;

    localVideo = document.getElementById('localVideo');
    remoteVideo = document.getElementById('remoteVideo');

    serverConnection = new WebSocket('wss://' + window.location.hostname + ':8443');
    serverConnection.onmessage = gotMessageFromServer;
    serverConnection.onopen = joinRoom;
    
    const constraints = {
        video: true,
        audio: true,
    };

    if (navigator.mediaDevices.getUserMedia) {
        try {
            localStream = await navigator.mediaDevices.getUserMedia(constraints);
            localVideo.srcObject = localStream;
        } catch (err) {
            errorHandler(err);
        }
    } else {
        alert('Your browser does not support getUserMedia API');
    }
}


//Join a Room
function joinRoom() {
    serverConnection.send(JSON.stringify({ action: 'join', roomId: 'salon', uuid }));
}

async function start(isCaller) {
    peerConnection = new RTCPeerConnection(peerConnectionConfig);
    peerConnection.onicecandidate = gotIceCandidate;
    peerConnection.ontrack = gotRemoteStream;
    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

    if (isCaller) {
        const offer = await peerConnection.createOffer();
        await createdDescription(offer);
    }
}

async function gotMessageFromServer(message) {
    if (!peerConnection) start(false);

    const signal = JSON.parse(message);

    // Ignore messages from ourself
    if (signal.uuid == uuid) return;

    try {
        if (signal.sdp) {
            await peerConnection.setRemoteDescription(signal.sdp);
            // Only create answers in response to offers
            if (signal.sdp.type == 'offer') {
                const answer = await peerConnection.createAnswer();
                await createdDescription(answer);
            }
        } else if (signal.ice) {
            peerConnection.addIceCandidate(signal.ice);
        }
    } catch (err) {
        errorHandler(err);
    }
}

function gotIceCandidate(event) {
    if (event.candidate != null) {
        serverConnection.send(JSON.stringify({ ice: event.candidate, uuid, action: 'signal', roomId: 'salon' }));
    }
}

async function createdDescription(description) {
    console.log('got description');
    try {
        await peerConnection.setLocalDescription(description);
        serverConnection.send(JSON.stringify({ sdp: peerConnection.localDescription, uuid, action: 'signal', roomId: 'salon' }));
    } catch (err) {
        errorHandler(err);
    }
}

function gotRemoteStream(event) {
    console.log('got remote stream');
    remoteVideo.srcObject = event.streams[0];
}

function errorHandler(error) {
    console.error(error);
}

推荐阅读