Stream a webcam using Javascript, NodeJS, Android, Opera Mobile, Web Sockets and HTML5
With all the recent Goruck-ness it’s time to throw out a tech post. I’ve been prepping for a NodeJS presentation recently and wanted a unique demo. I had also come across the experimental build of Opera which supports accessing the native webcam of a mobile device. So I threw these two technologies together and came up with a Javascript powered way to stream a video camera from a mobile device to a bunch of desktop (or mobile) clients.
HTML5 had originally laid out support for a new element (the “device” element) but it appears this element has been scrapped now in favour of the “getUserMedia” API. So far I have only seen the Opera Mobile Experimental build support this but hopefully it’ll get into Dolphin and other mobile browsers soon.
http://my.opera.com/core/blog/2011/03/23/webcam-orientation-preview
If you just want to cut to the chase here’s a video of the final result:
There are three moving parts here
- a) The client page which captures the video
- b) the server which is a simple Node broadcast server
- c) and the Client which is just a web page that renders the output.
The client is just a copy-paste from Opera’s website. I take the video stream, render it to a canvas, then grab the canvas as a base64 encoded image. One image per frame. I could not find a way to limit the video’s size so the resulting image is fairly large (240×400 on an HTC Incredible). I down-sampled, cutting the image data to reduce the traffic over the wire. Framerates are still pretty poor but this is just an experiment.
The server is a generic websockets server and gets a message which is a raw data/base64 stream. It broadcasts this down the any connected clients.
The client then is the simplest of all, just connects to the socket server and renders whatever it gets to an tag.
It’s all Javascript.
Client (this is a jade view).
h1
Remote Webcam using NodeJS, Opera, Web Sockets and HTML5/Canvas
video(autoplay=true,id="sourcevid")
canvas(id="output")
div(id="log")
script
var log = function(msg) {
document.getElementById('log').innerHTML = document.getElementById('log').innerHTML + msg + "<br/>";
};
var video = document.getElementsByTagName('video')[0],
heading = document.getElementsByTagName('h1')[0];
if(navigator.getUserMedia) {
navigator.getUserMedia('video', successCallback, errorCallback);
function successCallback( stream ) {
video.src = stream;
};
function errorCallback( error ) {
heading.textContent = "An error occurred: [CODE " + error.code + "]";
};
} else {
heading.textContent = "Native web camera streaming is not supported in this browser!";
};
var back = document.createElement('canvas');
var backcontext = back.getContext('2d');
var ws;
if('WebSocket' in window){
connect('ws://192.168.2.100:8080/');
} else {
log ('web sockets not supported');
}
function connect(host) {
ws = new WebSocket(host);
ws.onopen = function () {
log('connected');
};
ws.onclose = function () {
log('socket closed');
};
ws.onerror = function (evt) {
log('<span style="color: red;">ERROR:</span> ' + evt.data);
};
};
function send(msg){
if (ws != null) {
if(ws.readyState === 1) {
ws.send(msg);
}
} else {
//log ('not ready yet');
}
}
cw = 120;//240;//video.clientWidth;
ch = 200;//400;//video.clientHeight;
log('width = ' + ch);
back.width = cw;
back.height = ch;
draw(video, backcontext, cw, ch);
function draw(v, bc, w, h) {
// First, draw it into the backing canvas
bc.drawImage(v, 0, 0, w, h);
// Grab the pixel data from the backing canvas
var stringData=back.toDataURL();
// send it on the wire
send(stringData);
// Start over! 10 frames a second = 100milliseconds
setTimeout(function(){ draw(v, bc, w, h); });
}
The server. Simple Broadcast app.
var sys = require("sys"),
ws = require("./ws.js");
var clients = [];
ws.createServer(
function (websocket) {
clients.push(websocket);
websocket.addListener("connect", function (resource) {
// emitted after handshake
sys.debug("connect: " + resource);
}).addListener("data", function (data) {
// handle incoming data
// send data to ALL clients whenever ANY client send up data
for (var i = 0 ; i < clients.length ; i ++ ) {
clients[i].write(data);
}
}).addListener("close", function () {
// emitted when server or client closes connection
sys.debug("close");
});
}).listen(8080);
sys.debug("Listening on port 8080");
The client.
<img src="" id="frame" style="width:240px;height:400px"/>
<div id="log"></div>
<script type="text/javascript">
var img;
function Init() {
img = document.getElementById("frame");
}
$(document).ready(function () {
Init();
});
// Web socket connection stuff is next...
if('WebSocket' in window){
connect('ws://localhost:8080/');
} else {
log ('web sockets not supported');
}
var ws;
function connect(host) {
ws = new WebSocket(host);
ws.onopen = function () {
log('connected');
};
ws.onmessage = function (evt) {
if (evt.data != null) {
if ((evt.data[0]=== "d") &amp;amp;amp;&amp;amp;amp; (evt.data[1]==="a") ) img.src=evt.data; //log('got' + evt.data);
}
};
ws.onclose = function () {
log('socket closed');
};
ws.onerror = function (evt) {
log('<span style="color: red;">ERROR:</span> ' + evt.data);
};
};
function log(msg){
document.getElementById('log').innerHTML = msg + "<br/>" + document.getElementById('log').innerHTML ;
}
</script>
Instead of broadcasting, you could store these frames in a MongoDB for assembly later. This would make the ultimate, storage independent video camera. Never run out of storage again!
I really need to start storing these snippets on github…
P.S. Everyone knows insects are clockwork which is why god made so many of them. Today’s image is from http://www.insectlabstudio.com/









