Zdalny strumień wideo nie działa z WebRTC
EDYCJA: Napisałem szczegółowy samouczek wyjaśniający, jak zbudować prostą aplikację Videochat, w tym serwer sygnalizacji:
Samouczek: Stwórz własną aplikację Videochat z HTML i JavaScriptPoinformuj mnie, jeśli uznasz to za pomocne i zrozumiałe. Dzięki!
Próbuję uzyskać strumieni do pracy za pośrednictwem WebRTC i Websocket (serwer nodejs). O ile widzę, uzgadnianie przez prace SDP i ustanowienie połączenia Peerconnection. Problem polega na tym, że zdalne wideo nie jest odtwarzane. Atrybut src pobiera Blob, a autoplay jest ustawiony, ale po prostu nie grał. Może robię coś złego z kandydatami do ICE (są one używane do przesyłania strumieniowego multimediów, prawda?). Czy jest jakiś sposób sprawdzenia, czy połączenie PeerConnection jest poprawnie skonfigurowane?
EDIT: Może powinienem wyjaśnić, jak działa kod
Przy obciążeniu strony internetowej nawiązywane jest połączenie z serwerem websocket, tworzony jest PeerConnection z użyciem serwera Google STUN, a strumienie wideo i audio są gromadzone i dodawane do PeerConnection
Gdy jeden użytkownik kliknie przycisk „Utwórz ofertę”, wiadomość zawierająca jego opis sesji (SDP) jest wysyłana na serwer (klient func sendOffer ()), który nadaje go innemu użytkownikowi
Drugi użytkownik otrzymuje wiadomość i zapisuje otrzymany SDP
Jeśli użytkownik kliknie „zaakceptuj ofertę”, SDP zostanie dodany do RemoteDescription (func createAnswer ()), który następnie wyśle wiadomość odpowiedzi (zawierającą SDP użytkownika odpowiadającego) do użytkownika oferującego
Po stronie oferującego użytkownika wykonywane jest func offerAccepted (), które dodaje SDP innego użytkownika do jego RemoteDesription.
Nie jestem pewien, w jakim momencie dokładnie wywoływane są procedury obsługi icecandidate, ale myślę, że powinny działać, ponieważ dostaję oba dzienniki po obu stronach.
Oto mój kod (jest to tylko testowanie, więc nawet jeśli istnieje funkcja nazywana transmisją, oznacza to, że tylko 2 użytkowników może być jednocześnie na tej samej stronie):
Markup of 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>
Oto kod serwera:
"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
};
});
A tutaj kod klienta:
$(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();
});
});
Czytałem również, że strumień mediów lokalnych musi zostać zebrany przed wysłaniem jakiejkolwiek oferty. Czy to znaczy, że muszę go dodać, gdy tworzone jest połączenie PeerConnection? To znaczy. coś takiego:
// create Connection
var peerConnection = new webkitRTCPeerConnection(
{
"iceServers": [{ "url": "stun:stun.l.google.com:19302" }],
"mediaStream": stream // attach media stream here?
}
);
Z góry dziękuję, doceniam każdą pomoc!
EDIT2: jestem teraz trochę dalej. wygląda na to, że dodanie zdalnych kandydatów do lodu (setIceCandidate w przypadku przełącznika w kodzie klienckim) nie działa, ponieważ „Podano niepoprawny lub niedozwolony łańcuch”. obiekt json.data.candidate wygląda tak:
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"
Próbowałem stworzyć nowego kandydata takiego jak ten
var remoteCandidate = new RTCIceCandidate(json.data.candidate);
peerConnection.addIceCandidate(remoteCandidate);
ale wciąż mam błąd składniowy