Home » Cool & Future Tech, Featured, Headline, html5, Things I've Made

Stream a webcam using Javascript, NodeJS, Android, Opera Mobile, Web Sockets and HTML5

19 September 2011 20 Comments
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;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/

20 Comments »

  • Stephan Bardubitzki said:

    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.

  • Sudeep said:

    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 :)

  • Francis (author) said:

    @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.

  • Joseph said:

    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.

  • Rafael Roballo said:

    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

  • Rajesh Pillai said:

    Amazing :) Thanks for sharing..

  • med said:

    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

  • medfromTunisia said:

    hello
    is a nice tutorial i search for solution to send from my android camera to wowza server any one can help me ???

  • Nick said:

    F’king awesome!
    Any idea how to connect directly to the PC over Wifi?

  • Francis (author) said:

    You could make an ad-hoc network between the PC and the phone. That’s what I did in this demo.

  • ket said:

    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?

  • Manner said:

    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

  • Manner said:

    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

  • Node.Js, Express y Socket.IO en UdADev 2012, Cuenca, Ecuador - Angel "Java" Lopez said:

    […] Stream a webcam using Javascript, NodeJS, Android, Opera Mobile, Web Sockets and HTML5 […]

  • HTML5 solution to upload a webcam/camera video stream to server - feed99 said:

    […] Periodically capture frame of streaming video and send that as image […]

  • Dave said:

    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)

  • Aly said:

    Nice.

  • Stefan said:

    Is this peer to peer ? And if yes, can it be used to have multiple pear into one stream ?

  • Node.Js: Links, News And Resources (21) | Angel "Java" Lopez on Blog said:

    […] Stream a webcam using Javascript, NodeJS, Android, Opera Mobile, Web Sockets and HTML5 | Francis Shanahan[.com] http://francisshanahan.com/index.php/2011/stream-a-webcam-using-javascript-nodejs-android-opera-mobi… […]

Leave your response!

Add your comment below, or trackback from your own site. You can also subscribe to these comments via RSS.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

This is a Gravatar-enabled weblog. To get your own globally-recognized-avatar, please register at Gravatar.