17 Commits

Autor SHA1 Mensagem Data
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
Bernhard K. Weisshuhn 7606a9f35c it's one point ohhhhh, baby! 2012-12-15 16:13:44 +01:00
Bernhard K. Weisshuhn e5a5960106 add @karlwestin as a contributor 2012-12-15 16:13:09 +01:00
Bernhard Weißhuhn 73e3b48356 Merge pull request #3 from karlwestin/modulize
Modulize
2012-12-15 04:36:38 -08:00
Bernhard Weißhuhn 91f60fc341 Update README.md
praise @karlwestin
2012-12-12 12:33:39 +01:00
Karl Westin 94dce52391 Update README.md
todo: fix the readme
2012-12-09 09:26:56 -08:00
Karl Westin 4feaaddbe7 Added a normal http.createServer example 2012-12-09 09:25:12 -08:00
Karl Westin 2c0641547e Adding a really stupid buildscript for the client, feel free to improve
:)
Also, removing html5 boilerplate from the example
2012-12-09 09:02:48 -08:00
Karl Westin 9787c83ad0 Added a package.json for the example
Bumped version no
2012-12-09 08:44:00 -08:00
Karl Westin cb6b2014a4 DS_Store to effin gitignore 2012-12-09 08:27:29 -08:00
Karl Westin 35e84bb038 Modularize, to make it easier to use in other apps
Howto:
// pass in a node http server object:
require("node-dronestream").listen(server);
// attach the 5 js files. Yes let's make a build some time
2012-12-09 08:26:06 -08:00
Karl Westin 808079f780 In between, working on making a module of the stream 2012-12-09 07:10:34 -08:00
34 arquivos alterados com 1426 adições e 311 exclusões
+1
Ver Arquivo
@@ -1,2 +1,3 @@
node_modules
.*.swp
.DS_Store
+26
Ver Arquivo
@@ -0,0 +1,26 @@
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
+30 -4
Ver Arquivo
@@ -10,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
@@ -22,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
@@ -45,3 +65,9 @@ is enough to be used as a starting point for your own integration.
- Brian Leroux for being not content with the original solution and for
cleaning up the predecessor, nodecopter-stream.
- @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
-82
Ver Arquivo
@@ -1,82 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes')
, http = require('http')
, path = require('path')
, app = express()
, server = http.createServer(app)
, WebSocketServer = require('ws').Server
, wss = new WebSocketServer({server: server})
, sockets = []
, Parser = require('./lib/PaVEParser')
, arDrone = require('ar-drone')
;
function init() {
var tcpVideoStream = new arDrone.Client.PngStream.TcpVideoStream({timeout: 4000})
, p = new Parser();
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});
});
});
}
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;
});
});
});
app.configure(function () {
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade', { pretty: true });
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
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);
if (module.parent) {
module.exports = server;
} else {
server.listen(app.get('port'), function () {
console.log("Express server listening on port " + app.get('port'));
});
}
Arquivo executável
+3
Ver Arquivo
@@ -0,0 +1,3 @@
#!/bin/bash
cat dist/vendor/broadway/sylvester.js dist/vendor/broadway/glUtils.js dist/vendor/broadway/util.js dist/vendor/broadway/avc-codec.js dist/vendor/broadway/avc.js dist/vendor/broadway/canvas.js dist/nodecopter-stream.js > dist/nodecopter-client.js
+1162
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+4 -25
Ver Arquivo
@@ -5,9 +5,7 @@
var NS,
socket,
avc,
webGLCanvas,
width,
height;
webGLCanvas;
function setupAvc() {
avc = new Avc();
@@ -34,10 +32,9 @@
}
function setupCanvas(div) {
var canvas = document.createElement('canvas');
width = div.attributes.width ? div.attributes.width.value : 640;
height = div.attributes.height ? div.attributes.height.value : 360;
var width = div.attributes.width ? div.attributes.width.value : 640,
height = div.attributes.height ? div.attributes.height.value : 360,
canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
@@ -61,24 +58,6 @@
socket.onmessage = handleNalUnits;
};
NS.prototype.getImageData = function (rgbaData) {
var gl = webGLCanvas.gl;
gl.readPixels(
0, 0, width, height,
gl.RGBA, gl.UNSIGNED_BYTE,
rgbaData
);
// WebGL returns pixels upside down.
// Instead of wasting time by vertically flipping it now,
// we just leave it like it is and invert the coordinates later:
return;
};
NS.prototype.getCanvas = function () {
return webGLCanvas.canvas;
};
window.NodecopterStream = NS;
}(window, document, undefined));
@@ -337,7 +337,7 @@ var WebGLCanvas = (function () {
},
onInitWebGL: function () {
try {
this.gl = this.canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
this.gl = this.canvas.getContext("experimental-webgl");
} catch(e) {}
if (!this.gl) {
+10
Ver Arquivo
@@ -0,0 +1,10 @@
var http = require("http"),
drone = require("../../index");
var server = http.createServer(function(req, res) {
require("fs").createReadStream(__dirname + "/index.html").pipe(res);
});
drone.listen(server);
server.listen(5555);
+16
Ver Arquivo
@@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Stream as module</title>
<script src="/dronestream/nodecopter-client.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<h1 id="heading">Stream through a normal require("http").createServer</h1>
<div id="droneStream" style="width: 640px; height: 360px"> </div>
<script type="text/javascript" charset="utf-8">
new NodecopterStream(document.getElementById("droneStream"));
</script>
</body>
</html>
+33
Ver Arquivo
@@ -0,0 +1,33 @@
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("../../index").listen(server);
server.listen(3000);
+13
Ver Arquivo
@@ -0,0 +1,13 @@
{
"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"
}
+22
Ver Arquivo
@@ -0,0 +1,22 @@
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)
@@ -7,7 +7,6 @@ html
title= title
link(rel='stylesheet', href='/css/normalize.min.css')
link(rel='stylesheet', href='/css/style.css')
script(src='/js/vendor/h5bp/modernizr-2.6.1-respond-1.1.0.min.js')
body
div.header-container
header.wrapper.clearfix
+1
Ver Arquivo
@@ -0,0 +1 @@
module.exports = require("./lib/server");
+49
Ver Arquivo
@@ -0,0 +1,49 @@
/*
* 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"
;
module.exports.listen = function listen(server) {
if(typeof server == "number") {
var port = server;
server = require("http").createServer();
server.listen(port);
}
/*
* Serving up the static files needed
*/
var oldHandlers = server.listeners("request").splice(0);
server.on("request", function(req, res) {
if(handler(req, res)) {
return;
}
for(var i = 0; i < oldHandlers.length; i++) {
oldHandlers[i].call(server, req, res);
}
});
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); });
return true;
}
/*
* Connecting stream + websocket server
*/
return require("./stream").attach(server);
};
+47
Ver Arquivo
@@ -0,0 +1,47 @@
/*
* 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')
;
function init() {
var tcpVideoStream = new arDrone.Client.PngStream.TcpVideoStream({timeout: 4000})
, p = new Parser();
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});
});
});
}
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;
});
});
});
};
+8 -6
Ver Arquivo
@@ -1,11 +1,12 @@
{
"name": "dronestream",
"description": "video live stream from your parrot ar.drone 2.0 to your browser in pure javascript",
"version": "0.2.0",
"version": "1.0.1",
"repository": {
"type": "git",
"url": "git@github.com:bkw/node-dronestream.git"
},
"main": "index",
"keywords": [
"drone",
"nodecopter",
@@ -15,16 +16,17 @@
"browser",
"x264"
],
"scripts": {
"start": "node app"
},
"dependencies": {
"express": "3.0.0rc5",
"jade": "*",
"ws": "~0.4.22",
"ar-drone": "0.0.3",
"buffy": "0.0.4"
},
"author": "Bernhard K. Weisshuhn <bkw@codingforce.com>",
"contributors": [
{
"name": "Karl Westin",
"email": "karl.westin@gmail.com"
}
],
"license": "BSD"
}
Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 5.5 KiB

-156
Ver Arquivo
@@ -1,156 +0,0 @@
/*jshint browser:true */
/*global jsfeat:true console:true */
(function (window, document, undefined) {
'use strict';
var NodecopterTrack,
lastTime;
function schedule (callback, element) {
var requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
var currTime = new Date().getTime(),
timeToCall = Math.max(0, 16 - (currTime - lastTime)),
id = window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
return requestAnimationFrame.call(window, callback, element);
}
var relMouseCoords = function (event) {
var totalOffsetX = 0,
totalOffsetY = 0,
canvasX = 0,
canvasY = 0,
currentElement = this;
do {
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
} while (currentElement = currentElement.offsetParent);
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {x:canvasX, y:canvasY};
};
NodecopterTrack = function (copterStream, imgId) {
var tracker = this;
this.curr_img_pyr = new jsfeat.pyramid_t(3);
this.prev_img_pyr = new jsfeat.pyramid_t(3);
this.point_count = 0;
this.point_status = new Uint8Array(1);
this.prev_xy = new Float32Array(2);
this.curr_xy = new Float32Array(2);
this.copterStream = copterStream;
this.canvas = copterStream.getCanvas();
this.rgbaData = new Uint8Array(
this.canvas.width * this.canvas.height * 4
); // RGBA
this.crosshairs = document.querySelector(imgId);
this.curr_img_pyr.allocate(
this.canvas.width, this.canvas.height, jsfeat.U8_t | jsfeat.C1_t
);
this.prev_img_pyr.allocate(
this.canvas.width, this.canvas.height, jsfeat.U8_t | jsfeat.C1_t
);
this.canvas.addEventListener('click', function(event) {
tracker.canvasClickHandler(event);
}, false);
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
// this.canvas.prototype.relMouseCoords = relMouseCoords;
this.update();
};
NodecopterTrack.prototype.update = function () {
var _pt_xy, _pyr,
tracker = this;
schedule(function () {
tracker.update();
});
if (! this.point_count) {
this.crosshairs.style.display = 'none';
return;
}
_pt_xy = this.prev_xy;
_pyr = this.prev_img_pyr;
this.prev_xy = this.curr_xy;
this.curr_xy = _pt_xy;
this.prev_img_pyr = this.curr_img_pyr;
this.curr_img_pyr = _pyr; // reuse old pyramid data structure
this.copterStream.getImageData(this.rgbaData);
jsfeat.imgproc.grayscale(
this.rgbaData,
this.curr_img_pyr.data[0].data
);
// optional: enhance contrast:
jsfeat.imgproc.equalize_histogram(
this.curr_img_pyr.data[0].data,
this.curr_img_pyr.data[0].data
);
this.curr_img_pyr.build(this.curr_img_pyr.data[0], true);
jsfeat.optical_flow_lk.track(
this.prev_img_pyr,
this.curr_img_pyr,
this.prev_xy,
this.curr_xy,
1,
50, // win_size
30, // max_iterations
this.point_status,
0.01, // epsilon,
0.001 // min_eigen
);
if (this.point_status[0] == 1) {
this.crosshairs.style.left = (this.curr_xy[0] - 83) + 'px';
this.crosshairs.style.top = (
this.canvas.height - 83 - this.curr_xy[1]
) + 'px';
this.crosshairs.style.display = 'block';
} else {
this.point_count = 0;
console.log('lost target');
}
};
NodecopterTrack.prototype.canvasClickHandler = function (e) {
var coords = this.canvas.relMouseCoords(e);
if (
(coords.x > 0) &&
(coords.y > 0) &&
(coords.x < this.canvas.width) &&
(coords.y < this.canvas.height)
) {
this.curr_xy[0] = coords.x;
this.curr_xy[1] = this.canvas.height - coords.y;
this.point_count = 1;
}
console.log('Click:', coords);
};
window.NodecopterTrack = NodecopterTrack;
}(window, document, undefined));
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
-22
Ver Arquivo
@@ -1,22 +0,0 @@
extends layout
block append head
script(type='text/javascript', src='/js/vendor/broadway/sylvester.js')
script(type='text/javascript', src='/js/vendor/broadway/glUtils.js')
script(type='text/javascript', src='/js/vendor/broadway/util.js')
script(type='text/javascript', src='/js/vendor/broadway/avc-codec.js')
script(type='text/javascript', src='/js/vendor/broadway/avc.js')
script(type='text/javascript', src='/js/vendor/broadway/canvas.js')
script(type='text/javascript', src='/js/vendor/jsfeat/jsfeat-min.js')
script(type='text/javascript', src='/js/nodecopter-stream.js')
script(type='text/javascript', src='/js/nodecopter-track.js')
block append bodyscripts
script
'use strict';
var copterStream = new NodecopterStream(document.querySelector('#dronestream'));
var tracker = new NodecopterTrack(copterStream, '#sniper');
block content
div#dronestream(width=640, height=360, style="position:relative")
img#sniper(src="images/sniper.png", style="position:absolute; opacity:0.6")