Very interesting. Did you have any luck with browsers other the opera to access the camera of the mobile device?
Could be PhoneGap (http://www.phonegap.com/) of any help? The download contains a JavaScript library that also can help to access the phone camera.
That’s some awesome stuff Francis. Thanks for posting. Does it still relay the frames if the screen goes off [i.e. the auto screen time out] ? Would be hell of a cool way to spy on folks if it did
@Stephan – nope, so far only Opera supports this HTML5 extension to access the device. I suppose Phonegap could be used but again you’d need to write an extension to access the device’s data.
@Sudeep – thanks man. The camera shuts off once the screen goes off to save battery life but you can always modify this in the device’s Settings. My droid gets fairly poor battery life but it’s two years old by now.
One could conceivably use this to have video input from any random mobile device (from someone visiting a page) and process that video information remotely in real time for some other purpose?
I suppose I’m conjuring up some sort of interactive web thing involving mobile phones here.
Hi Francis,
Unfortunately this is not working due to security aspects on actual browsers (testing on Opera). Currently the toDataURL() method cannot capture the data from Canvas when this is populated by HTMLVideoElement. If you know about this, do you have any other solution (workaround)?
Regards,
Rafael Roballo
Amazing
Thanks for sharing..
Hi is a amazing tutorial
thks very match
a search a solution to brodcasting android camera to a streaming server like wowza some one can help me ?? how can i change this code to send a stream to a wowza
hello
is a nice tutorial i search for solution to send from my android camera to wowza server any one can help me ???
F’king awesome!
Any idea how to connect directly to the PC over Wifi?
You could make an ad-hoc network between the PC and the phone. That’s what I did in this demo.
Thanks for demonstrate this awesome feature, but what is the ws.js? you did not include it in here. Is there any other software behind this to get it done?
[...] [...]
Hey,
I have a problem, my node can’t find ws.js? witch module is it? I tryed to install ws and websocket but i get everytime the same error
Can you post the link too your version of websocket?
Sorry i’m very new to node
Hey if my view client connects, the nodejs server crashes with:
Error: This socket is closed.
at Socket._write (net.js:517:19)
at Socket.write (net.js:509:15)
at EventEmitter.exports.createServer.net.createServer.emitter.write (/home/manner/Documents/nodejs/hello/ws.js:220:16)
at EventEmitter. (/home/manner/Documents/nodejs/hello/app.js:47:15)
at EventEmitter.emit (events.js:88:17)
at handle (/home/manner/Documents/nodejs/hello/ws.js:99:19)
at Socket.exports.createServer.net.createServer.emitter.remoteAddress (/home/manner/Documents/nodejs/hello/ws.js:198:9)
at Socket.EventEmitter.emit (events.js:88:17)
at TCP.onread (net.js:395:14)
any idea how to fix that?
Greetz Manner
[...] Stream a webcam using Javascript, NodeJS, Android, Opera Mobile, Web Sockets and HTML5 [...]
[...] Periodically capture frame of streaming video and send that as image [...]
Hello Francis,
Thanks for your cool post. I installed node and tried to run broadcasing js file but i got error, could you point out what config is missing.
Thanks,
Here is error info
$ node node_project/broadcast.js
/Users/dn/projects/node-v0.8.15/node_project/broadcast.js:6
ws.createServer(
^
TypeError: Object # has no method ‘createServer’
at Object. (/Users/dn/projects/node-v0.8.15/node_project/broadcast.js:6:4)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:492:10)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)