├── .gitignore ├── README.md ├── app.js ├── client.html ├── css └── style.css ├── index.html ├── js ├── camera.js └── client.js └── server.html /.gitignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | node_modules/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML5-Device-Streamer 2 | 3 | This is a simple demo of streaming live video from one web browser to another in Javascript. 4 | Video is captured using the mobile phone camera using getUserMedia(), periodically frames 5 | are captured and sent via a websocket to node.js which rebroadcasts them to all connected clients. 6 | The clients then show the video by rendering each frame as an image as it arrives. The framerate is 7 | slow due to the limited processor power of phones. 8 | 9 | ## Requirements 10 | 11 | * An android phone running opera mobile with getUserMedia support - http://my.opera.com/core/blog/2011/03/23/webcam-orientation-preview 12 | * A server running node.js to run the websocket server. The node module websocket-server is required. 13 | * A websocket compatible browser to watch the video 14 | 15 | ## Inspiration 16 | 17 | This quick hack was inspired by this video http://www.youtube.com/watch?v=cjmuwA8l1LQ 18 | but the author has not released the source anywhere I've found. As I had already been 19 | experimenting in the area I pretty much knew how to do it already so did it and put it here. -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var fs = require('fs'); 3 | var ws = require('websocket-server'); 4 | var sys = require("sys"); 5 | var path = require('path'); 6 | 7 | //create websocket server 8 | var ws_server = ws.createServer(); 9 | 10 | /** 11 | * Setup simple http server to serve static page elements on port 8000 12 | */ 13 | http.createServer(function (request, response) { 14 | 15 | console.log('request starting...'); 16 | 17 | var filePath = '.' + request.url; 18 | if (filePath == './') 19 | filePath = './index.html'; 20 | 21 | var extname = path.extname(filePath); 22 | var contentType = 'text/html'; 23 | switch (extname) { 24 | case '.js': 25 | contentType = 'text/javascript'; 26 | break; 27 | case '.css': 28 | contentType = 'text/css'; 29 | break; 30 | } 31 | 32 | path.exists(filePath, function(exists) { 33 | 34 | if (exists) { 35 | fs.readFile(filePath, function(error, content) { 36 | if (error) { 37 | response.writeHead(500); 38 | response.end(); 39 | } 40 | else { 41 | response.writeHead(200, { 'Content-Type': contentType }); 42 | response.end(content, 'utf-8'); 43 | } 44 | }); 45 | } 46 | else { 47 | response.writeHead(404); 48 | response.end(); 49 | } 50 | }); 51 | 52 | }).listen(8000); 53 | 54 | 55 | 56 | // Handle WebSocket Requests 57 | ws_server.addListener("connection", function(conn){ 58 | 59 | //add listener to rebroadcast incomming messages 60 | conn.addListener("message", function(msg){ 61 | console.log('message'); 62 | conn.broadcast(msg); 63 | }); 64 | }); 65 | 66 | ws_server.addListener("error", function(){ 67 | console.log(Array.prototype.join.call(arguments, ", ")); 68 | }); 69 | 70 | 71 | ws_server.addListener("disconnected", function(conn){ 72 | ws_server.broadcast("<"+conn.id+"> disconnected"); 73 | }); 74 | 75 | //start websocket server on port 8001 76 | ws_server.listen(8001); 77 | -------------------------------------------------------------------------------- /client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTML5 Video Streaming - Client 4 | 5 | 6 | 7 | 8 |

HTML5 Video Streaming Demo - Client

9 | 10 |

Server requires a browser which supports getUserMedia, basically a nightly 11 | opera mobile build or a customer ericsson webkit build.

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Document : style.css 3 | Created on : 05-Sep-2011, 21:08:10 4 | Author : oliversmith 5 | Description: 6 | Purpose of the stylesheet follows. 7 | */ 8 | 9 | /* 10 | TODO customize this sample style 11 | Syntax recommendation http://www.w3.org/TR/REC-CSS2/ 12 | */ 13 | 14 | root { 15 | display: block; 16 | } 17 | 18 | #buffer { 19 | display: none; 20 | } 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTML5 Video Streaming 4 | 5 | 6 | 7 | 8 |

HTML5 Video Streaming Demo

9 | 10 |

Server requires a browser which supports getUserMedia, basically a dev 11 | opera mobile build or a custom Ericsson webkit build.

