Remote VideoStream funktioniert nicht mit WebRTC
EDIT: Ich habe ein detailliertes Tutorial geschrieben, in dem erklärt wird, wie man eine einfache Videochat-Anwendung einschließlich eines Signalisierungsservers erstellt:
Tutorial: Erstellen Sie Ihre eigene Videochat-Anwendung mit HTML und JavaScriptSagen Sie mir bitte, ob Sie es hilfreich und verständlich finden. Vielen Dank!
Ich versuche, Streams über WebRTC und Websocket (NodeJS-Server) zum Laufen zu bringen. Soweit ich sehen kann funktioniert der Handshake via SDP und die Peerconnection ist aufgebaut. Das Problem ist - das Remote-Video wird nicht abgespielt. Das src-Attribut erhält den Blob und das Autoplay wird gesetzt, aber es wird gerade nicht abgespielt. Vielleicht mache ich etwas falsch mit den ICE-Kandidaten (sie werden für das Media-Streaming verwendet, oder?). Kann ich überprüfen, ob die PeerConnection korrekt eingerichtet ist?
EDIT: Vielleicht sollte ich erklären, wie der Code funktioniert
Beim Laden der Website wird eine Verbindung zum Websocket-Server hergestellt, eine PeerConnection mit dem googles STUN-Server erstellt und Video- und Audio-Streams gesammelt und zur PeerConnection hinzugefügt
Wenn ein Benutzer auf die Schaltfläche "Angebot erstellen" klickt, wird eine Nachricht mit seiner Sitzungsbeschreibung (SDP) an den Server gesendet (Client func sendOffer ()), der sie an den anderen Benutzer sendet
Der andere Benutzer erhält die Nachricht und speichert das SDP, das er erhalten hat
Wenn der Benutzer auf "Angebot annehmen" klickt, wird der SDP der RemoteDescription (func createAnswer ()) hinzugefügt, die dann eine Antwortnachricht (die den SDP des antwortenden Benutzers enthält) an den anbietenden Benutzer sendet
Auf der Seite des anbietenden Benutzers wird das func offerAccepted () ausgeführt, das die SDP des anderen Benutzers zu seiner RemoteDesription hinzufügt.
Ich bin mir nicht sicher, wann genau die Icecandidate-Handler aufgerufen werden, aber ich denke, sie sollten funktionieren, da ich beide Protokolle auf beiden Seiten bekomme.
Hier ist mein Code (dieser dient nur zu Testzwecken. Selbst wenn es eine Funktion namens "Broadcast" gibt, können sich nur zwei Benutzer gleichzeitig auf derselben Website befinden):
Auszeichnung von index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
#acceptOffer {
display: none;
}
</style>
</head>
<body>
<h2>Chat</h2>
<div>
<textarea class="output" name="" id="" cols="30" rows="10"></textarea>
</div>
<button id="createOffer">create Offer</button>
<button id="acceptOffer">accept Offer</button>
<h2>My Stream</h2>
<video id="myStream" autoplay src=""></video>
<h2>Remote Stream</h2>
<video id="remoteStream" autoplay src=""></video>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="websocketClient.js"></script>
</body>
</html>
Hier ist der Server-Code:
"use strict";
var webSocketsServerPort = 61122;
var webSocketServer = require('websocket').server,
http = require('http'),
clients = [];
var server = http.createServer(function(request, response) {
// Not important for us. We're writing WebSocket server, not HTTP server
});
server.listen(webSocketsServerPort, function() {
console.log((new Date()) + " Server is listening on port " + webSocketsServerPort);
});
var wsServer = new webSocketServer({
httpServer: server
});
wsServer.on('request', function(request) {
console.log((new Date()) + ' Connection from origin ' + request.origin + '.');
var connection = request.accept(null, request.origin),
index = clients.push(connection) - 1,
userName=false;
console.log((new Date()) + ' Connection accepted from '+connection.remoteAddress);
// user sent some message
connection.on('message', function(message) {
var json = JSON.parse(message.utf8Data);
console.log(json.type);
switch (json.type) {
case 'broadcast':
broadcast(json);
break;
case 'emit':
emit({type:'offer', data:json.data.data});
break;
case 'client':
respondToClient(json, clients[index]);
break;
default:
respondToClient({type:'error', data:'Sorry, i dont understand that.'}, clients[index]);
break;
}
});
connection.on('close', function(connection) {
clients.splice(index,1);
console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected.");
broadcast({type:'text', data: userName+' has left the channel.'});
});
var respondToClient = function(data, client){
client.sendUTF(JSON.stringify( data ));
};
var broadcast = function(data){
for(var i = 0; i < clients.length; i++ ) {
if(i != index ) {
clients[i].sendUTF(JSON.stringify( data ));
}
}
};
var emit = function(){
// TBD
};
});
Und hier der Client-Code:
$(function () {
"use strict";
/**
* Websocket Stuff
**/
window.WebSocket = window.WebSocket || window.MozWebSocket;
// open connection
var connection = new WebSocket('ws://url-to-node-server:61122'),
myName = false,
mySDP = false,
otherSDP = false;
connection.onopen = function () {
console.log("connection to WebSocketServer successfull");
};
connection.onerror = function (error) {
console.log("WebSocket connection error");
};
connection.onmessage = function (message) {
try {
var json = JSON.parse(message.data),
output = document.getElementsByClassName('output')[0];
switch(json.callback) {
case 'offer':
otherSDP = json.data;
document.getElementById('acceptOffer').style.display = 'block';
break;
case 'setIceCandidate':
console.log('ICE CANDITATE ADDED');
peerConnection.addIceCandidate(json.data);
break;
case 'text':
var text = output.value;
output.value = json.data+'\n'+output.value;
break;
case 'answer':
otherSDP = json.data;
offerAccepted();
break;
}
} catch (e) {
console.log('This doesn\'t look like a valid JSON or something else went wrong.');
return;
}
};
/**
* P2P Stuff
**/
navigator.getMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
// create Connection
var peerConnection = new webkitRTCPeerConnection(
{ "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] }
);
var remoteVideo = document.getElementById('remoteStream'),
myVideo = document.getElementById('myStream'),
// get local video-Stream and add to Peerconnection
stream = navigator.webkitGetUserMedia({ audio: false, video: true }, function (stream) {
myVideo.src = webkitURL.createObjectURL(stream);
console.log(stream);
peerConnection.addStream(stream);
});
// executes if other side adds stream
peerConnection.onaddstream = function(e){
console.log("stream added");
if (!e)
{
return;
}
remoteVideo.setAttribute("src",URL.createObjectURL(e.stream));
console.log(e.stream);
};
// executes if my icecandidate is received, then send it to other side
peerConnection.onicecandidate = function(candidate){
console.log('ICE CANDITATE RECEIVED');
var json = JSON.stringify( { type: 'broadcast', callback:'setIceCandidate', data:candidate});
connection.send(json);
};
// send offer via Websocket
var sendOffer = function(){
peerConnection.createOffer(function (sessionDescription) {
peerConnection.setLocalDescription(sessionDescription);
// POST-Offer-SDP-For-Other-Peer(sessionDescription.sdp, sessionDescription.type);
var json = JSON.stringify( { type: 'broadcast', callback:'offer',data:{sdp:sessionDescription.sdp,type:'offer'}});
connection.send(json);
}, null, { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } });
};
// executes if offer is received and has been accepted
var createAnswer = function(){
peerConnection.setRemoteDescription(new RTCSessionDescription(otherSDP));
peerConnection.createAnswer(function (sessionDescription) {
peerConnection.setLocalDescription(sessionDescription);
// POST-answer-SDP-back-to-Offerer(sessionDescription.sdp, sessionDescription.type);
var json = JSON.stringify( { type: 'broadcast', callback:'answer',data:{sdp:sessionDescription.sdp,type:'answer'}});
connection.send(json);
}, null, { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } });
};
// executes if other side accepted my offer
var offerAccepted = function(){
peerConnection.setRemoteDescription(new RTCSessionDescription(otherSDP));
console.log('it should work now');
};
$('#acceptOffer').on('click',function(){
createAnswer();
});
$('#createOffer').on('click',function(){
sendOffer();
});
});
Ich habe auch gelesen, dass der Local-Media-Stream gesammelt werden muss, bevor ein Angebot gesendet wird. Bedeutet das, dass ich es hinzufügen muss, wenn die PeerConnection erstellt wird? Das heißt etwas wie das:
// create Connection
var peerConnection = new webkitRTCPeerConnection(
{
"iceServers": [{ "url": "stun:stun.l.google.com:19302" }],
"mediaStream": stream // attach media stream here?
}
);
Vielen Dank im Voraus, ich freue mich über jede Hilfe!
EDIT2: Ich bin jetzt ein bisschen weiter. Es scheint, dass das Hinzufügen der Remote-Eiskandidaten (switch-case setIceCandidate im Client-Code) nicht funktioniert, weil "eine ungültige oder unzulässige Zeichenfolge angegeben wurde". Das json.data.candidate-Objekt sieht folgendermaßen aus:
candidate: "a=candidate:1663431597 2 udp 1845501695 141.84.69.86 57538 typ srflx raddr 10.150.16.92 rport 57538 generation 0
↵"
sdpMLineIndex: 1
sdpMid: "video"
Ich habe versucht, einen neuen Kandidaten wie diesen zu erstellen
var remoteCandidate = new RTCIceCandidate(json.data.candidate);
peerConnection.addIceCandidate(remoteCandidate);
Aber ich habe immer noch einen Syntaxfehler