17 Commits

Autor SHA1 Mensagem Data
Bernhard K. Weisshuhn 4cb48f1fd1 new release 1.0.2 2012-12-27 14:55:39 +01:00
Bernhard K. Weisshuhn ef1987f2a4 update ar-drone and buffy dependencies 2012-12-27 14:54:58 +01:00
Bernhard K. Weisshuhn ace272f349 add .jshintrc for code consistency
… to annoy substack ;-)
2012-12-26 16:20:47 +01:00
Bernhard K. Weisshuhn 54219e67ac update change log 2012-12-26 16:19:31 +01:00
Bernhard K. Weisshuhn d0d150fb37 don't require express rc in example 2012-12-26 16:19:09 +01:00
Bernhard K. Weisshuhn 8add9fc12b build new client 2012-12-26 16:15:10 +01:00
Bernhard K. Weisshuhn 2f19e20fd2 reindent 2012-12-26 16:14:38 +01:00
Bernhard K. Weisshuhn d8eaacc2ad use websocket namespace 'dronestream' 2012-12-26 16:04:22 +01:00
Bernhard K. Weisshuhn cf23e02416 use requestAnimationFrame for video rendering 2012-12-26 16:00:12 +01:00
Bernhard K. Weisshuhn 7163fb5262 add requestAnimationFrame shim 2012-12-26 15:59:35 +01:00
Bernhard Weisshuhn (a.k.a. bernhorst) 93236bd275 Update example/express/app.js
fix example require name
2012-12-26 08:02:01 +01:00
Bernhard K. Weisshuhn a0a9a38427 version 1.0.1 2012-12-16 16:42:48 +01:00
Bernhard K. Weisshuhn 270fb8fcbf add changelog 2012-12-16 16:42:33 +01:00
Karl Westin a4a4acf109 Merge pull request #4 from karlwestin/master
Documentation update + clean up 'examples folder'
2012-12-16 06:50:25 -08:00
Karl Westin 660206b3f4 Cleaned up 'examples' dir 2012-12-16 06:38:58 -08:00
Karl Westin 9b9c29a2df Added 'how to use' section in README
Removed "documentation out of date" + added code example
2012-12-16 06:38:01 -08:00
Bernhard K. Weisshuhn b1b2967654 Add link to cgnjs demo 2012-12-15 16:55:45 +01:00
17 arquivos alterados com 278 adições e 264 exclusões
+32
Ver Arquivo
@@ -0,0 +1,32 @@
{
"es5" : true,
"browser" : false,
"boss" : true,
"curly": true,
"debug": false,
"devel": true,
"eqeqeq": true,
"evil": false,
"forin": true,
"immed": true,
"indent": 4,
"jquery": true,
"laxbreak": false,
"laxcomma": true,
"newcap": true,
"noarg": false,
"noempty": true,
"nonew": true,
"nomen": false,
"onevar": true,
"plusplus": false,
"regexp": false,
"trailing": true,
"undef": true,
"sub": false,
"strict": true,
"globalstrict": true,
"white": true,
"node" : true
}
+33
Ver Arquivo
@@ -0,0 +1,33 @@
0.1.0 / 2012-10-20
==================
* Initial version, unreleased
0.2.0 / 2012-10-20
==================
* released as npm package dronestream
1.0.0 / 2012-12-15
==================
* add support for multiple browser clients
* add support for browser reloads
* reconnect to drone on failure
* add support for bare http sockers
* remove express dependency
* export as a module
* added examples for using in other applications
1.0.1 / 2012-12-16
==================
* update documentation
* add changelog
1.0.2 / 2012-12-27
==================
* use requestAnimationFrame() for rendering video frames
* cleaned up express example
* update ar-drone and buffy dependencies to latest versions
+28 -6
Ver Arquivo
@@ -3,8 +3,6 @@
Get a realtime live video stream from your
[Parrot AR Drone 2.0](http://ardrone2.parrot.com/) straight to your browser.
## Documentation is a little out of date, please check the two examples for now!
## Requirements
You'll need a decent and current browser and some cpu horsepower.
@@ -12,6 +10,27 @@ This code uses web-sockets and the incredibly awesome
[Broadway.js](https://github.com/mbebenita/Broadway) to render the video frames
in your browser using a WebGL canvas.
## How to use
Please see the http.createServer and Express 3.0 examples in the 'examples' dir.
You attach the stream to your server like this:
```javascript
// in node:
//
// note that the 'server' object points to a server instance and NOT an express app.
require("dronestream").listen(server);
```
We serve the client in the same manner as Socket.IO. Add a reference to
**/dronestream/nodecopter-client.js** in your template. Then attach the stream to a DOM node:
```html
<!-- on the client -->
<script src="/dronestream/nodecopter-client.js"></script>
<script>
// video canvas will auto-size to the DOM-node, or default to 640*360 if no size is set.
new NodecopterStream(document.getElementById("droneStream"));
</script>
```
## How it works
@@ -24,10 +43,9 @@ In the browser broadway takes care of the rendering of the WebGL canvas.
## Status
For this release I was exclusively interested in the lowest possible latency.
There is no error handling for the websockets, the connection to the drone or
the video player what-so-ever. This may come eventually, or may not. I think it
is enough to be used as a starting point for your own integration.
Node-dronestream has gained some stability in the last release. It attempts
to recover lost connections to the drone, and it handles multiple clients,
disconnections, etc. See "How to use" for API.
## Thanks
@@ -49,3 +67,7 @@ is enough to be used as a starting point for your own integration.
- @karlwestin for picking up where I was to lazy to actually make this usable.
## Demo
Watch @felixge demoing node-dronestream live at german user group cgnjs:
http://www.youtube.com/watch?v=nwGNNMJt4mE&t=19m52
+49 -7
Ver Arquivo
@@ -1098,7 +1098,47 @@ var FilterWebGLCanvas = (function () {
return constructor;
})(); /*jshint browser:true */
/*global Avc:true, YUVWebGLCanvas: true, Size: true */
/*global Avc:true, YUVWebGLCanvas: true, Size: true, requestAnimationFrame:true */
/* requestAnimationFrame polyfill: */
(function (window) {
'use strict';
var lastTime = 0,
vendors = ['ms', 'moz', 'webkit', 'o'],
x,
length,
currTime,
timeToCall;
for (x = 0, length = vendors.length; x < length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[
vendors[x] + 'RequestAnimationFrame'
];
window.cancelAnimationFrame = window[
vendors[x] + 'CancelAnimationFrame'
] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback, element) {
currTime = new Date().getTime();
timeToCall = Math.max(0, 16 - (currTime - lastTime));
lastTime = currTime + timeToCall;
return window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}
}(window));
/* NodeCopterStream: */
(function (window, document, undefined) {
'use strict';
var NS,
@@ -1122,12 +1162,14 @@ var FilterWebGLCanvas = (function () {
}
function handleDecodedFrame(buffer, width, height) {
var lumaSize = width * height,
requestAnimationFrame(function () {
var lumaSize = width * height,
chromaSize = lumaSize >> 2;
webGLCanvas.YTexture.fill(buffer.subarray(0, lumaSize));
webGLCanvas.UTexture.fill(buffer.subarray(lumaSize, lumaSize + chromaSize));
webGLCanvas.VTexture.fill(buffer.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize));
webGLCanvas.drawScene();
webGLCanvas.YTexture.fill(buffer.subarray(0, lumaSize));
webGLCanvas.UTexture.fill(buffer.subarray(lumaSize, lumaSize + chromaSize));
webGLCanvas.VTexture.fill(buffer.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize));
webGLCanvas.drawScene();
});
}
function setupCanvas(div) {
@@ -1151,7 +1193,7 @@ var FilterWebGLCanvas = (function () {
socket = new WebSocket(
'ws://' +
window.document.location.hostname + ':' +
window.document.location.port
window.document.location.port + '/dronestream'
);
socket.binaryType = 'arraybuffer';
socket.onmessage = handleNalUnits;
+49 -7
Ver Arquivo
@@ -1,5 +1,45 @@
/*jshint browser:true */
/*global Avc:true, YUVWebGLCanvas: true, Size: true */
/*global Avc:true, YUVWebGLCanvas: true, Size: true, requestAnimationFrame:true */
/* requestAnimationFrame polyfill: */
(function (window) {
'use strict';
var lastTime = 0,
vendors = ['ms', 'moz', 'webkit', 'o'],
x,
length,
currTime,
timeToCall;
for (x = 0, length = vendors.length; x < length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[
vendors[x] + 'RequestAnimationFrame'
];
window.cancelAnimationFrame = window[
vendors[x] + 'CancelAnimationFrame'
] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback, element) {
currTime = new Date().getTime();
timeToCall = Math.max(0, 16 - (currTime - lastTime));
lastTime = currTime + timeToCall;
return window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}
}(window));
/* NodeCopterStream: */
(function (window, document, undefined) {
'use strict';
var NS,
@@ -23,12 +63,14 @@
}
function handleDecodedFrame(buffer, width, height) {
var lumaSize = width * height,
requestAnimationFrame(function () {
var lumaSize = width * height,
chromaSize = lumaSize >> 2;
webGLCanvas.YTexture.fill(buffer.subarray(0, lumaSize));
webGLCanvas.UTexture.fill(buffer.subarray(lumaSize, lumaSize + chromaSize));
webGLCanvas.VTexture.fill(buffer.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize));
webGLCanvas.drawScene();
webGLCanvas.YTexture.fill(buffer.subarray(0, lumaSize));
webGLCanvas.UTexture.fill(buffer.subarray(lumaSize, lumaSize + chromaSize));
webGLCanvas.VTexture.fill(buffer.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize));
webGLCanvas.drawScene();
});
}
function setupCanvas(div) {
@@ -52,7 +94,7 @@
socket = new WebSocket(
'ws://' +
window.document.location.hostname + ':' +
window.document.location.port
window.document.location.port + '/dronestream'
);
socket.binaryType = 'arraybuffer';
socket.onmessage = handleNalUnits;
-33
Ver Arquivo
@@ -1,33 +0,0 @@
var express = require('express')
, routes = require('./routes')
, app = express()
, path = require('path')
, server = require("http").createServer(app)
;
app.configure(function () {
app.set('views', __dirname + '/views');
app.set('view engine', 'jade', { pretty: true });
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});
app.configure('development', function () {
app.use(express.errorHandler());
app.locals.pretty = true;
});
app.get('/', routes.index);
/*
* Important:
*
* pass in the server object to listen, not the express app
* call 'listen' on the server, not the express app
*/
// should be require("node-dronestream").listen(server)
require("../lib/server").listen(server);
server.listen(3000);
+1 -1
Ver Arquivo
@@ -28,6 +28,6 @@ app.get('/', routes.index);
* pass in the server object to listen, not the express app
* call 'listen' on the server, not the express app
*/
// should be require("node-dronestream").listen(server)
// should be require("dronestream").listen(server);
require("../../index").listen(server);
server.listen(3000);
+2 -2
Ver Arquivo
@@ -1,8 +1,8 @@
{
"name": "dronestream-example",
"version": "0.1.0",
"version": "0.1.1",
"dependencies": {
"express": "3.0.0rc5",
"express": "3.0.x",
"jade": "*"
},
"scripts": {
-13
Ver Arquivo
@@ -1,13 +0,0 @@
{
"name": "dronestream-example",
"version": "0.1.0",
"dependencies": {
"express": "3.0.0rc5",
"jade": "*"
},
"scripts": {
"start": "node app"
},
"author": "Bernhard K. Weisshuhn <bkw@codingforce.com>",
"license": "BSD"
}
-50
Ver Arquivo
@@ -1,50 +0,0 @@
/*! normalize.css v1.0.1 | MIT License | git.io/normalize */
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block}
audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
audio:not([controls]){display:none;height:0}
[hidden]{display:none}
html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}
html,button,input,select,textarea{font-family:sans-serif}
body{margin:0}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
h2{font-size:1.5em;margin:.83em 0}
h3{font-size:1.17em;margin:1em 0}
h4{font-size:1em;margin:1.33em 0}
h5{font-size:.83em;margin:1.67em 0}
h6{font-size:.75em;margin:2.33em 0}
abbr[title]{border-bottom:1px dotted}
b,strong{font-weight:bold}
blockquote{margin:1em 40px}
dfn{font-style:italic}
mark{background:#ff0;color:#000}
p,pre{margin:1em 0}
code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}
pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}
q{quotes:none}
q:before,q:after{content:'';content:none}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-0.5em}
sub{bottom:-0.25em}
dl,menu,ol,ul{margin:1em 0}
dd{margin:0 0 0 40px}
menu,ol,ul{padding:0 0 0 40px}
nav ul,nav ol{list-style:none;list-style-image:none}
img{border:0;-ms-interpolation-mode:bicubic}
svg:not(:root){overflow:hidden}
figure{margin:0}
form{margin:0}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0;white-space:normal;*margin-left:-7px}
button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}
button,input{line-height:normal}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}
button[disabled],input[disabled]{cursor:default}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
-8
Ver Arquivo
@@ -1,8 +0,0 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
-10
Ver Arquivo
@@ -1,10 +0,0 @@
'use strict';
/*
* GET home page.
*/
exports.index = function (req, res) {
res.render('index', { title: 'Express' });
};
-22
Ver Arquivo
@@ -1,22 +0,0 @@
extends layout
block append head
script(type='text/javascript', src='/dronestream/nodecopter-client.js')
-# for developing the client, use those url:s
-# script(type='text/javascript', src='/dronestream/vendor/broadway/sylvester.js')
-# script(type='text/javascript', src='/dronestream/vendor/broadway/glUtils.js')
-# script(type='text/javascript', src='/dronestream/vendor/broadway/util.js')
-# script(type='text/javascript', src='/dronestream/vendor/broadway/avc-codec.js')
-# script(type='text/javascript', src='/dronestream/vendor/broadway/avc.js')
-# script(type='text/javascript', src='/dronestream/vendor/broadway/canvas.js')
-# script(type='text/javascript', src='/dronestream/nodecopter-stream.js')
-# concatenated version of client
block append bodyscripts
script
var copterStream = new NodecopterStream(document.querySelector('#dronestream'));
block content
div#dronestream(width=640, height=360)
-30
Ver Arquivo
@@ -1,30 +0,0 @@
!!! 5
html
block head
head
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
meta(name='viewport', content='width=device-width')
title= title
link(rel='stylesheet', href='/css/normalize.min.css')
link(rel='stylesheet', href='/css/style.css')
body
div.header-container
header.wrapper.clearfix
block header
nav
block navigation
div.main-container
div.main.wrapper.clearfix
block content
div.footer-container
footer.wrapper
block footer
block bodyscripts
// script(src='//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js')
// script
// window.jQuery || document.write('<script src="/js/vendor/h5bp/jquery-1.8.2.min.js"><\\x3C/script>')
+43 -37
Ver Arquivo
@@ -2,48 +2,54 @@
* Drone Stream listen:
* Takes a) a port number or b) a server object (node http or express, etc);
*/
var staticDir = "dronestream"
, check = new RegExp("^/" + staticDir, "i")
, dist = __dirname + "/../dist"
;
var staticDir = 'dronestream',
check = new RegExp('^/' + staticDir, 'i'),
dist = __dirname + '/../dist';
module.exports.listen = function listen(server) {
if(typeof server == "number") {
var port = server;
server = require("http").createServer();
server.listen(port);
}
'use strict';
var port, oldHandlers;
/*
* Serving up the static files needed
*/
var oldHandlers = server.listeners("request").splice(0);
server.on("request", function(req, res) {
if(handler(req, res)) {
return;
if (typeof server === 'number') {
port = server;
server = require('http').createServer();
server.listen(port);
}
for(var i = 0; i < oldHandlers.length; i++) {
oldHandlers[i].call(server, req, res);
}
});
/*
* Serving up the static files needed
*/
oldHandlers = server.listeners('request').splice(0);
function handler(req, res, next) {
if(!check.test(req.url)) {
return false;
}
var path = dist + req.url.replace(check, "");
console.log("checking static path: %s", path);
var read = require('fs').createReadStream(path);
read.pipe(res);
read.on("error", function(e) { console.log("Stream error: %s", e.message); });
server.on('request', function (req, res) {
var i = 0;
if (handler(req, res)) {
return;
}
return true;
}
/*
* Connecting stream + websocket server
*/
return require("./stream").attach(server);
for (; i < oldHandlers.length; ++i) {
oldHandlers[i].call(server, req, res);
}
});
function handler(req, res, next) {
var path, read;
if (!check.test(req.url)) {
return false;
}
path = dist + req.url.replace(check, '');
console.log('checking static path: %s', path);
read = require('fs').createReadStream(path);
read.pipe(res);
read.on('error', function (e) {
console.log('Stream error: %s', e.message);
});
return true;
}
/*
* Connecting stream + websocket server
*/
return require('./stream').attach(server);
};
+38 -35
Ver Arquivo
@@ -1,47 +1,50 @@
/*jshint node:true*/
/*
* Sets up a real stream + attaches it to a server
*/
module.exports.attach = function droneStream(server) {
var WebSocketServer = require('ws').Server
, wss = new WebSocketServer({server: server})
, sockets = []
, Parser = require('./PaVEParser')
, arDrone = require('ar-drone')
;
'use strict';
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({server: server, path: '/dronestream'}),
sockets = [],
Parser = require('./PaVEParser'),
arDrone = require('ar-drone');
function init() {
var tcpVideoStream = new arDrone.Client.PngStream.TcpVideoStream({timeout: 4000})
, p = new Parser();
function init() {
var tcpVideoStream = new arDrone.Client.PngStream.TcpVideoStream({
timeout: 4000
}),
p = new Parser();
console.log("Connecting to stream");
console.log("Connecting to stream");
tcpVideoStream.connect(function () {
tcpVideoStream.pipe(p);
});
tcpVideoStream.on("error", function(err) {
console.log("There was an error: %s", err.message);
tcpVideoStream.end();
tcpVideoStream.emit("end");
init();
});
p.on('data', function (data) {
sockets.forEach(function(socket) {
socket.send(data, {binary: true});
tcpVideoStream.connect(function () {
tcpVideoStream.pipe(p);
});
});
}
init();
wss.on('connection', function (socket) {
sockets.push(socket);
tcpVideoStream.on('error', function (err) {
console.log('There was an error: %s', err.message);
tcpVideoStream.end();
tcpVideoStream.emit("end");
init();
});
socket.on("close", function() {
console.log("Closing socket");
sockets = sockets.filter(function(el) {
return el !== socket;
});
p.on('data', function (data) {
sockets.forEach(function (socket) {
socket.send(data, {binary: true});
});
});
}
init();
wss.on('connection', function (socket) {
sockets.push(socket);
socket.on("close", function () {
console.log("Closing socket");
sockets = sockets.filter(function (el) {
return el !== socket;
});
});
});
});
};
+3 -3
Ver Arquivo
@@ -1,7 +1,7 @@
{
"name": "dronestream",
"description": "video live stream from your parrot ar.drone 2.0 to your browser in pure javascript",
"version": "1.0.0",
"version": "1.0.2",
"repository": {
"type": "git",
"url": "git@github.com:bkw/node-dronestream.git"
@@ -18,8 +18,8 @@
],
"dependencies": {
"ws": "~0.4.22",
"ar-drone": "0.0.3",
"buffy": "0.0.4"
"ar-drone": "0.0.5",
"buffy": "0.0.5"
},
"author": "Bernhard K. Weisshuhn <bkw@codingforce.com>",
"contributors": [