thing-it-device-swann
Version:
[thing-it-node] Device Plugin for Swann © products.
318 lines (258 loc) • 9.69 kB
JavaScript
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();
}
};
}