Reputation: 3
I am trying to setup a web-based Live webcam streaming service where a user can broadcast live via webcam (Web based Only) i.e:
User A Starts a Live Broadcast at
http://www.example.com/user-a
User B,C,D,E and count goes on visits that url i.e:
http://www.example.com/user-a
and will be able to watch the live broadcast/stream. I was playing around with RTCPeerConnection
to create peer to peer to share stream to save bandwidth and server load and i am able to use the following code to go with standard createOffer
and createAnswer
by sending OfferToReceiveAudio: true
, OfferToReceiveVideo: true
constraints. It is working all the way fine.
Now i have managed to create new peer connection for every connected peer (viewer). This way any one can broadcast from webcam and others (peers) will be able to watch broadcast. I am confused if this is a scalable process means what if user-a is broadcasting and viewers are 500+, and user-b is broadcasting and viewers are 1000. Is this reliable for live broadcasting. I don't want to setup a media server thats why i am using webrtc thing.
Client
var
ws = new WebSocket('wss://www.example.com:443'),
isRemote = false,
iStream = null,
iDescription = null,
iAnswer = null,
video = document.querySelector('video'),
peerConnCfg = {'iceServers':
[{'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'}]
},
mediaConstraints = {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
},
peer;
ws.onmessage = function(message){
var json = JSON.parse(message.data);
if(isRemote==true){
if(json.result=="ok"){
StartWatch(json.sdp, json.ice);
}else{
console.log("Watch start failed...");
}
}else{
if(json.result=="ok" && json.action=="rsdp"){
peer.setRemoteDescription(new RTCSessionDescription(json.sdp), function () {
peer.addIceCandidate(new RTCIceCandidate(json.ice)).then(function(){
console.log("added remote ice");
}).catch(function(e){
console.log(e);
});
}, function(error){
console.log("Offer remote failed error : " + error);
});
}
console.log("Remote is false...");
}
};
function golive(){
peer = new RTCPeerConnection(peerConnCfg);
peer.onicecandidate = function(event){
console.log("Got candidate...");
if(event.candidate != null) {
ws.send(JSON.stringify({'mod' : 'golive', 'channel': 'channel-xyz', 'sdp' : iDescription, 'ice' : event.candidate}));
}
};
navigator.getUserMedia({ audio: true, video: { width: 1280, height: 720 } },
function(stream) {
iVideoStream = stream;
video.src = window.URL.createObjectURL(stream);
video.onloadedmetadata = function(e){
video.play();
};
peer.addStream(stream);
peer.createOffer(function(description){
//success
console.log("Got description...");
peer.setLocalDescription(description, function () {
iDescription = description;
}, function() {console.log('set description error')});
}, function(){
//error
});
},
function(err) {
console.log("The following error occurred: " + err.name);
}
);
}
function watch(){
ws.send(JSON.stringify({'mod' : 'watch', 'channel': 'channel-xyz'}));
}
function StartWatch(sdp, ice){
peer = new RTCPeerConnection(peerConnCfg);
peer.onicecandidate = function(event){
console.log("Got candidate...");
if(event.candidate != null) {
ws.send(JSON.stringify({'mod' : 'goliveupdate', 'channel': 'channel-xyz', 'sdp' : iAnswer, 'ice' : event.candidate}));
}
};
peer.onaddstream = function(event){
peer.addStream(event.stream);
video.src = window.URL.createObjectURL(event.stream);
video.onloadedmetadata = function(e){
video.play();
};
};
peer.setRemoteDescription(new RTCSessionDescription(sdp), function () {
peer.createAnswer(function(answer){
peer.setLocalDescription(answer, function() {
iAnswer = answer;
peer.addIceCandidate(new RTCIceCandidate(ice)).then(function(){
console.log("added");
}).catch(function(e){
console.log(e);
});
}, function (error) {
console.log("Local desc error: " + error);
});
}, function(error){
console.log("Answer Error: " + error);
});
}, function(error) { console.log('set description error: ' + error)});
}
//i.e waitForSocketConnection
function _init(){
setTimeout(function(){
if (ws.readyState === 1) {
console.log('Connected to WS');
<?php
if(!isset($_GET['v'])){
echo 'golive();';
}else{
echo 'isRemote = true; watch();';
}
?>
return;
}else{
_init();
}
}, 5);
}
_init();
Socket Server for Signaling
"use strict";
var fs = require('fs');
var cfg = {
ssl: true,
port: 443,
ssl_key: '/home/csr/example_in.key',
ssl_cert: '/home/csr/example_in.crt'
};
var app = null;
var httpServ = ( cfg.ssl ) ? require('https') : require('http');
var processRequest = function( req, res ) {
res.writeHead(200);
res.end("All glory to WebSockets!\n");
};
if ( cfg.ssl ) {
app = httpServ.createServer({
// providing server with SSL key/cert
key: fs.readFileSync( cfg.ssl_key ),
cert: fs.readFileSync( cfg.ssl_cert )
}, processRequest ).listen( cfg.port );
} else {
app = httpServ.createServer( processRequest ).listen( cfg.port );
}
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({server: app, host:'www.example.in'}),
example = {
channels: [],
run: function(){
wss.on('connection', function(ws) {
//example.channels.push(ws);
ws.on('message', function(message) {
//example.respond(ws, message);
example.respond(ws, JSON.parse(message));
});
});
wss.on('close', function() {
example.closeChannel(ws);
});
},
closeChannel(ws){
for (var channel in example.channels) {
var _channel = example.channels[channel];
if (_channel[i] == ws){ delete _channel[i]; }
example.channels[channel] = example.swapArray(_channel);
if(example.channels && example.channels[channel] && !example.channels[channel].length)
delete example.channels[channel];
}
},
swapArray: function(arr) {
var swapped = [],
length = arr.length;
for (var i = 0; i < length; i++) {
if (arr[i])
swapped[swapped.length] = arr[i];
}
return swapped;
},
respond : function(ws, message){
//ws.send(message.mod); return;
switch(message.mod){
case "golive":
example.channels[message.channel] = {
name : message.channel,
socket : ws,
sdp : message.sdp,
rsdp: null,
ice: message.ice,
rice: null
};
ws.send(JSON.stringify({'result' : 'ok', 'action' : 'connected', 'channel': message.channel}));
break;
case "goliveupdate":
example.channels[message.channel].rsdp = message.sdp;
example.channels[message.channel].rice = message.ice;
example.channels[message.channel].socket.send(JSON.stringify({'result' : 'ok', 'action' : 'rsdp', 'sdp' : message.sdp, 'ice' : message.ice, 'message': 'A peer connected'}));
break;
case "watch":
if(example.channels[message.channel]){
example.channels[message.channel].socket.send(JSON.stringify({'result' : 'ok', 'message': 'A peer connected'}));
ws.send(JSON.stringify({
'result' : 'ok',
'action' : 'connected',
'message': 'Connected to ' + message.channel,
'sdp' : example.channels[message.channel].sdp,
'ice' : example.channels[message.channel].ice
}));
}else{
ws.send(JSON.stringify({'result' : 'ok', 'message': 'Unable to connect to ' + message.channel}));
}
break;
}
}
};
example.run();
Upvotes: 0
Views: 531
Reputation: 42500
A one-to-one peer connection is inherently not "broadcast". Unless the particular user is in Google Fiber City with a beefy box, serving 500+ video peer connections from one user's upload link isn't going to scale obviously. For that amount of traffic you need a media server.
Upvotes: 0