12 | 13 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /js/camera.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /******************************************************************************* 4 | * 5 | * Camera Functionality 6 | * 7 | ******************************************************************************* 8 | */ 9 | 10 | var videoId = 'video', 11 | scaleFactor = 0.5, 12 | snapshot = null, 13 | upload_image, 14 | video = document.getElementsByTagName('video')[0], 15 | heading = document.getElementsByTagName('h1')[0]; 16 | var ws = new WebSocket("ws://192.168.1.2:8001/"); 17 | 18 | 19 | 20 | /** 21 | * Captures a image frame from the provided video element. 22 | * 23 | * @param {Video} video HTML5 video element from where the image frame will be captured. 24 | * @param {Number} scaleFactor Factor to scale the canvas element that will be return. This is an optional parameter. 25 | * 26 | * @return {Canvas} 27 | */ 28 | function capture(video, scaleFactor) { 29 | var w = video.videoWidth * scaleFactor; 30 | var h = video.videoHeight * scaleFactor; 31 | var canvas = document.createElement('canvas'); 32 | canvas.width = w; 33 | canvas.height = h; 34 | var ctx = canvas.getContext('2d'); 35 | ctx.drawImage(video, 0, 0, w, h); 36 | return canvas; 37 | } 38 | 39 | /** 40 | * Invokes the capture() function and attaches the canvas element to the DOM. 41 | */ 42 | function shoot(){ 43 | var output = document.getElementById('output'); 44 | var canvas = capture(video, scaleFactor); 45 | canvas.onclick = function(){ 46 | window.open(this.toDataURL()); 47 | }; 48 | 49 | //load the photo when the new page opens 50 | $('#photos').live('pageshow',function(){ 51 | $('#output').html(canvas); 52 | upload_image = canvas.toDataURL(); 53 | }); 54 | 55 | //load the photos page 56 | $.mobile.changePage('#photos'); 57 | 58 | } 59 | 60 | /** 61 | * Tests support for toDataURL required for linking to full size images 62 | * 63 | * @return {bool} 64 | */ 65 | 66 | function supportsToDataURL() 67 | { 68 | var c = document.createElement("canvas"); 69 | var data = c.toDataURL("image/png"); 70 | return (data.indexOf("data:image/png") == 0); 71 | } 72 | 73 | /** 74 | * Tests support for getUserMedia... kind of the whole point of this 75 | * 76 | * @return {bool} 77 | */ 78 | 79 | function supportsGetUserMedia() 80 | { 81 | if(!navigator.getUserMedia) 82 | { 83 | heading.textContent = 84 | "Native web camera streaming is not supported in this browser!"; 85 | 86 | return false 87 | } 88 | else 89 | { 90 | return true; 91 | } 92 | } 93 | 94 | 95 | if(supportsGetUserMedia()) 96 | { 97 | navigator.getUserMedia('video', successCallback, errorCallback); 98 | 99 | function successCallback( stream ) 100 | { 101 | video.src = stream; 102 | } 103 | 104 | function errorCallback( error ) 105 | { 106 | heading.textContent = 107 | "An error occurred: [CODE " + error.code + "]"; 108 | } 109 | } 110 | 111 | 112 | if(!supportsToDataURL()) 113 | { 114 | heading.textContent+="You browser is lame and does NOT support Canvas.toDataURL();" 115 | } 116 | 117 | 118 | /******************************************************************************* 119 | * 120 | * Websockets stuff 121 | * 122 | ******************************************************************************* 123 | */ 124 | button = document.getElementById('button'); 125 | 126 | //setup websocket connection 127 | 128 | 129 | 130 | //setup callbacks 131 | ws.onopen = function() 132 | { 133 | console.log('Websocket Open'); 134 | }; 135 | 136 | ws.onclose = function() 137 | { 138 | console.log('Websocket Closed'); 139 | }; 140 | 141 | ws.onerror = function(event) 142 | { 143 | console.log('error'); 144 | }; 145 | 146 | ws.onmessage = function(event) 147 | { 148 | console.log('message'); 149 | }; 150 | 151 | //bind button to send picture 152 | addEventListener('click', streamFrame, false); 153 | 154 | /** 155 | * Start grabbing frames and sending them via the websocket 156 | * 157 | * Called when the button is pressed, grabs a frame of video, 158 | * renders it to the canvas, grabs the base64 encoded data and 159 | * sends it through the websocket. The framerate can be adjusted 160 | * by changing the interval time but I found this to be the fastest 161 | * my phone could cope with 162 | * 163 | */ 164 | 165 | function streamFrame() 166 | { 167 | var context = canvas.getContext("2d"); 168 | 169 | setInterval(function(){ 170 | context.drawImage(video, 0, 0, 240, 320); 171 | var image = canvas.toDataURL("image/png"); 172 | 173 | 174 | console.log('send'); 175 | ws.send(image); 176 | 177 | },400); 178 | } -------------------------------------------------------------------------------- /js/client.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Streaming Video Client 4 | * 5 | * Recieves image data via a websocket connection and renders it to #img 6 | * 7 | ******************************************************************************* 8 | */ 9 | 10 | var ws = new WebSocket("ws://192.168.1.2:8001/"), 11 | img = document.getElementById('img'); 12 | 13 | //setup callbacks 14 | ws.onopen = function() 15 | { 16 | console.log('Websocket Open'); 17 | }; 18 | 19 | ws.onclose = function() 20 | { 21 | console.log('Websocket Closed'); 22 | }; 23 | 24 | ws.onerror = function(event) 25 | { 26 | console.log('error'); 27 | }; 28 | 29 | ws.onmessage = function(message) 30 | { 31 | img.src=message.data; 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /server.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTML5 Video Streaming - Server 4 | 5 | 6 | 7 | 8 |

HTML5 Video Streaming Demo

9 | 10 |

Server requires a browser which supports getUserMedia, basically a nightly 11 | opera mobile build or a customer ericsson webkit build.

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------