54 Commits

Autor SHA1 Mensagem Data
Bernhard K. Weisshuhn 40b51e056c update change log 2013-08-31 18:07:47 +02:00
Bernhard K. Weisshuhn fc8754dc3e bump version to 1.1.1 2013-08-31 18:05:11 +02:00
Bernhard K. Weisshuhn 309376797d new build 2013-08-31 18:04:50 +02:00
Bernhard K. Weisshuhn 6397b10149 update broadway avc.js to cleaned up version 2013-08-31 18:04:39 +02:00
Bernhard K. Weisshuhn 6b0f2e374a bump ar-drone version to 0.2.0 2013-08-31 18:04:07 +02:00
Bernhard K. Weisshuhn 5cb04779a7 bump package version 2013-08-07 14:06:33 +02:00
Bernhard K. Weisshuhn 524f7ed21e update History 2013-08-07 14:06:33 +02:00
Bernhard K. Weisshuhn 47c3e2c57f bump ardrone dependency 2013-08-07 14:06:33 +02:00
Bernhard K. Weisshuhn 2064227220 bump ws dependency 2013-08-07 14:06:33 +02:00
Bernhard K. Weisshuhn bfd7471b87 add onNextFrame() for tracking post processors 2013-08-07 14:06:32 +02:00
Bernhard K. Weisshuhn feca81242a remove old build script in favour of grunt 2013-08-07 14:06:32 +02:00
Bernhard K. Weisshuhn 6625c22291 add grunt for building 2013-08-07 14:04:32 +02:00
Bernhard K. Weisshuhn 1832c0296b make jshint happy 2013-08-07 14:04:32 +02:00
Bernhard K. Weisshuhn b57cea6d40 rearrange hostname check for jshint 2013-08-07 14:03:23 +02:00
Bernhard K. Weisshuhn 14c874c08d that comma should be a semicolon 2013-08-07 14:00:43 +02:00
Bernhard K. Weisshuhn 4b30275f4e use explicit text only mode for script tag in example 2013-08-07 14:00:43 +02:00
Bernhard K. Weisshuhn 6edf209575 move const to var for strict mode 2013-08-07 14:00:43 +02:00
Bernhard K. Weisshuhn 6c89f533f1 remove superfluous es5 option from jshintrc 2013-08-07 14:00:43 +02:00
Bernhard K. Weisshuhn f396d4caee clean up whitespace 2013-08-07 14:00:40 +02:00
Bernhard Weisshuhn (a.k.a. bernhorst) 550a1b63d9 Merge pull request #7 from eschnou/optional_tcpvideostream
Support for passing an existing tcpVideoStream
2013-08-07 01:09:07 -07:00
Laurent Eschenauer 499d8737e4 Support for passing an existing tcpVideoStream
Since the AR Drone supports only a single connection to the video
stream, it is sometimes required to re-use the same stream accross an
application.
2013-05-26 23:31:09 +02:00
Bernhard Weisshuhn (a.k.a. bernhorst) 3d05582d2f Merge pull request #6 from eschnou/add_client_options
Add support for custom host/port
2013-05-26 14:29:09 -07:00
Laurent Eschenauer 4310a9308f Add support for custom host/port 2013-05-25 11:54:16 +02:00
Karl Westin 3c0abd5f16 Bump package version 2013-03-19 08:49:31 +01:00
Karl Westin 57fade81c7 Make sure to 'clean up' server, so we can deal correctly with static
files
2013-03-18 10:01:56 +01:00
Karl Westin cec02b0ca9 Added options, for example to set up the drone on a different IP 2013-03-18 09:52:41 +01:00
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
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 820 adições e 472 exclusões
+2
Ver Arquivo
@@ -1,2 +1,4 @@
node_modules
dist/broadway.js
.*.swp
.DS_Store
+30
Ver Arquivo
@@ -0,0 +1,30 @@
{
"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
}
+49
Ver Arquivo
@@ -0,0 +1,49 @@
module.exports = function (grunt) {
'use strict';
// load all grunt tasks
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
options: {
jshintrc: '.jshintrc'
},
all: [
'Gruntfile.js',
'lib/{,*/}*.js',
'dist/nodecopter-stream.js'
]
},
uglify: {
dist: {
files: {
'dist/broadway.js' : [
'dist/vendor/broadway/glUtils.js',
'dist/vendor/broadway/util.js',
'dist/vendor/broadway/avc.js',
'dist/vendor/broadway/canvas.js',
'dist/nodecopter-stream.js'
]
}
}
},
concat: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
},
dist: {
src: [
'dist/vendor/broadway/sylvester.js',
'dist/vendor/broadway/avc-codec.js',
'dist/broadway.js'
],
dest: 'dist/nodecopter-client.js'
}
}
});
grunt.registerTask('default', ['jshint', 'uglify', 'concat']);
};
+57
Ver Arquivo
@@ -0,0 +1,57 @@
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
1.0.3 / 2013-03-19
==================
* add custom drone ip to server options
* fix static files on webserver
1.1.0 / 2013-08-07
==================
* add support for custom host and port to client
* support passing an existing video stream
* switch to grunt for building
* express fixes
* remove constants for strict mode
* bump ws and ar-drone dependencies
* add hook for postprocessors
1.1.1 / 2013-08-31
==================
* use cleaned up version of broadways avc.js, fixes errors about 'clip'
* switch to ardrone 0.2.0
+32 -4
Ver Arquivo
@@ -10,6 +10,29 @@ 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);
// if your drone is on a different IP
require("dronestream").listen(server, { ip: "192.168.2.155" });
```
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 +45,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 +67,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'));
});
}
+5
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+129
Ver Arquivo
@@ -0,0 +1,129 @@
/*jshint browser: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,
socket,
avc,
webGLCanvas,
width,
height,
callbackOnce = null;
function setupAvc() {
avc = new Avc();
avc.configure({
filter: 'original',
filterHorLuma: 'optimized',
filterVerLumaEdge: 'optimized',
getBoundaryStrengthsA: 'optimized'
});
avc.onPictureDecoded = handleDecodedFrame;
}
function handleNalUnits(message) {
avc.decode(new Uint8Array(message.data));
}
function handleDecodedFrame(buffer, bufWidth, bufHeight) {
var callback;
requestAnimationFrame(function () {
var lumaSize = bufWidth * bufHeight,
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();
});
// call callback with Y portion (grayscale image)
if (null !== callbackOnce && width) {
callback = callbackOnce;
callbackOnce = null;
// decoded buffer size may be larger,
// so use subarray with actual dimensions
callback(buffer.subarray(0, width * height));
}
}
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;
canvas.width = width;
canvas.height = height;
canvas.style.backgroundColor = "#333333";
div.appendChild(canvas);
webGLCanvas = new YUVWebGLCanvas(canvas, new Size(width, height));
}
NS = function (div, options) {
var hostname, port;
options = options || {};
hostname = options.hostname || window.document.location.hostname;
port = options.port || window.document.location.port;
setupCanvas(div);
setupAvc();
socket = new WebSocket(
'ws://' + hostname + ':' + port + '/dronestream'
);
socket.binaryType = 'arraybuffer';
socket.onmessage = handleNalUnits;
};
// enqueue callback oto be called with next (black&white) frame
NS.prototype.onNextFrame = function (callback) {
callbackOnce = callback;
};
window.NodecopterStream = NS;
}(window, document, undefined));
+285
Ver Arquivo
@@ -0,0 +1,285 @@
/**
* Requires: avc-codec.js
**/
assert (Module);
var Avc = (function avc() {
'use strict';
var MAX_STREAM_BUFFER_LENGTH = 1024 * 1024,
HEAP8 = Module.HEAP8,
HEAPU8 = Module.HEAPU8,
HEAP16 = Module.HEAP16,
HEAP32 = Module.HEAP32,
_h264bsdClip = Module._get_h264bsdClip(),
patches = {
"filter": {
name: "_h264bsdFilterPicture",
display: "Filter Picture",
original: "Original_h264bsdFilterPicture",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
}
},
"filterHorLuma": {
name: "_FilterHorLuma",
display: "Filter Hor Luma",
original: "OriginalFilterHorLuma",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
optimized: {display: "Optimized", fn: OptimizedFilterHorLuma}
}
},
"filterVerLumaEdge": {
name: "_FilterVerLumaEdge",
display: "Filter Ver Luma Edge",
original: "OriginalFilterVerLumaEdge",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
optimized: {display: "Optimized", fn: OptimizedFilterVerLumaEdge}
}
},
"getBoundaryStrengthsA": {
name: "_GetBoundaryStrengthsA",
display: "Get Boundary Strengths",
original: "OriginalGetBoundaryStrengthsA",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
optimized: {display: "Optimized", fn: OptimizedGetBoundaryStrengthsA}
}
}
},
origFn = {};
function clip(x, y, z) {
return z < x ? x : (z > y ? y : z);
}
function OptimizedGetBoundaryStrengthsA($mb, $bS) {
var $totalCoeff = $mb + 28;
var tc0 = HEAP16[$totalCoeff + 0 >> 1];
var tc1 = HEAP16[$totalCoeff + 2 >> 1];
var tc2 = HEAP16[$totalCoeff + 4 >> 1];
var tc3 = HEAP16[$totalCoeff + 6 >> 1];
var tc4 = HEAP16[$totalCoeff + 8 >> 1];
var tc5 = HEAP16[$totalCoeff + 10 >> 1];
var tc6 = HEAP16[$totalCoeff + 12 >> 1];
var tc7 = HEAP16[$totalCoeff + 14 >> 1];
var tc8 = HEAP16[$totalCoeff + 16 >> 1];
var tc9 = HEAP16[$totalCoeff + 18 >> 1];
var tc10 = HEAP16[$totalCoeff + 20 >> 1];
var tc11 = HEAP16[$totalCoeff + 22 >> 1];
var tc12 = HEAP16[$totalCoeff + 24 >> 1];
var tc13 = HEAP16[$totalCoeff + 26 >> 1];
var tc14 = HEAP16[$totalCoeff + 28 >> 1];
var tc15 = HEAP16[$totalCoeff + 30 >> 1];
HEAP32[$bS + 32 >> 2] = tc2 || tc0 ? 2 : 0;
HEAP32[$bS + 40 >> 2] = tc3 || tc1 ? 2 : 0;
HEAP32[$bS + 48 >> 2] = tc6 || tc4 ? 2 : 0;
HEAP32[$bS + 56 >> 2] = tc7 || tc5 ? 2 : 0;
HEAP32[$bS + 64 >> 2] = tc8 || tc2 ? 2 : 0;
HEAP32[$bS + 72 >> 2] = tc9 || tc3 ? 2 : 0;
HEAP32[$bS + 80 >> 2] = tc12 || tc6 ? 2 : 0;
HEAP32[$bS + 88 >> 2] = tc13 || tc7 ? 2 : 0;
HEAP32[$bS + 96 >> 2] = tc10 || tc8 ? 2 : 0;
HEAP32[$bS + 104 >> 2] = tc11 || tc9 ? 2 : 0;
HEAP32[$bS + 112 >> 2] = tc14 || tc12 ? 2 : 0;
HEAP32[$bS + 120 >> 2] = tc15 || tc13 ? 2 : 0;
HEAP32[$bS + 12 >> 2] = tc1 || tc0 ? 2 : 0;
HEAP32[$bS + 20 >> 2] = tc4 || tc1 ? 2 : 0;
HEAP32[$bS + 28 >> 2] = tc5 || tc4 ? 2 : 0;
HEAP32[$bS + 44 >> 2] = tc3 || tc2 ? 2 : 0;
HEAP32[$bS + 52 >> 2] = tc6 || tc3 ? 2 : 0;
HEAP32[$bS + 60 >> 2] = tc7 || tc6 ? 2 : 0;
HEAP32[$bS + 76 >> 2] = tc9 || tc8 ? 2 : 0;
HEAP32[$bS + 84 >> 2] = tc12 || tc9 ? 2 : 0;
HEAP32[$bS + 92 >> 2] = tc13 || tc12 ? 2 : 0;
HEAP32[$bS + 108 >> 2] = tc11 || tc10 ? 2 : 0;
HEAP32[$bS + 116 >> 2] = tc14 || tc11 ? 2 : 0;
HEAP32[$bS + 124 >> 2] = tc15 || tc14 ? 2 : 0;
}
function OptimizedFilterVerLumaEdge ($data, bS, $thresholds, imageWidth) {
var delta, tc, tmp;
var p0, q0, p1, q1, p2, q2;
var tmpFlag;
var $clp = _h264bsdClip + 512;
var alpha = HEAP32[$thresholds + 4 >> 2];
var beta = HEAP32[$thresholds + 8 >> 2];
var val;
if (bS < 4) {
tmp = tc = HEAPU8[HEAP32[$thresholds >> 2] + (bS - 1)] & 255;
for (var i = 4; i > 0; i--) {
p1 = HEAPU8[$data + -2] & 255;
p0 = HEAPU8[$data + -1] & 255;
q0 = HEAPU8[$data] & 255;
q1 = HEAPU8[$data + 1] & 255;
if (
(Math.abs(p0 - q0) < alpha) &&
(Math.abs(p1 - p0) < beta) &&
(Math.abs(q1 - q0) < beta)
) {
p2 = HEAPU8[$data - 3] & 255;
if (Math.abs(p2 - p0) < beta) {
val = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1;
HEAP8[$data - 2] = p1 + clip(-tc, tc, val);
tmp++;
}
q2 = HEAPU8[$data + 2] & 255;
if (Math.abs(q2 - q0) < beta) {
val = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1;
HEAP8[$data + 1] = (q1 + clip(-tc, tc, val));
tmp++;
}
val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
delta = clip(-tmp, tmp, val);
p0 = HEAPU8[$clp + (p0 + delta)] & 255;
q0 = HEAPU8[$clp + (q0 - delta)] & 255;
tmp = tc;
HEAP8[$data - 1] = p0;
HEAP8[$data] = q0;
$data += imageWidth;
}
}
} else {
origFn.OriginalFilterVerLumaEdge($data, bS, $thresholds, imageWidth);
}
}
/**
* Filter all four successive horizontal 4-pixel luma edges. This can be
* done when bS is equal to all four edges.
*/
function OptimizedFilterHorLuma ($data, bS, $thresholds, imageWidth) {
var delta, tc, tmp;
var p0, q0, p1, q1, p2, q2;
var tmpFlag;
var $clp = _h264bsdClip + 512;
var alpha = HEAP32[$thresholds + 4 >> 2];
var beta = HEAP32[$thresholds + 8 >> 2];
var val;
if (bS < 4) {
tmp = tc = HEAPU8[HEAP32[$thresholds >> 2] + (bS - 1)] & 255;
for (var i = 16; i > 0; i--) {
p1 = HEAPU8[$data + (-imageWidth << 1)] & 255;
p0 = HEAPU8[$data + -imageWidth] & 255;
q0 = HEAPU8[$data] & 255;
q1 = HEAPU8[$data + imageWidth] & 255;
if (
(Math.abs(p0 - q0) < alpha) &&
(Math.abs(p1 - p0) < beta) &&
(Math.abs(q1 - q0) < beta)
) {
p2 = HEAPU8[$data + (-imageWidth * 3)] & 255;
if (Math.abs(p2 - p0) < beta) {
val = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1;
HEAP8[$data + (-imageWidth << 1)] = p1 + clip(-tc, tc, val);
tmp++;
}
q2 = HEAPU8[$data + (imageWidth << 2)] & 255;
if (Math.abs(q2 - q0) < beta) {
val = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1;
HEAP8[$data + imageWidth] = (q1 + clip(-tc, tc, val));
tmp++;
}
val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
delta = clip(-tmp, tmp, val);
p0 = HEAPU8[$clp + (p0 + delta)] & 255;
q0 = HEAPU8[$clp + (q0 - delta)] & 255;
tmp = tc;
HEAP8[$data - imageWidth] = p0;
HEAP8[$data] = q0;
$data ++;
}
}
} else {
origFn.OriginalFilterHorLuma($data, bS, $thresholds, imageWidth);
}
}
function patchOptimizations(config, patches) {
for (var name in patches) {
var patch = patches[name];
if (patch) {
var option = config[name];
if (!option) option = "original";
console.info(name + ": " + option);
assert (option in patch.options);
var fn = patch.options[option].fn;
if (fn) {
origFn[patch.original] = Module.patch(null, patch.name, fn);
console.info("Patching: " + patch.name + ", with: " + option);
}
}
}
}
function constructor() {
Module._broadwayInit();
this.streamBuffer = toU8Array(Module._broadwayCreateStream(MAX_STREAM_BUFFER_LENGTH), MAX_STREAM_BUFFER_LENGTH);
this.pictureBuffers = {};
this.onPictureDecoded = function (buffer, width, height) {
// console.info(buffer.length);
};
Module.patch(null, "_broadwayOnHeadersDecoded", function (x,y,z) {});
Module.patch(null, "_broadwayOnPictureDecoded", function ($buffer, width, height) {
var buffer = this.pictureBuffers[$buffer];
if (!buffer) {
buffer = this.pictureBuffers[$buffer] = toU8Array($buffer, (width * height * 3) / 2);
}
this.onPictureDecoded(buffer, width, height);
}.bind(this));
}
/**
* Creates a typed array from a HEAP8 pointer.
*/
function toU8Array(ptr, length) {
return HEAPU8.subarray(ptr, ptr + length);
}
constructor.prototype = {
/**
* Decodes a stream buffer. This may be one single (unframed) NAL unit
* without the start code, or a sequence of NAL units with framing start
* code prefixes. This function overwrites stream buffer allocated by
* the codec with the supplied buffer.
*/
decode: function decode(buffer) {
// console.info("Decoding: " + buffer.length);
this.streamBuffer.set(buffer);
Module._broadwaySetStreamLength(buffer.length);
Module._broadwayPlayStream();
},
configure: function (config) {
patchOptimizations(config, patches);
console.info("Broadway Configured: " + JSON.stringify(config));
}
};
return constructor;
})();
+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("dronestream").listen(server);
require("../../index").listen(server);
server.listen(3000);
+13
Ver Arquivo
@@ -0,0 +1,13 @@
{
"name": "dronestream-example",
"version": "0.1.1",
"dependencies": {
"express": "3.0.x",
"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");
+56
Ver Arquivo
@@ -0,0 +1,56 @@
/*
* 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, options) {
'use strict';
var port, oldHandlers;
if (typeof server === 'number') {
port = server;
server = require('http').createServer();
server.listen(port);
}
/*
* Serving up the static files needed
*/
oldHandlers = server.listeners('request').splice(0);
server.removeAllListeners('request');
server.on('request', function (req, res) {
var i = 0;
if (handler(req, res)) {
return;
}
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, options);
};
+62
Ver Arquivo
@@ -0,0 +1,62 @@
/*jshint node:true*/
/*
* Sets up a real stream + attaches it to a server
*/
module.exports.attach = function droneStream(server, options) {
'use strict';
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({server: server, path: '/dronestream'}),
sockets = [],
Parser = require('./PaVEParser'),
arDrone = require('ar-drone');
options = options || {};
options.timeout = options.timeout || 4000;
function init() {
var tcpVideoStream, parser;
if (!options.tcpVideoStream) {
tcpVideoStream = new arDrone.Client.PngStream.TcpVideoStream(
options
);
console.log(
"Connecting to drone on %s", options.ip || "192.168.1.1"
);
tcpVideoStream.connect();
tcpVideoStream.on('error', function (err) {
console.log('There was an error: %s', err.message);
tcpVideoStream.end();
tcpVideoStream.emit("end");
init();
});
} else {
tcpVideoStream = options.tcpVideoStream;
}
parser = new Parser();
tcpVideoStream.on('data', function (data) {
parser.write(data);
});
parser.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;
});
});
});
};
+18 -9
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.1.1",
"repository": {
"type": "git",
"url": "git@github.com:bkw/node-dronestream.git"
},
"main": "index",
"keywords": [
"drone",
"nodecopter",
@@ -15,16 +16,24 @@
"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"
"ws": "~0.4.27",
"ar-drone": "0.2.0",
"buffy": "0.0.5"
},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-uglify": "~0.2.2",
"grunt-contrib-jshint": "~0.6.2",
"matchdep": "~0.1.2",
"grunt-contrib-concat": "~0.3.0"
},
"author": "Bernhard K. Weisshuhn <bkw@codingforce.com>",
"contributors": [
{
"name": "Karl Westin",
"email": "karl.westin@gmail.com"
}
],
"license": "BSD"
}
-63
Ver Arquivo
@@ -1,63 +0,0 @@
/*jshint browser:true */
/*global Avc:true, YUVWebGLCanvas: true, Size: true */
(function (window, document, undefined) {
'use strict';
var NS,
socket,
avc,
webGLCanvas;
function setupAvc() {
avc = new Avc();
avc.configure({
filter: 'original',
filterHorLuma: 'optimized',
filterVerLumaEdge: 'optimized',
getBoundaryStrengthsA: 'optimized'
});
avc.onPictureDecoded = handleDecodedFrame;
}
function handleNalUnits(message) {
avc.decode(new Uint8Array(message.data));
}
function handleDecodedFrame(buffer, width, height) {
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();
}
function setupCanvas(div) {
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;
canvas.style.backgroundColor = "#333333";
div.appendChild(canvas);
webGLCanvas = new YUVWebGLCanvas(canvas, new Size(width, height));
}
NS = function (div) {
setupCanvas(div);
setupAvc();
socket = new WebSocket(
'ws://' +
window.document.location.hostname + ':' +
window.document.location.port
);
socket.binaryType = 'arraybuffer';
socket.onmessage = handleNalUnits;
};
window.NodecopterStream = NS;
}(window, document, undefined));
-283
Ver Arquivo
@@ -1,283 +0,0 @@
/**
* Requires: avc-codec.js
**/
assert (Module);
HEAP8 = Module.HEAP8;
HEAPU8 = Module.HEAPU8;
HEAP16 = Module.HEAP16;
HEAP32 = Module.HEAP32;
_h264bsdClip = Module._get_h264bsdClip();
var Avc = (function avc() {
const MAX_STREAM_BUFFER_LENGTH = 1024 * 1024;
function constructor() {
Module._broadwayInit();
this.streamBuffer = toU8Array(Module._broadwayCreateStream(MAX_STREAM_BUFFER_LENGTH), MAX_STREAM_BUFFER_LENGTH);
this.pictureBuffers = {};
this.onPictureDecoded = function (buffer, width, height) {
// console.info(buffer.length);
}
Module.patch(null, "_broadwayOnHeadersDecoded", function () {
});
Module.patch(null, "_broadwayOnPictureDecoded", function ($buffer, width, height) {
var buffer = this.pictureBuffers[$buffer];
if (!buffer) {
buffer = this.pictureBuffers[$buffer] = toU8Array($buffer, (width * height * 3) / 2);
}
this.onPictureDecoded(buffer, width, height);
}.bind(this));
}
/**
* Creates a typed array from a HEAP8 pointer.
*/
function toU8Array(ptr, length) {
return HEAPU8.subarray(ptr, ptr + length);
}
constructor.prototype = {
/**
* Decodes a stream buffer. This may be one single (unframed) NAL unit without the
* start code, or a sequence of NAL units with framing start code prefixes. This
* function overwrites stream buffer allocated by the codec with the supplied buffer.
*/
decode: function decode(buffer) {
// console.info("Decoding: " + buffer.length);
this.streamBuffer.set(buffer);
Module._broadwaySetStreamLength(buffer.length);
Module._broadwayPlayStream();
},
configure: function (config) {
patchOptimizations(config, patches);
console.info("Broadway Configured: " + JSON.stringify(config));
}
};
return constructor;
})();
function patchOptimizations(config, patches) {
var scope = getGlobalScope();
for (var name in patches) {
var patch = patches[name];
if (patch) {
var option = config[name];
if (!option) option = "original";
console.info(name + ": " + option);
assert (option in patch.options);
var fn = patch.options[option].fn;
if (fn) {
scope[patch.original] = Module.patch(null, patch.name, fn);
console.info("Patching: " + patch.name + ", with: " + option);
}
}
}
}
var patches = {
"filter": {
name: "_h264bsdFilterPicture",
display: "Filter Picture",
original: "Original_h264bsdFilterPicture",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
}
},
"filterHorLuma": {
name: "_FilterHorLuma",
display: "Filter Hor Luma",
original: "OriginalFilterHorLuma",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
optimized: {display: "Optimized", fn: OptimizedFilterHorLuma}
}
},
"filterVerLumaEdge": {
name: "_FilterVerLumaEdge",
display: "Filter Ver Luma Edge",
original: "OriginalFilterVerLumaEdge",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
optimized: {display: "Optimized", fn: OptimizedFilterVerLumaEdge}
}
},
"getBoundaryStrengthsA": {
name: "_GetBoundaryStrengthsA",
display: "Get Boundary Strengths",
original: "OriginalGetBoundaryStrengthsA",
options: {
none: {display: "None", fn: function () {}},
original: {display: "Original", fn: null},
optimized: {display: "Optimized", fn: OptimizedGetBoundaryStrengthsA}
}
}
};
function getGlobalScope() {
return function () { return this; }.call(null);
}
/* Optimizations */
function clip(x, y, z) {
return z < x ? x : (z > y ? y : z);
}
function OptimizedGetBoundaryStrengthsA($mb, $bS) {
var $totalCoeff = $mb + 28;
var tc0 = HEAP16[$totalCoeff + 0 >> 1];
var tc1 = HEAP16[$totalCoeff + 2 >> 1];
var tc2 = HEAP16[$totalCoeff + 4 >> 1];
var tc3 = HEAP16[$totalCoeff + 6 >> 1];
var tc4 = HEAP16[$totalCoeff + 8 >> 1];
var tc5 = HEAP16[$totalCoeff + 10 >> 1];
var tc6 = HEAP16[$totalCoeff + 12 >> 1];
var tc7 = HEAP16[$totalCoeff + 14 >> 1];
var tc8 = HEAP16[$totalCoeff + 16 >> 1];
var tc9 = HEAP16[$totalCoeff + 18 >> 1];
var tc10 = HEAP16[$totalCoeff + 20 >> 1];
var tc11 = HEAP16[$totalCoeff + 22 >> 1];
var tc12 = HEAP16[$totalCoeff + 24 >> 1];
var tc13 = HEAP16[$totalCoeff + 26 >> 1];
var tc14 = HEAP16[$totalCoeff + 28 >> 1];
var tc15 = HEAP16[$totalCoeff + 30 >> 1];
HEAP32[$bS + 32 >> 2] = tc2 || tc0 ? 2 : 0;
HEAP32[$bS + 40 >> 2] = tc3 || tc1 ? 2 : 0;
HEAP32[$bS + 48 >> 2] = tc6 || tc4 ? 2 : 0;
HEAP32[$bS + 56 >> 2] = tc7 || tc5 ? 2 : 0;
HEAP32[$bS + 64 >> 2] = tc8 || tc2 ? 2 : 0;
HEAP32[$bS + 72 >> 2] = tc9 || tc3 ? 2 : 0;
HEAP32[$bS + 80 >> 2] = tc12 || tc6 ? 2 : 0;
HEAP32[$bS + 88 >> 2] = tc13 || tc7 ? 2 : 0;
HEAP32[$bS + 96 >> 2] = tc10 || tc8 ? 2 : 0;
HEAP32[$bS + 104 >> 2] = tc11 || tc9 ? 2 : 0;
HEAP32[$bS + 112 >> 2] = tc14 || tc12 ? 2 : 0;
HEAP32[$bS + 120 >> 2] = tc15 || tc13 ? 2 : 0;
HEAP32[$bS + 12 >> 2] = tc1 || tc0 ? 2 : 0;
HEAP32[$bS + 20 >> 2] = tc4 || tc1 ? 2 : 0;
HEAP32[$bS + 28 >> 2] = tc5 || tc4 ? 2 : 0;
HEAP32[$bS + 44 >> 2] = tc3 || tc2 ? 2 : 0;
HEAP32[$bS + 52 >> 2] = tc6 || tc3 ? 2 : 0;
HEAP32[$bS + 60 >> 2] = tc7 || tc6 ? 2 : 0;
HEAP32[$bS + 76 >> 2] = tc9 || tc8 ? 2 : 0;
HEAP32[$bS + 84 >> 2] = tc12 || tc9 ? 2 : 0;
HEAP32[$bS + 92 >> 2] = tc13 || tc12 ? 2 : 0;
HEAP32[$bS + 108 >> 2] = tc11 || tc10 ? 2 : 0;
HEAP32[$bS + 116 >> 2] = tc14 || tc11 ? 2 : 0;
HEAP32[$bS + 124 >> 2] = tc15 || tc14 ? 2 : 0;
}
function OptimizedFilterVerLumaEdge ($data, bS, $thresholds, imageWidth) {
var delta, tc, tmp;
var p0, q0, p1, q1, p2, q2;
var tmpFlag;
var $clp = _h264bsdClip + 512;
var alpha = HEAP32[$thresholds + 4 >> 2];
var beta = HEAP32[$thresholds + 8 >> 2];
var val;
if (bS < 4) {
tmp = tc = HEAPU8[HEAP32[$thresholds >> 2] + (bS - 1)] & 255;
for (var i = 4; i > 0; i--) {
p1 = HEAPU8[$data + -2] & 255;
p0 = HEAPU8[$data + -1] & 255;
q0 = HEAPU8[$data] & 255;
q1 = HEAPU8[$data + 1] & 255;
if ((Math.abs(p0 - q0) < alpha) && (Math.abs(p1 - p0) < beta) && (Math.abs(q1 - q0) < beta)) {
p2 = HEAPU8[$data - 3] & 255;
if (Math.abs(p2 - p0) < beta) {
val = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1;
HEAP8[$data - 2] = p1 + clip(-tc, tc, val);
tmp++;
}
q2 = HEAPU8[$data + 2] & 255;
if (Math.abs(q2 - q0) < beta) {
val = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1;
HEAP8[$data + 1] = (q1 + clip(-tc, tc, val));
tmp++;
}
val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
delta = clip(-tmp, tmp, val);
p0 = HEAPU8[$clp + (p0 + delta)] & 255;
q0 = HEAPU8[$clp + (q0 - delta)] & 255;
tmp = tc;
HEAP8[$data - 1] = p0;
HEAP8[$data] = q0;
$data += imageWidth;
}
}
} else {
OriginalFilterVerLumaEdge($data, bS, $thresholds, imageWidth);
}
}
/**
* Filter all four successive horizontal 4-pixel luma edges. This can be done when bS is equal to all four edges.
*/
function OptimizedFilterHorLuma ($data, bS, $thresholds, imageWidth) {
var delta, tc, tmp;
var p0, q0, p1, q1, p2, q2;
var tmpFlag;
var $clp = _h264bsdClip + 512;
var alpha = HEAP32[$thresholds + 4 >> 2];
var beta = HEAP32[$thresholds + 8 >> 2];
var val;
if (bS < 4) {
tmp = tc = HEAPU8[HEAP32[$thresholds >> 2] + (bS - 1)] & 255;
for (var i = 16; i > 0; i--) {
p1 = HEAPU8[$data + (-imageWidth << 1)] & 255;
p0 = HEAPU8[$data + -imageWidth] & 255;
q0 = HEAPU8[$data] & 255;
q1 = HEAPU8[$data + imageWidth] & 255;
if ((Math.abs(p0 - q0) < alpha) && (Math.abs(p1 - p0) < beta) && (Math.abs(q1 - q0) < beta)) {
p2 = HEAPU8[$data + (-imageWidth * 3)] & 255;
if (Math.abs(p2 - p0) < beta) {
val = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1;
HEAP8[$data + (-imageWidth << 1)] = p1 + clip(-tc, tc, val);
tmp++;
}
q2 = HEAPU8[$data + (imageWidth << 2)] & 255;
if (Math.abs(q2 - q0) < beta) {
val = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1;
HEAP8[$data + imageWidth] = (q1 + clip(-tc, tc, val));
tmp++;
}
val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
delta = clip(-tmp, tmp, val);
p0 = HEAPU8[$clp + (p0 + delta)] & 255;
q0 = HEAPU8[$clp + (q0 - delta)] & 255;
tmp = tc;
HEAP8[$data - imageWidth] = p0;
HEAP8[$data] = q0;
$data ++;
}
}
} else {
OriginalFilterHorLuma($data, bS, $thresholds, imageWidth);
}
}
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
-17
Ver Arquivo
@@ -1,17 +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/nodecopter-stream.js')
block append bodyscripts
script
var copterStream = new NodecopterStream(document.querySelector('#dronestream'));
block content
div#dronestream(width=640, height=360)