我的 webrtc 在我的 sdp 中发送 recvonly 方向
Posted
技术标签:
【中文标题】我的 webrtc 在我的 sdp 中发送 recvonly 方向【英文标题】:My webrtc sends recvonly direction in my sdp 【发布时间】:2020-11-06 07:34:20 【问题描述】:我尝试使用 webrtc 进行视频通话,但在测试时遇到了错误。我打算将此项目集成到 我的 android 应用程序的 webview 中。我使用 phone-pc 和 phone-phone 进行测试。
Case A当电脑初始化调用时一切正常。 没有问题
案例 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:s-s-rc-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>
【问题讨论】:
【参考方案1】:因为您使用的是then()
或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);
【讨论】:
感谢您的回答,我觉得它很好但是@kenji tanaka 如果您提供示例代码,我只是 javascript 的初学者,它会有所帮助 感谢再版。我使用 chrome 在我的本地网络中连接了两部手机尝试了您的代码。但我仍然有同样的问题。我的服务器代码是否正确? 并注意到,如果我使用手机联系电脑,一切正常。但是如果手机收到来自电脑的电话我有同样的问题 请@kenji tanaka 我用您在客户端的回答更新了问题。但我仍然遇到上面评论中提到的问题以上是关于我的 webrtc 在我的 sdp 中发送 recvonly 方向的主要内容,如果未能解决你的问题,请参考以下文章
iOS 上的 webRTC:无法发送 SDP 应答,RTCPeerConnection.setRemoteDescription() 失败