UNPKG

thing-it-device-swann

Version:

[thing-it-node] Device Plugin for Swann © products.

318 lines (258 loc) 9.69 kB
module.exports = { metadata: { family: "camera", plugin: "camera", label: "Swann © Camera", tangible: true, state: [{id: "video", label: "Video", type: {id: "video"}}, { id: "snapshotImage", label: "Snapshot Image", type: {id: "image"} }, {id: "pan", label: "Pan", type: {id: "number"}}, {id: "tilt", label: "Tilt", type: {id: "number"}}], actorTypes: [], sensorTypes: [], services: [{id: "panLeft", label: "Pan Left"}, {id: "panRight", label: "Pan Right"}, {id: "tiltUp", label: "Tilt Up"}, {id: "tiltDown", label: "Tilt Down"}], configuration: [{ id: "url", label: "URL", type: {id: "string"} }, {id: "videoWidth", label: "Video Width", type: {id: "integer"}, default: "320"}, { id: "videoHeight", label: "Video Height", type: {id: "integer"}, default: "180" }] }, create: function () { return new Camera(); } }; var q = require('q'); var fs = require('fs'); var child_process = require('child_process'); var ffmpeg; function mkdirSynch(path) { try { fs.mkdirSync(path); } catch (e) { if (e.code != 'EEXIST') throw e; } } /** * */ function Camera() { /** * */ Camera.prototype.start = function () { var deferred = q.defer(); this.state = {pan: 0, tilt: 0, snapshotImage: __dirname + "/data/brooklyn.jpg"}; if (this.isSimulated()) { deferred.resolve(); // Create directory for snapshot images and streaming buffer mkdirSynch(this.node.options.dataDirectory + "/devices"); mkdirSynch(this.node.options.dataDirectory + "/devices/" + this.id); this.imageIndex = 0; this.rotateIndex = 0; setInterval(function () { this.flipImage(); this.publishStateChange(); }.bind(this), 5000); } else { var input = this.configuration.url; // Attempt to load the stream before clients connect - not working deferred.resolve(); } return deferred.promise; }; /** * */ Camera.prototype.flipImage = function () { fs.createReadStream(__dirname + "/data/snapshotImage" + (this.imageIndex + 1) + ".png").pipe(fs.createWriteStream(this.node.options.dataDirectory + "/devices/" + this.id + "/snapshotImage" + (this.rotateIndex + 1) + ".png")); this.imageIndex = (this.imageIndex + 1) % 3; this.rotateIndex = (this.rotateIndex + 1) % 2; this.state.snapshotImage = "/data/devices/" + this.id + "/snapshotImage" + (this.rotateIndex + 1) + ".png"; }; /** * */ Camera.prototype.setState = function (state) { this.state = state; }; /** * */ Camera.prototype.getState = function () { return this.state; }; /** * */ Camera.prototype.streamWebCam = function (req, res) { //if (!ffmpeg) { // ffmpeg = require("fluent-ffmpeg"); //} this.logDebug("Start WebCam Streaming"); // Should mimick ffmpeg -f qtkit -video_device_index 0 -i "" out.mpg //ffmpeg().input("0"/*this.configuration.inputDevice*/).inputFormat("qtkit"/*this.configuration.inputFormat*/) // .duration(5000 /*this.configuration.latency*/)/*.size(this.videoSize)*/ // /*.pipe(res)*/ // .output("./out.mpg") // .on('end', function () { // this.streamWebCam(); // }.bind(this)) // .on('error', function (error) { // this.logError(error); // }.bind(this)); ffmpeg = child_process.spawn("ffmpeg", [ "-f", "qtkit", "-video_device_index", "0", "-i", "", "-f", "mp4", "-movflags", "frag_keyframe+empty_moov", "-reset_timestamps", "1", "out.mpg" ], {detached: false}); //ffmpeg.stdout.pipe(res); ffmpeg.stdout.on("data", function (data) { this.logDebug("Data"); res.write(data); }.bind(this)); ffmpeg.stderr.on("data", function (data) { this.logError("Error -> " + data); }.bind(this)); ffmpeg.on("exit", function (code) { this.logDebug("ffmpeg terminated with code " + code); }.bind(this)); ffmpeg.on("error", function (e) { this.logError("ffmpeg system error: " + e); }.bind(this)); }; Camera.prototype.writeData = function (data) { if (ffmpeg && this.res) { ffmpeg.stdout.pipe(this.res); } } /** * */ Camera.prototype.video = function (req, res) { if (this.isSimulated()) { this.logDebug("Piping file"); this.pipeFile(req, res, __dirname + "/data/spaceship.m4v", "video/mp4"); } else if (false) { this.streamWebCam(req, res); } else if (false) { this.logDebug("Starting video tag processing."); var input = this.configuration.url; res.writeHead(200, { //'Transfer-Encoding': 'binary' "Connection": "keep-alive" , "Content-Type": "video/mp4" //, 'Content-Length': chunksize // ends after all bytes delivered , "Accept-Ranges": "bytes" // Helps Chrome }); if (!ffmpeg) { // TODO use require("fluent-ffmpeg") and code like pushCameraInput on top ffmpeg = child_process.spawn("ffmpeg", [ "-i", input, "-s", "480x360", "-vcodec", "libx264", "-timeout", "10", "-f", "mp4", "-movflags", "frag_keyframe+empty_moov", "-reset_timestamps", "1", "-vsync", "1", "-flags", "global_header", "-bsf:v", "dump_extra", "-y", "-" // output to stdout ], {detached: false}); /* ffmpeg = child_process.spawn("ffmpeg", [ "-i", input, "-s", "320x240", "-r", "30000/1001", "-b", "200k", "-bt", "240k", "-vcodec", "libx264", "-coder", "0", "-bf", "0", "-refs", "1", "-flags2", "-wpred-dct8x8", "-level", "30", "-maxrate", "10M", "-bufsize", "10M", "-acodec", "libfaac", "-ac", "2", "-ar", "48000", "-ab", "192k", "-y", "-" // output to stdout ], {detached: false}); */ this.logDebug("Spawned ffmpeg child process. Piping response now."); ffmpeg.stdout.pipe(res); this.logDebug("Piping called."); ffmpeg.stdout.on("data", function (data) { this.logDebug("Data"); }.bind(this)); ffmpeg.stderr.on("data", function (data) { this.logError("Error -> " + data); }.bind(this)); ffmpeg.on("exit", function (code) { this.logDebug("ffmpeg terminated with code " + code); }.bind(this)); ffmpeg.on("error", function (e) { this.logError("ffmpeg system error: " + e); }.bind(this)); } //this.res = res; req.on("close", function () { closeStream("closed") }) req.on("end", function () { closeStream("ended") }); function closeStream(event) { //TODO: Stream is only shut when the browser has exited, so switching screens in the client app does not kill the session console.log("Live streaming connection to client has " + event) /* if (ffmpeg) { ffmpeg.kill(); ffmpeg = null; } */ } } }; /** * */ Camera.prototype.panLeft = function (parameters) { var angle = parameters && parameters.angle ? parameters.angle : 5; if (this.isSimulated()) { this.state.pan = Math.max(-180, this.state.pan - angle); this.flipImage(); this.publishStateChange(); } else { this.publishStateChange(); } }; /** * */ Camera.prototype.panRight = function (parameters) { var angle = parameters && parameters.angle ? parameters.angle : 5; if (this.isSimulated()) { this.state.pan = Math.min(180, this.state.pan + angle); this.flipImage(); this.publishStateChange(); } else { this.publishStateChange(); } }; /** * */ Camera.prototype.tiltUp = function (parameters) { var angle = parameters && parameters.angle ? parameters.angle : 5; if (this.isSimulated()) { this.state.tilt = Math.min(180, this.state.tilt + angle); this.flipImage(); this.publishStateChange(); } else { this.publishStateChange(); } }; /** * */ Camera.prototype.tiltDown = function (parameters) { var angle = parameters && parameters.angle ? parameters.angle : 5; if (this.isSimulated()) { this.state.tilt = Math.max(-180, this.state.tilt - angle); this.flipImage(); this.publishStateChange(); } else { this.publishStateChange(); } }; }