diff --git a/.vscode/launch.json b/.vscode/launch.json index 7935651..fed875e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,7 +3,7 @@ "configurations": [ { - "env": {"CONFIGDIRECTORY": "./config/private/debug" , "LOCALDEBUG":"true", "DEBUG": "radarMonitor,radarStalker2,batteryMonitor,ffmpegOverlay,ffplay,radarDatabase,radarPacketParser,remoteMongoDBServer,radarEmulator,gpsMonitor"}, + "env": {"CONFIGDIRECTORY": "./config/private/debug" , "LOCALDEBUG":"true", "DEBUG": "radarMonitor,radarStalker2,batteryMonitor,ffmpegVideoInput,ffmpegVideoOutputFile,ffmpegVideoOutputRtmp,ffplay,radarDatabase,radarPacketParser,remoteMongoDBServer,radarEmulator,gpsMonitor"}, "command": "npm start", "name": "LocalDebug Run npm start", "request": "launch", diff --git a/app.js b/app.js index 679ae89..0f750a4 100644 --- a/app.js +++ b/app.js @@ -17,7 +17,9 @@ const BatteryMonitor = require("./modules/batteryMonitor.js"); const GpsMonitor = require("./modules/gpsMonitor.js"); const DataDisplay = require("./modules/dataDisplay.js"); const RadarDatabase = require("./modules/radarDatabase.js"); -const FfmpegOverlay = require("./modules/ffmpegOverlay.js"); +//const FfmpegOverlay = require("./modules/ffmpegOverlay.js"); +const FfmpegVideoInput = require("./modules/ffmpegVideoInput.js"); +const VideoOverlayParser = require("./modules/videoOverlayParser.js"); const FFplay = require('./modules/ffplay.js'); const { v4: uuidv4 } = require('uuid'); @@ -111,7 +113,10 @@ var radarStalker2 = new RadarStalker2(objOptions.radarStalker2, logUtilHelper); var batteryMonitor = new BatteryMonitor(objOptions.batteryMonitor, logUtilHelper); var gpsMonitor = new GpsMonitor(objOptions.gpsMonitor, logUtilHelper); var dataDisplay = new DataDisplay(objOptions.dataDisplay, logUtilHelper); -var ffmpegOverlay = new FfmpegOverlay(objOptions.ffmpegOverlay, logUtilHelper); +//var ffmpegOverlay = new FfmpegOverlay(objOptions.ffmpegOverlay, logUtilHelper); +var videoOverlayParser = new VideoOverlayParser(objOptions.videoOverlayParser, logUtilHelper); +var ffmpegVideoInput = new FfmpegVideoInput(objOptions.ffmpegVideoInput, videoOverlayParser, logUtilHelper); + var radarDatabase = new RadarDatabase(objOptions.radarDatabase, logUtilHelper, objOptions.dataDirectory); var commonData = { @@ -215,7 +220,9 @@ routes.put('/data/scoregame', function (req, res) { // } //}) commonData.game = game; - updateOverlayText(); + if(ffmpegVideoInput != null){ + ffmpegVideoInput.updateOverlay({gameData:commonData.game, radarData:commonData.currentRadarSpeedData}); + } io.emit("gameChanged", { cmd: "scoreGame", data: { game: commonData.game } }); res.json({ game: commonData.game }); @@ -302,122 +309,7 @@ routes.put('/data/team', function (req, res) { }); -var updateOverlayText = function () { - try { - var OverlayText = "" - if (commonData.game) { - - let homeTeamName = "Home"; - if (commonData.game && commonData.game.home && commonData.game.home.team && commonData.game.home.team.name) { - homeTeamName = commonData.game.home.team.shortName - } - if (commonData.game.score && commonData.game.score.home) { - OverlayText += homeTeamName.padStart(12) + ": " + commonData.game.score.home.toString().padStart(2); - } - - - if (commonData.game.pitcher) { - OverlayText += " P: " - var pitcherName = " "; - if (commonData.game.pitcher.player) { - pitcherName = "#" + commonData.game.pitcher.player.jerseyNumber + " " + commonData.game.pitcher.player.firstName + " " + commonData.game.pitcher.player.lastName; - - if (pitcherName.length > 19) { - pitcherName = "#" + commonData.game.pitcher.player.jerseyNumber + " " + commonData.game.pitcher.player.firstName.substring(0, 1) + ". " + commonData.game.pitcher.player.lastName; - } - if (pitcherName.length > 19) { - pitcherName = ("#" + commonData.game.pitcher.player.jerseyNumber + " " + commonData.game.pitcher.player.lastName).substring(0, 19); - } - } - //need checks to ControlLength pad and truncate - OverlayText += pitcherName.padEnd(19); - } else { - OverlayText += " ".padEnd(22); - } - } - - if (commonData.currentRadarSpeedData) { - OverlayText += " PV: " + commonData.currentRadarSpeedData.inMaxSpeed.toFixed(1).toString().padStart(4, "0") + " MPH " - } else { - OverlayText += " PV: 00.0 MPH " - } - - if (commonData.game) { - - - if (commonData.game.outs) { - OverlayText += " O: " + commonData.game.outs.toString(); - } - - if (commonData.game.balls !== undefined && commonData.game.balls !== null) { - OverlayText += " B: " + commonData.game.balls; - } - - if (commonData.game.strikes !== undefined && commonData.game.strikes !== null) { - OverlayText += " S: " + commonData.game.strikes; - } - - } - - if (commonData.game) { - - - - OverlayText += "\n"; - - let guestTeamName = "Guest"; - if (commonData.game && commonData.game.guest && commonData.game.guest.team && commonData.game.guest.team.name) { - guestTeamName = commonData.game.guest.team.shortName; - } - if (commonData.game.score && commonData.game.score.home) { - OverlayText += guestTeamName.padStart(12) + ": " + commonData.game.score.guest.toString().padStart(2); - } - - if (commonData.game.batter ) { - OverlayText += " B: "; - var batterName = ""; - if (commonData.game.batter.player) { - batterName = "#" + commonData.game.batter.player.jerseyNumber + " " + commonData.game.batter.player.firstName + " " + commonData.game.batter.player.lastName; - - //need checks to ControlLength pad and truncate - if (batterName.length > 19) { - batterName = "#" + commonData.game.batter.player.jerseyNumber + " " + commonData.game.batter.player.firstName.substring(0, 1) + ". " + commonData.game.batter.player.lastName; - } - if (batterName.length > 19) { - batterName = ("#" + commonData.game.batter.player.jerseyNumber + " " + commonData.game.batter.player.lastName).substring(0, 19); - } - } - - OverlayText += batterName.padEnd(19); - } else { - OverlayText += " ".padEnd(22); - } - } - if (commonData.currentRadarSpeedData) { - OverlayText += " EV: " + commonData.currentRadarSpeedData.outMaxSpeed.toFixed(1).toString().padStart(4, "0") + " MPH "; - } else { - OverlayText += " EV: 00.0 MPH "; - } - if (commonData.game) { - - if (commonData.game.inning && commonData.game.inningPosition) { - OverlayText += " I: " + commonData.game.inning.toString() + " " + commonData.game.inningPosition; - } - } - - logUtilHelper.log(appLogName, "app", "trace", "updateOverlayText", OverlayText); - - ffmpegOverlay.updateOverlayText(OverlayText); - } catch (ex) { - logUtilHelper.log(appLogName, "app", "error", "error updating Overlay text", ex); - try { - ffmpegOverlay.updateOverlayText(""); - } catch (ex2) { - logUtilHelper.log(appLogName, "app", "error", "error blanking Overlay text", ex2) - } - } -} app.use('/', routes); @@ -503,21 +395,41 @@ io.on('connection', function(socket) { }); - socket.on('stream', function (message) { - logUtilHelper.log(appLogName, "socketio", "debug",'stream:' + message.cmd + ', client id:' + socket.id); + socket.on('videoStream', function (message) { + logUtilHelper.log(appLogName, "socketio", "debug",'videoStream:' + message.cmd + ', client id:' + socket.id); switch (message.cmd) { case "start": - ffmpegOverlay.streamStart(); + ffmpegVideoInput.streamStart(); break; case "stop": - ffmpegOverlay.streamStop(); + ffmpegVideoInput.streamStop(); break; - case "startRemote": - io.emit('stream', message); + case "youtubeStart": + ffmpegVideoInput.streamStartRtmp(); break; - case "stopRemote": - io.emit('stream', message); + case "youtubeStop": + ffmpegVideoInput.streamStopRtmp(); break; + + case "gamechangerStart": + ffmpegVideoInput.streamStartRtmp2(); + break; + case "gamechangerStop": + ffmpegVideoInput.streamStopRtmp2(); + break; + case "fileStart": + ffmpegVideoInput.streamStartFile(); + break; + case "fileStop": + ffmpegVideoInput.streamStopFile(); + break; + //Was used in cases where local CPU power not enough to encode so started encoding server side encoded and sent rtmp to distination + // case "startRemote": + // io.emit('stream', message); + // break; + // case "stopRemote": + // io.emit('stream', message); + // break; } }); @@ -631,76 +543,11 @@ io.on('connection', function(socket) { } commonData.gameIsDirty = true; io.emit("gameChanged", { cmd: "gameChanged", data: message.data }); //use io to send it to everyone - updateOverlayText(); + if(ffmpegVideoInput !== null){ + ffmpegVideoInput.updateOverlay({gameData: commonData.game, radarData: commonData.currentRadarSpeedData}); + } break; - - - //case "inningChange": - // commonData.game.inning = message.data.inning; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "inningChanged", data: { inning: commonData.game.inning } }); //use io to send it to everyone - // updateOverlayText(); - // break; - //case "inningPositionChange": - // commonData.game.inningPosition = message.data.inningPosition; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "inningPositionChanged", data: { inningPosition: commonData.game.inningPosition } }); //use io to send it to everyone - // updateOverlayText(); - // break; - //case "homeScoreChange": - // if (commonData.game.score === undefined) { - // commonData.game.score = {}; - // } - // commonData.game.score.home = message.data.score.home; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "homeScoreChanged", data: { score: { home: commonData.game.score.home } } }); //use io to send it to everyone - // updateOverlayText(); - // break; - //case "guestScoreChange": - // if (commonData.game.score === undefined) { - // commonData.game.score = {}; - // } - // commonData.game.score.guest = message.data.score.guest; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "guestScoreChanged", data: { score: { guest: commonData.game.score.guest } } }); //use io to send it to everyone - // updateOverlayText(); - // break; - //case "outsChange": - // commonData.game.outs = message.data.outs; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "outsChanged", data: { outs: commonData.game.outs } }); //use io to send it to everyone - // updateOverlayText(); - // break; - //case "strikesChange": - // commonData.game.strikes = message.data.strikes; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "strikesChanged", data: { strikes: commonData.game.strikes } }); //use io to send it to everyone - // updateOverlayText(); - // break; - //case "ballsChange": - // commonData.game.balls = message.data.balls; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "ballsChanged", data: { balls: commonData.game.balls } }); //use io to send it to everyone - // updateOverlayText(); - // break; - //case "pitcherChange": - // commonData.game.pitcher = message.data.pitcher; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "pitcherChanged", data: { pitcher: commonData.game.pitcher } }); //use io to send it to everyone - // //radarStalker2.pitcher({ data: data.pitcher, socket: socket }); - // updateOverlayText(); - // break; - //case "batterChange": - // commonData.game.batter = message.data.batter; - // commonData.gameIsDirty = true; - // io.emit("gameChanged", { cmd: "batterChanged", data: { batter: commonData.game.batter } }); //use io to send it to everyone - // //radarStalker2.batter({ data: data.batter, socket: socket }); - // updateOverlayText(); - // break; - } - - //updateOverlayText(); }) @@ -750,20 +597,7 @@ io.on('connection', function(socket) { - //socket.on("startStream", function (data) { - // ffmpegOverlay.startStream(); - //}) - //socket.on("stopStream", function (data) { - // ffmpegOverlay.stopStream(); - //}) - - - //socket.on("startRemoteStream", function (data) { - // io.emit('startRemoteStream', data); - //}) - //socket.on("stopRemoteStream", function (data) { - // io.emit('stopRemoteStream', data); - //}) + if (socket.client.request.headers["origin"] !== "ArduinoSocketIo") { @@ -797,7 +631,9 @@ radarStalker2.on('radarSpeed', function (data) { dataDisplay.updateSpeedData(data); io.emit('radarSpeed', data); commonData.currentRadarSpeedData = data; - updateOverlayText(); + if(ffmpegVideoInput !== null){ + ffmpegVideoInput.updateOverlay({gameData: commonData.game, radarData: commonData.currentRadarSpeedData}); + } }); radarStalker2.on('radarTimeout', function (data) { io.emit('radarTimeout', data); diff --git a/config/defaultConfig.json b/config/defaultConfig.json index 74a4753..3d23794 100644 --- a/config/defaultConfig.json +++ b/config/defaultConfig.json @@ -52,19 +52,42 @@ } }, - "ffmpegOverlay":{ - "input": "", - "rtmpUrl": "", - "logLevel": "debug", - "inputOptions": [ "-rtsp_transport tcp", "-stimeout 30000000" ], - "outputOptions": [ "-pix_fmt +", "-c:v libx264", "-preset:very fast", "-c:a aac", "-f flv" ], - "capture": false, - "overlayFileName": "overlay.txt", - "videoFilters": { - "filter": "drawtext", - "options": "fontfile=arial.ttf:fontsize=50:box=1:boxcolor=black@0.75:boxborderw=5:fontcolor=white:x=(w-text_w)/2:y=((h-text_h)/2)+((h-text_h)/2):textfile=overlay.txt:reload=1" - } - }, + "ffmpegVideoInput": { + "input":"rtsp://10.100.34.112:554/s0", + "inputOptions": ["-rtsp_transport tcp"], + "outputOptions": [ "-c:a copy", "-c:v copy", "-f nut" ], + "capture":true, + "outputs": { + "ffmpegVideoOutputRtmp" : { + "rtmpUrl": "rtmp://a.rtmp.youtube.com/live2/", + "inputOptions": [], + "outputOptions": [ "-c:a copy", "-c:v libx264", "-preset ultrafast", "-qp 0", "-f flv" ], + "overlayFileName": "data/overlays/youtubeOverlay.txt", + "videoFilters": { + "filter": "drawtext", + "options": "fontfile=arial.ttf:fontsize=50:box=1:boxcolor=black@0.75:boxborderw=5:fontcolor=white:x=(w-text_w)/2:y=((h-text_h)/2)+((h-text_h)/2):textfile=overlay.txt:reload=1" + } + }, + "ffmpegVideoOutputRtmp2" : { + "rtmpUrl": "rtmps://601c62c19c9e.global-contribute.live-video.net:443/app/", + "inputOptions": [], + "outputOptions": [ "-c:a copy", "-c:v libx264", "-vf scale=1920:1080", "-preset ultrafast", "-qp 0", "-f flv" ], + "overlayFileName": "data/overlays/gamechangerOverlay.txt", + "overlayFilter": {"showRadarIn":true}, + "videoFilters": { + "filter": "drawtext", + "options": "fontfile=arial.ttf:fontsize=50:box=1:boxcolor=black@0.75:boxborderw=5:fontcolor=white:x=(w-text_w)/2:y=((h-text_h)/2)+((h-text_h)/2):textfile=overlay.txt:reload=1" + } + }, + "ffmpegVideoOutputFile" : { + "inputOptions": [], + "outputOptions": [ "-c:a copy", "-c:v copy", "-f flv", "-y" ], + "outputFile": "data/videos/captureVideo.flv", + "overlayFileName": null, + "videoFilters": null + } + } + }, "appLogLevels":{ "radarMonitor": { "app":"info", @@ -74,7 +97,13 @@ "radarStalker2": { "app":"info" }, - "ffmpegOverlay": { + "ffmpegVideoInput" :{ + "app":"info" + }, + "ffmpegVideoOutputRtmp" :{ + "app":"info" + }, + "ffmpegVideoOutputFile" : { "app":"info" }, "radarDatabase" :{ diff --git a/data/overlays/gamechangerOverlay.txt b/data/overlays/gamechangerOverlay.txt new file mode 100644 index 0000000..e69de29 diff --git a/data/overlays/youtubeOverlay.txt b/data/overlays/youtubeOverlay.txt new file mode 100644 index 0000000..e69de29 diff --git a/install/foxconn ni/readme.md b/install/foxconn ni/readme.md index 3f34c3d..984c0b7 100644 --- a/install/foxconn ni/readme.md +++ b/install/foxconn ni/readme.md @@ -1,5 +1,5 @@ -Ubunto Server 20 +### Ubuntu Server 20 Audio Trouble Shooting with Intell HDA ``` sudo apt-get install ffmpeg @@ -9,7 +9,7 @@ sudo alsa force-reload ffplay -nodisp -autoexit Pitbull_Fireball.m4a -# no Video Device is du e to no x11 sudo apt-get install xorg-dev +# no Video Device is due to no x11 sudo apt-get install xorg-dev ffplay -devices ``` \ No newline at end of file diff --git a/install/raspberrypi/readme.md b/install/raspberrypi/readme.md index 43198a6..02b857b 100644 --- a/install/raspberrypi/readme.md +++ b/install/raspberrypi/readme.md @@ -1,4 +1,4 @@ -##Raspberry Pi 4 +### Raspberry Pi 4 serial port /dev/ttyS0 @@ -6,16 +6,18 @@ i2c port is /dev/i2c-1 use i2CDevice = 1 in dataDisplays -Bullseye Camera Updates means we have to you libcamera +# Bullseye Camera Updates means we have to you libcamera +``` libcamera-vid -t 0 --width 1024 --height 768 --inline -o - | cvlc stream:///dev/stdin --sout '#rtp{sdp=rtsp://:8554/stream1}' :demux=h264 libcamera-vid -t 0 --width 1920 --height 1024 --autofocus-mode manual --inline --listen -o tcp://0.0.0.0:8888 tcp/h264://raspberrypi.local:8888 -f video4linux2 +``` -#Capture Devices +# Capture Devices ``` ffmpeg -list_devices true -f video4linux2 -i dummy diff --git a/modules/ffmpegVideoInput.js b/modules/ffmpegVideoInput.js index a9afeeb..0c79898 100644 --- a/modules/ffmpegVideoInput.js +++ b/modules/ffmpegVideoInput.js @@ -10,15 +10,15 @@ const { libcamera } = require('@andrewiski/libcamera'); const fs = require('fs'); const { Stream } = require("stream"); const FfmpegVideoOutputRtmp = require("./ffmpegVideoOutputRtmp"); -const FfmpegVideoOutputMp4File = require("./ffmpegVideoOutputMp4File"); -var FfmpegVideoInput = function (options, logUtilHelper) { +const FfmpegVideoOutputFile = require("./ffmpegVideoOutputFile"); +var FfmpegVideoInput = function (options, videoOverlayParser, logUtilHelper) { var self = this; var defaultOptions = { "input": "video='Integrated Camera':audio='Microphone (Realtek High Definition Audio)'", "inputOptions": ["-f dshow", "-video_size 1280x720", "-rtbufsize 702000k", "-framerate 30"], // ["-rtsp_transport tcp","-stimeout 30000000"] //"outputOptions": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-g 50", "-use_wallclock_as_timestamps 1", "-fflags +genpts", "-r 50", "-preset llhq", "-rc vbr_hq", "-f flv" ], - "capture":true, + "capture":true, outputs: null } @@ -27,6 +27,7 @@ var FfmpegVideoInput = function (options, logUtilHelper) { } self.options = extend({}, defaultOptions, options); + self.videoOverlayParser = videoOverlayParser; var CircularChunkArray = []; if (process.platform === 'win32' && (process.env.FFMPEG_PATH === undefined || process.env.FFMPEG_PATH === '')) { @@ -231,12 +232,12 @@ var FfmpegVideoInput = function (options, logUtilHelper) { // incoming and backup transtream pipe to this depending on active source to transform stream commonData.streamStats.incoming.incomingChunkCounter = 0; commonData.streamStats.incoming.incomingChunkShow = 0; - incomingTransStream = new Stream.Transform(); + incomingTransStream = new Stream.Transform({highWaterMark: 1638400}); incomingTransStream._transform = function (chunk, encoding, done) { try { if (commonData.streamStats.incoming.incomingChunkCounter >= commonData.streamStats.incoming.incomingChunkShow) { - logUtilHelper.log(appLogName, "app", 'trace', "incomingMonitorStream", "chunks processed: " + commonData.streamStats.incoming.incomingChunkShow); - commonData.streamStats.incoming.incomingChunkShow = commonData.streamStats.incoming.incomingChunkShow + 50; + logUtilHelper.log(appLogName, "app", 'trace', "incomingTransStream", "chunks processed: " + commonData.streamStats.incoming.incomingChunkShow); + commonData.streamStats.incoming.incomingChunkShow = commonData.streamStats.incoming.incomingChunkShow + 100; } commonData.streamStats.incoming.incomingChunkCounter++; this.push(chunk); @@ -257,7 +258,6 @@ var FfmpegVideoInput = function (options, logUtilHelper) { if (commonData.streamStats.incoming.chunkCounter >= commonData.streamStats.incoming.chunkShow) { logUtilHelper.log(appLogName, "app", 'trace', "incomingMonitorStream", "chunks processed: " + commonData.streamStats.incoming.chunkCounter); commonData.streamStats.incoming.chunkShow = commonData.streamStats.incoming.chunkShow + 50; - self.emit('streamStats', commonData.streamStats, false); } // CircularChunkArray.push(new Buffer.from(chunk)); // if (CircularChunkArray.length > 100) { @@ -282,11 +282,11 @@ var FfmpegVideoInput = function (options, logUtilHelper) { next(); }; - //incomingTransStream.pipe(incomingMonitorStream); + incomingTransStream.pipe(incomingMonitorStream); var ffmpegVideoOutputRtmp = null; var ffmpegVideoOutputRtmp2 = null; - var ffmpegVideoOutputMp4File = null; + var ffmpegVideoOutputFile = null; var startIncomingStream = function () { @@ -336,27 +336,10 @@ var FfmpegVideoInput = function (options, logUtilHelper) { logUtilHelper.log(appLogName, "app", 'info', 'streamStart Command'); try { startIncomingStream(); - if (self.options.outputs && self.options.outputs.ffmpegVideoOutputRtmp) { - - if (ffmpegVideoOutputRtmp === null) { - ffmpegVideoOutputRtmp = new FfmpegVideoOutputRtmp(self.options.outputs.ffmpegVideoOutputRtmp, logUtilHelper); - } - ffmpegVideoOutputRtmp.streamStart(incomingTransStream, throwError); - } - if (self.options.outputs && self.options.outputs.ffmpegVideoOutputRtmp2) { - - if (ffmpegVideoOutputRtmp2 === null) { - ffmpegVideoOutputRtmp2 = new FfmpegVideoOutputRtmp(self.options.outputs.ffmpegVideoOutputRtmp2, logUtilHelper); - } - ffmpegVideoOutputRtmp2.streamStart(incomingTransStream, throwError); - } - if (self.options.outputs && self.options.outputs.ffmpegVideoOutputMp4File) { - - if (ffmpegVideoOutputMp4File === null) { - ffmpegVideoOutputMp4File = new FfmpegVideoOutputMp4File(self.options.outputs.ffmpegVideoOutputMp4File, logUtilHelper); - } - ffmpegVideoOutputMp4File.streamStart(incomingTransStream, throwError); - } + streamStartRtmp(); + streamStartRtmp2(); + streamStartFile(); + self.emit("streamStarted",{}); } catch (ex) { logUtilHelper.log(appLogName, "app", 'error', 'Error Starting Video Stream ', ex); if (throwError === true) { @@ -366,20 +349,16 @@ var FfmpegVideoInput = function (options, logUtilHelper) { }; var streamStop = function (throwError) { + logUtilHelper.log(appLogName, "app", 'warning', 'streamStop command'); - if (ffmpegVideoOutputRtmp !== null) { - ffmpegVideoOutputRtmp.streamStop(throwError); - } - if (ffmpegVideoOutputRtmp2 !== null) { - ffmpegVideoOutputRtmp2.streamStop(throwError); - } - if (ffmpegVideoOutputMp4File !== null) { - ffmpegVideoOutputMp4File.streamStop(throwError); - } + streamStopRtmp(); + streamStopRtmp2(); + streamStopFile(); try { if (!(command === null || command === undefined)) { command.kill(); } + self.emit("streamStopped",{}); } catch (ex) { logUtilHelper.log(appLogName, "app", 'error', 'Error Stopping Video Stream ', ex); if (throwError === true) { @@ -388,14 +367,97 @@ var FfmpegVideoInput = function (options, logUtilHelper) { } }; - var updateOverlayText = function (overlayText) { - var overlayFilePath = path.join(__dirname, '..', self.options.overlayFileName); - try { - fs.writeFileSync(overlayFilePath, overlayText); + var streamStartRtmp = function (throwError){ + if (self.options.outputs && self.options.outputs.ffmpegVideoOutputRtmp && self.options.outputs.ffmpegVideoOutputRtmp.rtmpUrl) { + + if (ffmpegVideoOutputRtmp === null) { + ffmpegVideoOutputRtmp = new FfmpegVideoOutputRtmp(self.options.outputs.ffmpegVideoOutputRtmp, self.videoOverlayParser, logUtilHelper); + ffmpegVideoOutputRtmp.on("streamStart", function (data) { + self.emit("streamStartRtmp",{}); + }); + ffmpegVideoOutputRtmp.on("streamStop", function (data) { + self.emit("streamStopRtmp",{}); + }) + } + ffmpegVideoOutputRtmp.streamStart(incomingTransStream, throwError); + } + }; + var streamStopRtmp= function (throwError){ + if (ffmpegVideoOutputRtmp !== null) { + ffmpegVideoOutputRtmp.streamStop(incomingTransStream, throwError); + } + }; + var streamStartRtmp2 = function (throwError){ + if (self.options.outputs && self.options.outputs.ffmpegVideoOutputRtmp2 && self.options.outputs.ffmpegVideoOutputRtmp2.rtmpUrl) { + + if (ffmpegVideoOutputRtmp2 === null) { + ffmpegVideoOutputRtmp2 = new FfmpegVideoOutputRtmp(self.options.outputs.ffmpegVideoOutputRtmp2, self.videoOverlayParser, logUtilHelper); + ffmpegVideoOutputRtmp.on("streamStart", function (data) { + self.emit("streamStartRtmp2",{}); + }); + ffmpegVideoOutputRtmp.on("streamStop", function (data) { + self.emit("streamStopRtmp2",{}); + }); + } + ffmpegVideoOutputRtmp2.streamStart(incomingTransStream, throwError); + } + }; + var streamStopRtmp2= function (throwError){ + if (ffmpegVideoOutputRtmp2 !== null) { + ffmpegVideoOutputRtmp2.streamStop(incomingTransStream, throwError); + } + }; + var streamStartFile= function (throwError){ + if (self.options.outputs && self.options.outputs.ffmpegVideoOutputFile && self.options.outputs.ffmpegVideoOutputFile.outputFile) { + + if (ffmpegVideoOutputFile === null) { + ffmpegVideoOutputFile = new FfmpegVideoOutputFile(self.options.outputs.ffmpegVideoOutputFile, self.videoOverlayParser, logUtilHelper); + ffmpegVideoOutputRtmp.on("streamStart", function (data) { + self.emit("streamStartFile",{}); + }); + ffmpegVideoOutputRtmp.on("streamStop", function (data) { + self.emit("streamStopFile",{}); + }); + } + ffmpegVideoOutputFile.streamStart(incomingTransStream, throwError); + } + }; + var streamStopFile= function (throwError){ + if (ffmpegVideoOutputFile !== null) { + ffmpegVideoOutputFile.streamStop(incomingTransStream, throwError); + } + }; + + var updateOverlay= function (options) { + try { + if(self.videoOverlayParser !== null){ + var overlayText = self.videoOverlayParser.getOverlayText(options) + if(self.options.overlayFileName != null ){ + var overlayFilePath = path.join(__dirname, '..', self.options.overlayFileName); + try { + fs.writeFileSync(overlayFilePath, overlayText); + + } catch (ex) { + logUtilHelper.log(appLogName, "app", "error", "Error Writing OverlayText File", ex); + } + } + if (ffmpegVideoOutputRtmp !== null) { + ffmpegVideoOutputRtmp.updateOverlay(options); + } + if (ffmpegVideoOutputRtmp2 !== null) { + ffmpegVideoOutputRtmp2.updateOverlay(options); + } + if (ffmpegVideoOutputFile !== null) { + ffmpegVideoOutputFile.updateOverlay(options); + } + }else{ + logUtilHelper.log(appLogName, "app", "warning", "videoOverlayParser is null"); + } } catch (ex) { logUtilHelper.log(appLogName, "app", "error", "Error Writing OverlayText File", ex); } + } @@ -403,6 +465,14 @@ var FfmpegVideoInput = function (options, logUtilHelper) { self.streamStart = streamStart; self.streamStop = streamStop; + self.streamStartRtmp = streamStartRtmp; + self.streamStopRtmp = streamStopRtmp; + self.streamStartRtmp2 = streamStartRtmp2; + self.streamStopRtmp2 = streamStopRtmp2; + self.streamStartFile = streamStartFile; + self.streamStopFile = streamStopFile; + self.updateOverlay = updateOverlay; + self.commonData = function () { return commonData; } diff --git a/modules/ffmpegVideoOutputMp4File.js b/modules/ffmpegVideoOutputFile.js similarity index 83% rename from modules/ffmpegVideoOutputMp4File.js rename to modules/ffmpegVideoOutputFile.js index 0b3b733..652a05a 100644 --- a/modules/ffmpegVideoOutputMp4File.js +++ b/modules/ffmpegVideoOutputFile.js @@ -1,5 +1,5 @@ 'use strict'; -const appLogName = "ffmpegVideoOutputMp4File"; +const appLogName = "ffmpegVideoOutputFile"; var util = require('util'); const EventEmitter = require('events').EventEmitter; //const debug = require('debug')('ffmpegVideoCapture'); @@ -8,7 +8,7 @@ const extend = require('extend'); const ffmpeg = require('fluent-ffmpeg'); const fs = require('fs'); const { Stream } = require("stream"); -var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { +var FfmpegVideoOutputFile = function (options, videoOverlayParser, logUtilHelper) { var self = this; var defaultOptions = { @@ -23,7 +23,7 @@ var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { } self.options = extend({}, defaultOptions, options); - + self.videoOverlayParser = videoOverlayParser; if (process.platform === 'win32' && (process.env.FFMPEG_PATH === undefined || process.env.FFMPEG_PATH === '')) { process.env.FFMPEG_PATH = path.join(__dirname, '..', 'ffmpeg', 'ffmpeg.exe'); @@ -210,22 +210,16 @@ var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { commonData.streamStats.error = "commandEnd Called"; self.emit('streamStats', commonData.streamStats); logUtilHelper.log(appLogName, "app", 'error', 'Source Stream Closed'); - //setTimeout(restartStream, 30000); + }; - var restartStream = function () { - //this is where we would play a local file until we get reconnected to internet. - logUtilHelper.log(appLogName, "app", 'error', 'Restarting incoming Stream because it was Closed'); - startIncomingStream(); - }; - var incomingTransStream = null; var first100 = false; // incoming and backup transtream pipe to this depending on active source to transform stream var incomingTransStreamChunkCounter = 0; - var incomingTransStreamChunkCounter = 0; - incomingTransStream = new Stream.Transform(); + var incomingTransStreamChunkShow = 0; + incomingTransStream = new Stream.Transform({highWaterMark: 1638400}); incomingTransStream._transform = function (chunk, encoding, done) { try { if(incomingTransStreamChunkCounter >= incomingTransStreamChunkShow ){ @@ -236,28 +230,11 @@ var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { this.push(chunk); return done(); } catch (ex) { - logUtilHelper.log(appLogName, "app", 'trace', "error", ex); + logUtilHelper.log(appLogName, "app", "error", ex); } }; - // // Start incomingMonitor Stream - // // Read from the source stream, to keeps it alive and flowing - // var incomingMonitorStream = new Stream.Writable({}); - // // Consume the stream - // incomingMonitorStream._write = (chunk, encoding, next) => { - - // commonData.streamStats.incoming.chunkCounter++; - // if (commonData.streamStats.incoming.chunkCounter >= commonData.streamStats.incoming.chunkShow) { - // logUtilHelper.log(appLogName, "app", 'trace', "incomingMonitorStream", "chunks processed: " + commonData.streamStats.incoming.chunkCounter); - // commonData.streamStats.incoming.chunkShow = commonData.streamStats.incoming.chunkShow + 50; - // self.emit('streamStats', commonData.streamStats, false); - // } - - // next(); - // }; - - // incomingTransStream.pipe(incomingMonitorStream); - + var startIncomingStream = function () { @@ -304,11 +281,13 @@ var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { try { sourceStream.unpipe(incomingTransStream); commonData.streamStats.status = "disconnected"; - commonData.streamStats.error = "commandEnd Called"; - if (!(command === null || command === undefined)) { - command.kill(); + commonData.streamStats.error = "commandEnd Called q sent"; + if (!(command === null || command === undefined || command.ffmpegProc === null || command.ffmpegProc === undefined || command.ffmpegProc.stdin === null || command.ffmpegProc.stdin === undefined)) { + command.ffmpegProc.stdin.write('q'); + }else{ + streamKill(sourceStream,throwError); } - eventEmit("streamStats", commonData.streamStats); + self.emit("streamStats", commonData.streamStats); } catch (ex) { logUtilHelper.log(appLogName, "app", 'error', 'Error Stopping Video Stream ', ex); if (throwError === true) { @@ -317,13 +296,44 @@ var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { } }; - var updateOverlayText = function (overlayText) { - var overlayFilePath = path.join(__dirname, '..', self.options.overlayFileName); + var streamKill = function (sourceStream, throwError) { + commonData.shouldAutoRestart = false; + + logUtilHelper.log(appLogName, "app", 'warning', self.options.rtmpUrl, 'streamKill command'); try { - fs.writeFileSync(overlayFilePath, overlayText); + sourceStream.unpipe(incomingTransStream); + commonData.streamStats.status = "disconnected"; + commonData.streamStats.error = "commandKill Called"; + if (!(command === null || command === undefined)) { + command.kill(); + } + self.emit("streamStats", commonData.streamStats); + } catch (ex) { + logUtilHelper.log(appLogName, "app", 'error', 'Error Killing Video Stream ', ex); + if (throwError === true) { + throw ex; + } + } + }; + var updateOverlay = function (options) { + try { + if(self.videoOverlayParser){ + var overlayText = self.videoOverlayParser.getOverlayText(options) + if(self.options.overlayFileName != null ){ + var overlayFilePath = path.join(__dirname, '..', self.options.overlayFileName); + try { + fs.writeFileSync(overlayFilePath, overlayText); + + } catch (ex) { + logUtilHelper.log(appLogName, "app", "error", "Error Writing OverlayText File", ex); + } + } + }else{ + logUtilHelper.log(appLogName, "app", "warning", "videoOverlayParser is not set", ex); + } } catch (ex) { - logUtilHelper.log(appLogName, "app", "error", "Error Writing OverlayText File", ex); + logUtilHelper.log(appLogName, "app", "error", "Error updating Overlay", ex); } } @@ -333,6 +343,8 @@ var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { self.streamStart = streamStart; self.streamStop = streamStop; + self.streamKill= streamKill; + self.updateOverlay = updateOverlay; self.commonData = function () { return commonData; @@ -340,7 +352,7 @@ var FfmpegVideoOutputMp4File = function (options, logUtilHelper) { } // extend the EventEmitter class using our RadarMonitor class -util.inherits(FfmpegVideoOutputMp4File, EventEmitter); +util.inherits(FfmpegVideoOutputFile, EventEmitter); -module.exports = FfmpegVideoOutputMp4File; +module.exports = FfmpegVideoOutputFile; diff --git a/modules/ffmpegVideoOutputRtmp.js b/modules/ffmpegVideoOutputRtmp.js index f626bf3..cce69f0 100644 --- a/modules/ffmpegVideoOutputRtmp.js +++ b/modules/ffmpegVideoOutputRtmp.js @@ -8,7 +8,7 @@ const extend = require('extend'); const ffmpeg = require('fluent-ffmpeg'); const fs = require('fs'); const { Stream } = require("stream"); -var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { +var FfmpegVideoOutputRtmp = function (options, videoOverlayParser, logUtilHelper) { var self = this; var defaultOptions = { @@ -24,6 +24,7 @@ var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { self.options = extend({}, defaultOptions, options); + self.videoOverlayParser = videoOverlayParser; if (process.platform === 'win32' && (process.env.FFMPEG_PATH === undefined || process.env.FFMPEG_PATH === '')) { process.env.FFMPEG_PATH = path.join(__dirname, '..', 'ffmpeg', 'ffmpeg.exe'); @@ -170,29 +171,29 @@ var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { case 'verbose': if (stdOut.values.size) { commonData.streamStats.info = stdOut.values; - logUtilHelper.log(appLogName, "app", 'trace', 'parsed stdErr: ', stdOut); + logUtilHelper.log(appLogName, "app", 'trace', self.options.rtmpUrl, 'parsed stdErr: ', stdOut); } else { - logUtilHelper.log(appLogName, "app", 'debug', 'parsed stdErr: ', stdOut); + logUtilHelper.log(appLogName, "app", 'debug', self.options.rtmpUrl, 'parsed stdErr: ', stdOut); } break; default: - logUtilHelper.log(appLogName, "app", 'debug', 'parsed stderr: ', stdOut); + logUtilHelper.log(appLogName, "app", 'debug', self.options.rtmpUrl, 'parsed stderr: ', stdOut); } }; var commandError = function (err, stdout, stderr) { - logUtilHelper.log(appLogName, "app", 'error', 'an error happened: ' + err.message, err, stdout, stderr); + logUtilHelper.log(appLogName, "app", 'error', self.options.rtmpUrl, 'an error happened: ' + err.message, err, stdout, stderr); commonData.streamStats.status = "disconnected"; commonData.streamStats.error = err; commonData.streamStats.stdout = stdout; commonData.streamStats.stderr = stderr; self.emit('streamStats', commonData.streamStats); - if (err && err.message && err.message.startsWith('ffmpeg exited with code') === true) { - setTimeout(restartStream, 30000); - } + // if (err && err.message && err.message.startsWith('ffmpeg exited with code') === true) { + // setTimeout(restartStream, 30000); + // } }; var commandProgress = function (progress) { @@ -209,34 +210,26 @@ var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { commonData.streamStats.status = "disconnected"; commonData.streamStats.error = "commandEnd Called"; self.emit('streamStats', commonData.streamStats); - logUtilHelper.log(appLogName, "app", 'error', 'Source Stream Closed'); - //setTimeout(restartStream, 30000); + logUtilHelper.log(appLogName, "app", 'error', self.options.rtmpUrl, 'Source Stream Closed'); }; - var restartStream = function () { - //this is where we would play a local file until we get reconnected to internet. - logUtilHelper.log(appLogName, "app", 'error', 'Restarting incoming Stream because it was Closed'); - - startIncomingStream(); - }; + var incomingTransStream = null; - var first100 = false; - // incoming and backup transtream pipe to this depending on active source to transform stream var incomingTransStreamChunkCounter = 0; var incomingTransStreamChunkShow = 0; - incomingTransStream = new Stream.Transform(); + incomingTransStream = new Stream.Transform({highWaterMark: 1638400}); incomingTransStream._transform = function (chunk, encoding, done) { try { if(incomingTransStreamChunkCounter >= incomingTransStreamChunkShow ){ - logUtilHelper.log(appLogName, "app", 'debug', '[' + incomingTransStreamChunkCounter + '] transform stream chunk length: ' + chunk.length + ', highwater: ' + this.readableHighWaterMark); + logUtilHelper.log(appLogName, "app", 'debug', self.options.rtmpUrl, '[' + incomingTransStreamChunkCounter + '] transform stream chunk length: ' + chunk.length + ', highwater: ' + this.readableHighWaterMark); incomingTransStreamChunkShow = incomingTransStreamChunkShow + 50; } incomingTransStreamChunkCounter++; this.push(chunk); return done(); } catch (ex) { - logUtilHelper.log(appLogName, "app", 'trace', "error", ex); + logUtilHelper.log(appLogName, "app", "error", self.options.rtmpUrl, ex); } }; @@ -259,7 +252,7 @@ var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { // incomingTransStream.pipe(incomingMonitorStream); - var startIncomingStream = function () { + var startIncomingStream = function (sourceStream) { if (!(command === null || command === undefined)) { command.kill(); @@ -268,6 +261,7 @@ var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { self.emit('streamStats', commonData.streamStats); logUtilHelper.log(appLogName, "app", "info", "RTMP destination", self.options.rtmpUrl) command = ffmpeg({ source: incomingTransStream }); + //command = ffmpeg({ source: sourceStream }); command.inputOptions(self.options.inputOptions) command.addOption('-loglevel level+info') //added by Andy so we can parse out stream info @@ -289,25 +283,29 @@ var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { } command.run(); - logUtilHelper.log(appLogName, "app","info", "ffmpeg Started"); + logUtilHelper.log(appLogName, "app", "info", self.options.rtmpUrl, "ffmpeg Started"); }; var streamStart = function (sourceStream,throwError) { - logUtilHelper.log(appLogName, "app", 'info', 'streamStart Command'); + logUtilHelper.log(appLogName, "app", 'info', self.options.rtmpUrl, 'streamStart Command'); try { sourceStream.unpipe(incomingTransStream); sourceStream.pipe(incomingTransStream); - startIncomingStream(); + startIncomingStream(sourceStream); } catch (ex) { - logUtilHelper.log(appLogName, "app", 'error', 'Error Starting Video Stream ', ex); + logUtilHelper.log(appLogName, "app", 'error', self.options.rtmpUrl, 'Error Starting Video Stream ', ex); if (throwError === true) { throw ex; } } }; - var streamStop = function (sourceStream,throwError) { - logUtilHelper.log(appLogName, "app", 'warning', 'streamStop command'); + + + var streamKill = function (sourceStream,throwError) { + commonData.shouldAutoRestart = false; + + logUtilHelper.log(appLogName, "app", 'warning', self.options.rtmpUrl, 'streamKill command'); try { sourceStream.unpipe(incomingTransStream); commonData.streamStats.status = "disconnected"; @@ -315,32 +313,62 @@ var FfmpegVideoOutputRtmp = function (options, logUtilHelper) { if (!(command === null || command === undefined)) { command.kill(); } - eventEmit("streamStats", commonData.streamStats); + self.emit("streamStats", commonData.streamStats); } catch (ex) { - logUtilHelper.log(appLogName, "app", 'error', 'Error Stopping Video Stream ', ex); + logUtilHelper.log(appLogName, "app", 'error', 'Error Killing Video Stream ', ex); if (throwError === true) { throw ex; } } }; - var updateOverlayText = function (overlayText) { - var overlayFilePath = path.join(__dirname, '..', self.options.overlayFileName); + + var streamStop = function (sourceStream,throwError) { + commonData.shouldAutoRestart = false; + logUtilHelper.log(appLogName, "app", 'warning', self.options.rtmpUrl, 'streamStop command'); try { - fs.writeFileSync(overlayFilePath, overlayText); + sourceStream.unpipe(incomingTransStream); + commonData.streamStats.status = "disconnected"; + commonData.streamStats.error = "commandEnd Called"; + if (!(command === null || command === undefined || command.ffmpegProc === null || command.ffmpegProc === undefined || command.ffmpegProc.stdin === null || command.ffmpegProc.stdin === undefined)) { + command.ffmpegProc.stdin.write('q'); + }else{ + streamKill(sourceStream,throwError); + } + self.emit("streamStats", commonData.streamStats); + } catch (ex) { + logUtilHelper.log(appLogName, "app", 'error', self.options.rtmpUrl, 'Error Stopping Video Stream ', ex); + if (throwError === true) { + throw ex; + } + } + }; + var updateOverlay = function (options) { + try { + if(self.videoOverlayParser){ + var overlayText = self.videoOverlayParser.getOverlayText(options) + if(self.options.overlayFileName != null ){ + var overlayFilePath = path.join(__dirname, '..', self.options.overlayFileName); + try { + fs.writeFileSync(overlayFilePath, overlayText); + + } catch (ex) { + logUtilHelper.log(appLogName, "app", "error", self.options.rtmpUrl, "Error Writing OverlayText File", ex); + } + } + }else{ + logUtilHelper.log(appLogName, "app", "warning", self.options.rtmpUrl, "videoOverlayParser is not set", ex); + } } catch (ex) { - logUtilHelper.log(appLogName, "app", "error", "Error Writing OverlayText File", ex); + logUtilHelper.log(appLogName, "app", "error", self.options.rtmpUrl, "Error updating Overlay", ex); } } - - - - self.streamStart = streamStart; self.streamStop = streamStop; - + self.streamKill = streamKill; + self.updateOverlay = updateOverlay; self.commonData = function () { return commonData; } diff --git a/modules/rtspVideoInput.js b/modules/rtspVideoInput.js index 07e654d..9a7e184 100644 --- a/modules/rtspVideoInput.js +++ b/modules/rtspVideoInput.js @@ -9,7 +9,7 @@ const fs = require('fs'); const { Stream, pipeline } = require("stream"); var http = require('http'); const FfmpegVideoOutputRtmp = require("./ffmpegVideoOutputRtmp"); -const FfmpegVideoOutputMp4File = require("./ffmpegVideoOutputMp4File"); +const FfmpegVideoOutputFile = require("./ffmpegVideoOutputFile"); var FfmpegVideoInput = function (options, logUtilHelper) { var self = this; @@ -146,8 +146,8 @@ var FfmpegVideoInput = function (options, logUtilHelper) { } if (self.options.outputs && self.options.outputs.ffmpegVideoOutputMp4File) { - if (ffmpegVideoOutputMp4File === null) { - ffmpegVideoOutputMp4File = new FfmpegVideoOutputMp4File(self.options.outputs.ffmpegVideoOutputMp4File, logUtilHelper); + if (ffmpegVideoOutputFile === null) { + ffmpegVideoOutputFile = new FfmpegVideoOutputFile(self.options.outputs.ffmpegVideoOutputMp4File, logUtilHelper); } ffmpegVideoOutputMp4File.streamStart(incomingTransStream, throwError); } @@ -164,8 +164,8 @@ var FfmpegVideoInput = function (options, logUtilHelper) { if (ffmpegVideoOutputRtmp !== null) { ffmpegVideoOutputRtmp.streamStop(throwError); } - if (ffmpegVideoOutputMp4File !== null) { - ffmpegVideoOutputMp4File.streamStop(throwError); + if (ffmpegVideoOutputFile !== null) { + ffmpegVideoOutputFile.streamStop(throwError); } try { if (!(command === null || command === undefined)) { diff --git a/modules/rtsptoHls.js b/modules/rtsptoHls.js new file mode 100644 index 0000000..d5afb84 --- /dev/null +++ b/modules/rtsptoHls.js @@ -0,0 +1,7 @@ +//Write a function that takes incoming rtsp video stream as buffer object and creates hls m3u8 file +//and returns the path of the m3u8 file +function(stream){ + +} + + diff --git a/modules/videoOverlayParser.js b/modules/videoOverlayParser.js new file mode 100644 index 0000000..56062e7 --- /dev/null +++ b/modules/videoOverlayParser.js @@ -0,0 +1,159 @@ +'use strict'; +const appLogName = "videoOverlayParser"; +const extend = require('extend'); +var videoOverlayParser = function (options, logUtilHelper) { + + var self = this; + var defaultOptions = { + "capture":true, + outputs: null + } + if(logUtilHelper === null){ + throw new Error("logUtilHelper Can't be Null"); + } + self.options = extend({}, defaultOptions, options); + + + + var getOverlayText = function (options) { + try { + var gameData = options.gameData; + var radarData = options.radarData; + + let defaultFilters = { + showTeamNames: true, + showPitcherName: true, + showBatterName: true, + showBalls: true, + showStrikes: true, + showOuts: true, + showRadarPitchingVelocity: true, + showRadarHittingVelocity: true + } + + var overlayFilters = extend({},options.filters, defaultFilters); + var overlayText = ""; + if (gameData) { + + if(overlayFilters.showTeamNames === true){ + let homeTeamName = "Home"; + if (gameData && gameData.home && gameData.home.team && gameData.home.team.name) { + homeTeamName = gameData.home.team.shortName; + } + if (gameData.score && gameData.score.home) { + overlayText += homeTeamName.padStart(12) + ": " + gameData.score.home.toString().padStart(2); + } + } + + if (overlayFilters.showPitcherName && gameData.pitcher) { + overlayText += " P: " + var pitcherName = " "; + if (gameData.pitcher.player) { + pitcherName = "#" + gameData.pitcher.player.jerseyNumber + " " + gameData.pitcher.player.firstName + " " + gameData.pitcher.player.lastName; + + if (pitcherName.length > 19) { + pitcherName = "#" + gameData.pitcher.player.jerseyNumber + " " + gameData.pitcher.player.firstName.substring(0, 1) + ". " + gameData.pitcher.player.lastName; + } + if (pitcherName.length > 19) { + pitcherName = ("#" + gameData.pitcher.player.jerseyNumber + " " + gameData.pitcher.player.lastName).substring(0, 19); + } + } + //need checks to ControlLength pad and truncate + overlayText += pitcherName.padEnd(19); + } else { + overlayText += " ".padEnd(22); + } + } + + if (overlayFilters.showRadarPitchingVelocity){ + if(radarData) { + overlayText += " PV: " + radarData.inMaxSpeed.toFixed(1).toString().padStart(4, "0") + " MPH "; + } else { + overlayText += " PV: 00.0 MPH "; + } + } + if (gameData) { + if (overlayFilters.showOuts && gameData.outs) { + overlayText += " O: " + gameData.outs.toString(); + } + + if (overlayFilters.showBalls && gameData.balls !== undefined && gameData.balls !== null) { + overlayText += " B: " + gameData.balls; + } + + if (overlayFilters.showStrikes && gameData.strikes !== undefined && gameData.strikes !== null) { + overlayText += " S: " + gameData.strikes; + } + + } + + if (gameData) { + + + + overlayText += "\n"; + + if(overlayFilters.showTeamNames === true){ + let guestTeamName = "Guest"; + if (gameData && gameData.guest && gameData.guest.team && gameData.guest.team.name) { + guestTeamName = gameData.guest.team.shortName; + } + if (gameData.score && gameData.score.home) { + overlayText += guestTeamName.padStart(12) + ": " + gameData.score.guest.toString().padStart(2); + } + } + + if (overlayFilters.showBatterName && gameData.batter ) { + overlayText += " B: "; + var batterName = ""; + if (gameData.batter.player) { + batterName = "#" + gameData.batter.player.jerseyNumber + " " + gameData.batter.player.firstName + " " + gameData.batter.player.lastName; + + //need checks to ControlLength pad and truncate + if (batterName.length > 19) { + batterName = "#" + gameData.batter.player.jerseyNumber + " " + gameData.batter.player.firstName.substring(0, 1) + ". " + gameData.batter.player.lastName; + } + if (batterName.length > 19) { + batterName = ("#" + gameData.batter.player.jerseyNumber + " " + gameData.batter.player.lastName).substring(0, 19); + } + } + overlayText += batterName.padEnd(19); + } else { + overlayText += " ".padEnd(22); + } + } + if(filterOverlay.showRadarHittingVelocity === true){ + if (radarData) { + overlayText += " EV: " + radarData.outMaxSpeed.toFixed(1).toString().padStart(4, "0") + " MPH "; + } else { + overlayText += " EV: 00.0 MPH "; + } + if (gameData) { + if (gameData.inning && gameData.inningPosition) { + overlayText += " I: " + gameData.inning.toString() + " " + gameData.inningPosition; + } + } + } + + logUtilHelper.log(appLogName, "app", "trace", "updateOverlayText", OverlayText); + + return overlayText; + } catch (ex) { + logUtilHelper.log(appLogName, "app", "error", "error updating Overlay text", ex); + try { + return "" + } catch (ex2) { + logUtilHelper.log(appLogName, "app", "error", "error blanking Overlay text", ex2) + } + } + } + + + self.getOverlayText = getOverlayText; + + + + +} + +module.exports = videoOverlayParser; \ No newline at end of file diff --git a/public/app/img/GameChanger-icon.svg b/public/app/img/GameChanger-icon.svg new file mode 100644 index 0000000..382a23c --- /dev/null +++ b/public/app/img/GameChanger-icon.svg @@ -0,0 +1,18 @@ + + + Group 13 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/app/scoreboard/scoreboard.tpl.html b/public/app/scoreboard/scoreboard.tpl.html index 935f17d..2779321 100644 --- a/public/app/scoreboard/scoreboard.tpl.html +++ b/public/app/scoreboard/scoreboard.tpl.html @@ -981,18 +981,68 @@ - Video Streams + Video Streams
-
-
- - +
+
+
+
+ + + + + +
+ +
+
-
- - +
+ +
+
+
+ + + + + +
+ + +
+
+ +
+
+
+
+ + + + + +
+
+
+
+
+
+
+ + + + + +
diff --git a/public/app/scoreboard/scoreboardController.js b/public/app/scoreboard/scoreboardController.js index c324e83..ece1748 100644 --- a/public/app/scoreboard/scoreboardController.js +++ b/public/app/scoreboard/scoreboardController.js @@ -101,10 +101,10 @@ radarSpeedDataHistory: [], showRadarConfig : true, editRadarConfig: false, + serverLogsSubscribe: {timer:null,sockets:[]}, radarSpeedData: { id: 0, time: new Date(), - inMinSpeed: 0, inMaxSpeed: 0, outMinSpeed: 0, @@ -227,6 +227,27 @@ $scope.commonData.isGameSelect = true; } + + var resubscribeServerLogs = function () { + console.log("resubscribeServerLogs"); + if (streamerDetailsSubscribe.timerID && streamerDetailsSubscribe.audioStreamerId) { + Service.socket.emit("AudioStreamerAction", { "actionId": getFakeGuid(), "audioStreamerId": streamerDetailsSubscribe.audioStreamerId, "action": "SubscribeStreamerDetails" }); + streamerDetailsSubscribe.timerID = window.setTimeout(resubscribeStreamerDetails, 10 * 60 * 1000); + } + } + + var subscribeServerLogs = function (audioStreamerId) { + console.log("subscribeServerLogs"); + if (streamerDetailsSubscribe.timerID) { + unsubscribeStreamerDetails(); + } + streamerDetailsSubscribe.audioStreamerId = audioStreamerId; + Service.socket.emit("AudioStreamerAction", { "actionId": getFakeGuid(), "audioStreamerId": streamerDetailsSubscribe.audioStreamerId, "action": "SubscribeStreamerDetails", data: { logLevel: "info" } }); + streamerDetailsSubscribe.timerID = window.setTimeout(resubscribeStreamerDetails, 10 * 60 * 1000 ); + + } + + $scope.gameScore = function () { if ($scope.commonData.selectedGame.id === '00000000-0000-0000-0000-000000000000') { $scope.commonData.selectedGame.id = null; @@ -285,26 +306,46 @@ } - $scope.streamStart = function () { - //Tell server the inningChanged - radarMonitor.sendServerCommand("stream", { cmd: "start"}); - } + $scope.videoStreamStart = function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "start"}); + } - $scope.streamStop= function () { - //Tell server the inningChanged - radarMonitor.sendServerCommand("stream", { cmd: "stop" }); - } + $scope.videoStreamStop= function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "stop" }); + } + $scope.videoStreamYoutubeStart = function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "youtubeStart"}); + } - $scope.streamStartRemote = function () { - //Tell server the inningChanged - radarMonitor.sendServerCommand("stream", { cmd: "startRemote" }); - } + $scope.videoStreamYoutubeStop= function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "youtubeStop" }); + } - $scope.streamStopRemote = function () { - //Tell server the inningChanged - radarMonitor.sendServerCommand("stream", { cmd: "stopRemote" }); - } + $scope.videoStreamGameChangerStart = function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "gamechangerStart"}); + } + + $scope.videoStreamGameChangerStop= function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "gamechangerStop" }); + } + + $scope.videoStreamFileStart = function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "fileStart"}); + } + + $scope.videoStreamFileStop= function () { + //Tell server the inningChanged + radarMonitor.sendServerCommand("videoStream", { cmd: "fileStop" }); + } + $scope.batterChange = function () { @@ -1115,6 +1156,16 @@ // $scope.$apply(); //}); + + $rootScope.$on('stream:youtubeStarted', function(event, data) { + // use the data accordingly + //console.log('radarMonitor:radarSpeedDataHistory detected '); + console.debug(data); + $scope.commonData.stream = data; + $scope.$apply(); + }); + + $rootScope.$on('radarMonitor:radarSpeedDataHistory', function(event, data) { // use the data accordingly //console.log('radarMonitor:radarSpeedDataHistory detected '); diff --git a/public/tests/youtube.html b/public/tests/youtube.html new file mode 100644 index 0000000..b289d95 --- /dev/null +++ b/public/tests/youtube.html @@ -0,0 +1,50 @@ + + + + \ No newline at end of file diff --git a/tests/testVideoCapture.js b/tests/testVideoCapture.js index f92fb18..af85daf 100644 --- a/tests/testVideoCapture.js +++ b/tests/testVideoCapture.js @@ -16,7 +16,7 @@ var objOptions = { "app":"debug" }, "ffmpegVideoInput" :{ - "app":"debug" + "app":"trace" }, "ffmpegVideoOutputRtmp" :{ "app":"debug" @@ -31,50 +31,50 @@ var objOptions = { input:"rtsp://10.100.34.112:554/s0", //input:"video=Integrated Camera:audio=Microphone (Realtek High Definition Audio)", //inputOptions: ["-f dshow", "-video_size 1280x720", "-rtbufsize 702000k", "-framerate 30"], // ["-rtsp_transport tcp","-stimeout 30000000"] - inputOptions: ["-rtsp_transport tcp", "-stimeout 30000000"], + inputOptions: ["-rtsp_transport tcp"], //outputOptions: [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-g 12", "-use_wallclock_as_timestamps 1", "-fflags +genpts", "-preset hq", "-f flv" ], outputOptions: [ "-c:a copy", "-c:v copy", "-f nut" ], capture:true, outputs: { ffmpegVideoOutputRtmp : { - "rtmpUrl": "rtmp://a.rtmp.youtube.com/live2/x0fy-98kz-0967-s3gy-0jq2", + "rtmpUrl": "rtmp://a.rtmp.youtube.com/live2/a5fv-d7rc-7jsa-v7g4-2c70", //"rtmpUrl2": "rtmps://601c62c19c9e.global-contribute.live-video.net:443/app/sk_us-east-1_gIgq11gfyeCV_yCYDarNv5iHdMgZJbDHnAhfjWPKvjt", //"rtmpUrlRtp": "rtp://127.0.0.1:3000", "inputOptions": [], - //"outputOptions": [ "-c:a copy", "-pix_fmt +", "-c:v libx264", "-preset ultrafast", "-tune zerolatency", "-f flv" ], - "outputOptions": [ "-c:a copy", "-c:v copy", "-f flv" ], + "outputOptions": [ "-c:a copy", "-c:v libx264", "-preset ultrafast", "-qp 0", "-f flv" ], + //"outputOptions": [ "-c:a copy", "-c:v copy", "-f flv" ], //"outputOptions2": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-use_wallclock_as_timestamps 1", "-g 48", "-preset hq", "-f flv" ], //"outputOptionsRtp": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-b:v 4.5M", "-use_wallclock_as_timestamps 1", "-fflags +genpts", "-preset llhq", "-rc vbr_hq", "-sdp_file video.sdp", "-f rtp" ], - "overlayFileName": null, - "videoFilters": null, - // "videoFilters": { - // "filter": "drawtext", - // "options": "fontfile=arial.ttf:fontsize=50:box=1:boxcolor=black@0.75:boxborderw=5:fontcolor=white:x=(w-text_w)/2:y=((h-text_h)/2)+((h-text_h)/2):textfile=overlay.txt:reload=1" - // } + "overlayFileName": youTubeOverlay.txt, + "videoFilters": { + "filter": "drawtext", + "options": "fontfile=arial.ttf:fontsize=50:box=1:boxcolor=black@0.75:boxborderw=5:fontcolor=white:x=(w-text_w)/2:y=((h-text_h)/2)+((h-text_h)/2):textfile=overlay.txt:reload=1" + } } - //, - // ffmpegVideoOutputRtmp2 : { - // "rtmpUrl": "rtmps://601c62c19c9e.global-contribute.live-video.net:443/app/sk_us-east-1_a8gQblEYUN3V_iHEQ0mXeSm1VROYp7YHqGapllvvXd5", - // //"rtmpUrl2": "rtmps://601c62c19c9e.global-contribute.live-video.net:443/app/sk_us-east-1_gIgq11gfyeCV_yCYDarNv5iHdMgZJbDHnAhfjWPKvjt", - // //"rtmpUrlRtp": "rtp://127.0.0.1:3000", - // "inputOptions": [], - // "outputOptions": [ "-c:a copy", "-pix_fmt +", "-c:v libx264", "-use_wallclock_as_timestamps 1", "-g 12", "-preset hq", "-f flv" ], - // //"outputOptions2": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-use_wallclock_as_timestamps 1", "-g 48", "-preset hq", "-f flv" ], - // //"outputOptionsRtp": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-b:v 4.5M", "-use_wallclock_as_timestamps 1", "-fflags +genpts", "-preset llhq", "-rc vbr_hq", "-sdp_file video.sdp", "-f rtp" ], - // "overlayFileName": "overlay.txt", - // "videoFilters": { - // "filter": "drawtext", - // "options": "fontfile=arial.ttf:fontsize=50:box=1:boxcolor=black@0.75:boxborderw=5:fontcolor=white:x=(w-text_w)/2:y=((h-text_h)/2)+((h-text_h)/2):textfile=overlay.txt:reload=1" - // } - // }, - // ffmpegVideoOutputMp4File : { - // "inputOptions": [], - // "outputOptions": [ "-c:a copy", "-c:v copy", "-f flv", "-y" ], - // "outputFile": "e:\\videos\\testVideoCapture.flv", - // //"outputOptionsRtp": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-b:v 4.5M", "-use_wallclock_as_timestamps 1", "-fflags +genpts", "-preset llhq", "-rc vbr_hq", "-sdp_file video.sdp", "-f rtp" ], - // "overlayFileName": null, - // "videoFilters": null - // } + , + ffmpegVideoOutputRtmp2 : { + "rtmpUrl": "rtmps://601c62c19c9e.global-contribute.live-video.net:443/app/sk_us-east-1_iAKfeoPwrELU_yxEtfN8t1iC9IjsHHnLbC8vBUW8eoT", + //"rtmpUrl2": "rtmps://601c62c19c9e.global-contribute.live-video.net:443/app/sk_us-east-1_gIgq11gfyeCV_yCYDarNv5iHdMgZJbDHnAhfjWPKvjt", + //"rtmpUrlRtp": "rtp://127.0.0.1:3000", + "inputOptions": [], + "outputOptions": [ "-c:a copy", "-c:v libx264", "-vf scale=1920:1080", "-preset ultrafast", "-qp 0", "-f flv" ], + //"outputOptions2": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-use_wallclock_as_timestamps 1", "-g 48", "-preset hq", "-f flv" ], + //"outputOptionsRtp": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-b:v 4.5M", "-use_wallclock_as_timestamps 1", "-fflags +genpts", "-preset llhq", "-rc vbr_hq", "-sdp_file video.sdp", "-f rtp" ], + "overlayFileName": "GameChangerOverlay.txt", + "overlayFilter": {showRadarIn:true}, + "videoFilters": { + "filter": "drawtext", + "options": "fontfile=arial.ttf:fontsize=50:box=1:boxcolor=black@0.75:boxborderw=5:fontcolor=white:x=(w-text_w)/2:y=((h-text_h)/2)+((h-text_h)/2):textfile=overlay.txt:reload=1" + } + }, + ffmpegVideoOutputFile : { + "inputOptions": [], + "outputOptions": [ "-c:a copy", "-c:v copy", "-f flv", "-y" ], + "outputFile": "e:\\videos\\testVideoCapture.flv", + //"outputOptionsRtp": [ "-c:a copy", "-pix_fmt +", "-c:v h264_nvenc", "-b:v 4.5M", "-use_wallclock_as_timestamps 1", "-fflags +genpts", "-preset llhq", "-rc vbr_hq", "-sdp_file video.sdp", "-f rtp" ], + "overlayFileName": null, + "videoFilters": null + } } }