VideoStream Remoto não funciona com o WebRTC
EDIT: Eu escrevi um tutorial detalhado explicando como construir uma simples aplicação Videochat incluindo um servidor de sinalização:
Tutorial: Crie seu próprio aplicativo de videochat com HTML e JavaScriptPor favor, diga-me se você achar que é útil e compreensível. Obrigado!
Eu estou tentando fazer com que o Streams funcione via WebRTC e Websocket (nodejs-server). Tanto quanto eu posso ver o handshake via SDP funciona e o Peerconnection é estabelecido. O problema é que o Vídeo Remoto não está sendo reproduzido. O src-Attribute recebe o Blob e o autoplay está definido, mas ele simplesmente não será reproduzido. Talvez eu esteja fazendo algo errado com os candidatos ao ICE (eles são usados para streaming de mídia, certo?). Existe alguma maneira de verificar se o PeerConnection está configurado corretamente?
EDIT: Talvez eu deva explicar como funciona o código
No carregamento do site, uma conexão com o servidor websocket é estabelecida, um PeerConnection usando o servidor STUN do googles é criado e o Vídeo e os Fluxos de Áudio são coletados e adicionados ao PeerConnection.
Quando um usuário clica no botão "create offer", uma mensagem contendo seu Session-Description (SDP) é enviada para o servidor (client func sendOffer ()), que o transmite para o outro usuário.
O outro usuário recebe a mensagem e salva o SDP que ele recebeu
Se o usuário clicar em "aceitar oferta", o SDP será adicionado ao RemoteDescription (func createAnswer ()), que enviará uma mensagem de resposta (contendo o SDP do atendedor de chamadas) para o usuário da oferta.
No lado do usuário da oferta, o func offerAccepted () é executado, o que adiciona o SDP do outro usuário ao seu RemoteDesription.
Não tenho certeza em que ponto exatamente os responsáveis pelo icecandidate são chamados, mas acho que eles devem funcionar porque recebo os dois logs em ambos os lados.
Aqui está o meu código (isso é apenas para testes, por isso, mesmo que haja uma função chamada broadcast, significa que apenas dois usuários podem estar no mesmo site por vez):
Marcação de 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>
Aqui está o código do servidor:
"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
};
});
E aqui o código do cliente:
$(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();
});
});
Também li que o fluxo de mídia local precisa ser coletado antes que qualquer oferta seja enviada. Isso significa que eu tenho que adicioná-lo quando o PeerConnection é criado? Ou seja algo assim:
// create Connection
var peerConnection = new webkitRTCPeerConnection(
{
"iceServers": [{ "url": "stun:stun.l.google.com:19302" }],
"mediaStream": stream // attach media stream here?
}
);
Agradecemos antecipadamente, agradeço qualquer ajuda!
EDIT2: estou um pouco mais longe agora. parece que adicionar os candidatos-ice remotos (switchI caso setIceCandidate no código do cliente) não está funcionando porque "Uma cadeia inválida ou ilegal foi especificada." o objeto json.data.candidate se parece com isto:
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"
Eu tentei criar um novo candidato como este
var remoteCandidate = new RTCIceCandidate(json.data.candidate);
peerConnection.addIceCandidate(remoteCandidate);
mas eu ainda tenho um erro de sintaxe