javascript - 我的 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>
解决方案
因为您正在使用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);
}
推荐阅读
- azure - Azure NSG 是可能的
- django - 登录Django的多个数据库
- php - 从多个 PHP 数组创建 JSON
- java - Java中的方法和计数
- html - 为什么 Live Server 不从 Visual Studio 预览我的 CSS?
- azure-devops - Azure SQL 数据库部署任务
- ruby-on-rails - system_tests 和 test_framework 之间的区别
- qt - 即时更改 QPropertyAnimation 持续时间
- java - java:reduce vs anyMatch vs contains
- linux-kernel - 静默丢弃对 mmap 区域的写入