pistreamer
Version:
NodeJS module that allows streaming the raspberry pi camera module output over websocket to a webpage using a modified version of 131/h264-live-player.
218 lines • 9.15 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createClient = exports.createServer = exports.PiStreamServer = void 0;
const ws_1 = __importDefault(require("ws"));
const lodash_merge_1 = __importDefault(require("lodash.merge"));
const child_process_1 = require("child_process");
const fs_1 = __importDefault(require("fs"));
const http_1 = __importDefault(require("http"));
const path_1 = require("path");
const Notifications_1 = require("./Notifications");
const ImageEffects_1 = require("./enums/ImageEffects");
const Splitter = require('stream-split');
class PiStreamServer {
/**
* PiStreamServer constructor.
* @param wsServer - Instance of a websocket server.
* @param options - Options of the stream.
*/
constructor(wsServer, options) {
this.buffer = Buffer.from([0, 0, 0, 1]);
this.defaultOptions = {
videoOptions: {
height: 240,
width: 480,
framerate: 12,
imxfx: ImageEffects_1.ImageEffects.none,
brightness: 50,
saturation: 50,
sharpness: 50,
contrast: 50
},
dynamic: true,
limit: 0
};
/**
* Stop the feed and kill the raspivid process.
*/
this.stopFeed = () => {
process.kill(-this.streamer.pid);
this.streamer = null;
this.readStream = null;
};
/**
* Start the feed by creating one if there's none or by continuing the existant one.
*/
this.startFeed = () => {
if (this.readStream == null || this.readStream == undefined)
this.createFeed();
var rStream = this.streamer.stdout;
rStream = rStream.pipe(new Splitter(this.buffer));
rStream.on('data', this.broadcast);
this.readStream = rStream;
};
/**
* Create a new feed by starting a new raspivid process.
*/
this.createFeed = () => {
var opts = ['-t', '0', '-o', '-', '-pf', 'baseline'];
var opt;
for (opt in this.options.videoOptions) {
var current = this.options.videoOptions[opt];
opts.push(`--${opt.toLowerCase()}`);
if (opt == "imxfx")
opts.push(ImageEffects_1.ImageEffects[current]);
else if (opt != "vFlip" && opt != "hFlip")
opts.push(current);
}
PiStreamServer.log.info(`Start of stream !`);
this.streamer = child_process_1.spawn('raspivid', opts, { detached: true });
this.streamer.on('error', (error) => {
PiStreamServer.log.error(error.message);
});
this.streamer.on('exit', (code) => {
var msg = (code === null) ? 'Stream Exit' : `Failure code ${code}`;
PiStreamServer.log.log((code === null) ? 'info' : 'error', msg);
});
};
/**
* Broadcast the feed to all the websocket client connected.
* @param data - Video stream.
*/
this.broadcast = (data) => {
this.streamClients.forEach((socket) => {
if (socket.buzy)
return;
socket.buzy = true;
socket.buzy = false;
socket.send(Buffer.concat([this.buffer, data]), { binary: true }, function ack(error) {
socket.buzy = false;
});
});
};
/**
* Actions done when a new client is connected.
* @param socket - Client socket.
*/
this.newClient = (socket) => {
var userLimit = (this.options.limit > 0) ? this.options.limit : 0;
var condition = (userLimit == 0) ? true : (this.wsServer.clients.size <= userLimit);
var self = this;
if (condition) {
this.streamClients.push(socket);
PiStreamServer.log.info(`Someone just connected ! (${self.wsServer.clients.size} user(s) online.)`);
socket.send(JSON.stringify({
action: "init",
width: self.options.videoOptions.width,
height: self.options.videoOptions.height
}));
socket.on('close', () => {
PiStreamServer.log.info(`Someone just left. (${self.wsServer.clients.size} user(s) online.)`);
if (self.streamer != null)
self.readStream.destroy();
if (self.options.dynamic) {
if (self.wsServer.clients.size == 0 && self.streamer != null)
self.stopFeed();
}
});
socket.on("message", (data) => {
var cmd = "" + data, action = data.split(' ')[0];
var validAction = true;
//All of these actions are executed for all the connected users !
try {
switch (action) {
//Start the stream
case "REQUESTSTREAM":
if (self.streamer != null && self.streamer != undefined)
throw "A stream already exists.";
self.startFeed();
break;
//Stop the stream
case "STOPSTREAM":
if (self.streamer == null || self.streamer == undefined)
throw "There's no stream to stop.";
self.stopFeed();
break;
//Toggle pause the stream
case "clientPAUSESTREAM":
if (self.streamClients.includes(socket)) {
var id = self.streamClients.indexOf(socket);
self.streamClients.splice(id - 1, 1);
}
else
self.streamClients.push(socket);
break;
case "globalPAUSESTREAM":
if (self.streamer == null || self.streamer != undefined)
throw "There's no stream to pause.";
if (self.readStream.isPaused())
self.readStream.read();
else
self.readStream.pause();
break;
default:
validAction = false;
break;
}
if (validAction)
PiStreamServer.log.info(`Action incoming: ${action}`);
}
catch (error) {
PiStreamServer.log.error(error);
}
});
}
};
this.streamClients = [];
this.options = lodash_merge_1.default(this.defaultOptions, options);
this.wsServer = wsServer;
this.wsServer.on('connection', this.newClient);
this.setOptions.bind(this);
}
/**
* Set the options of the stream.
* @param options
*/
setOptions(options) {
this.options = lodash_merge_1.default(this.defaultOptions, options);
}
}
exports.PiStreamServer = PiStreamServer;
/**
* Static logger of PiStreamer. You can change the logger by giving
* a winston.Logger object.
* ```ts
* PiStreamServer.log = winston.createLogger(...);
* ```
*/
PiStreamServer.log = Notifications_1.logger;
/**
* Creates an instance of PiStreamServer and returns the Http server linked to it.
* @param requestListner - Request listener.
* @param video - Options of the stream.
*/
const createServer = (requestListener, video) => {
var server = http_1.default.createServer(requestListener);
var stream = new PiStreamServer(new ws_1.default.Server({ server }), video);
return server;
};
exports.createServer = createServer;
/**
* Copy the client file "http-live-player.js" to the given path.
* @param path - Path of the target folder.
*/
const createClient = (path = '.') => {
try {
var file = 'http-live-player.js';
if (fs_1.default.existsSync(path))
fs_1.default.createReadStream(path_1.join(__dirname, '../../vendor/' + file)).pipe(fs_1.default.createWriteStream(path_1.join(path, file)));
}
catch (error) {
PiStreamServer.log.error(error);
}
};
exports.createClient = createClient;
//# sourceMappingURL=PiStream.js.map