UNPKG

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
"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