From f7908f6a40cca63c8b59e94bc060fb0f63dcb933 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 15 Apr 2016 20:51:17 -0400 Subject: [PATCH 1/4] add osc status updates #23 --- README.md | 31 ++++++++--- index.js | 6 +++ lib/osc.js | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 lib/osc.js diff --git a/README.md b/README.md index efa3a96..bdca810 100644 --- a/README.md +++ b/README.md @@ -14,25 +14,42 @@ For now docs are in the source only. var CasparCG = require("caspar-cg"); - ccg = new CasparCG("localhost", 5250); - ccg.connect(function () { - ccg.info(function (err, serverInfo) { + ccg = new CasparCG({ + host: "localhost", + port: 5250, + debug: true, + osc: true, // osc status updates are opt in + oscThrottle: 250 // throttles status updates in ms + }); + + ccg.connect(() => { + ccg.info((err, serverInfo) => { console.log(serverInfo); }); ccg.play("1-1", "AMB"); + ccg.loadTemplate("1-20", "NTSC-TEST-60", true); - setTimeout(function () { + setTimeout(() => { ccg.clear("1"); ccg.disconnect(); - }, 10 * 1000); + }, 60 * 1000); }); - ccg.on("connected", function () { + ccg.on("connected", () => { console.log("Connected"); }); -## Changelog + // must opt in to osc + ccg.on("status", status => { + console.log(JSON.stringify(status)); + }); + +## Change log + +###v0.1.1 + +* Adds OSC Status updates ### v0.1.0 diff --git a/index.js b/index.js index d2d0446..5cc406e 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,9 @@ var ccg = module.exports = function (host, port) { this.options.port = port; } + // osc server + require("./lib/osc")(this); + this.index = count++; }; @@ -29,6 +32,9 @@ ccg.prototype.options = { reconnect: true, host: "localhost", port: 5250, + osc: false, + oscPort: 6250, + oscThrottle: 250, debug: false }; diff --git a/lib/osc.js b/lib/osc.js new file mode 100644 index 0000000..2e2e82e --- /dev/null +++ b/lib/osc.js @@ -0,0 +1,144 @@ +"use strict"; + +const osc = require("node-osc"); +const _ = require("underscore"); + +module.exports = function (ccg) { + if (!ccg.options.osc) return; + + const oscServer = new osc.Server(ccg.options.oscPort, "0.0.0.0"); + + const channels = {}; + + let emit = ccg.emit.bind(ccg); + + if (ccg.options.oscThrottle) { + emit = _.throttle(emit, ccg.options.oscThrottle); + } + + oscServer.on("message", function (msg, rinfo) { + try { + msg.shift(); // bundle + msg.shift(); // number? + + let message; + while (msg.length > 0) { + message = msg.shift(); + parseMessage(message[0], message[1]); + } + + emit("status", channels); + } catch (err) { + ccg.log("OSC Parsing Error", err); + } + }); + + function parseMessage(message, value) { + const parts = message.split("/"); + parts.shift(); + + if (parts.length <= 0) return console.log("too short", parts); + if (parts[0] !== "channel") return console.log("not channel", parts); + + const channel = channels[parts[1]] = channels[parts[1]] || { + layers: {}, + audioChannels: {} + }; + + if (parts[2] === "stage") { + if (parts[3] === "layer") { + if (parts.length < 6) return console.log("too short", parts); + const layer = channel.layers[parts[4]] = channel.layers[parts[4]] || {}; + + switch (parts[5]) { + case "time": + layer.time = value; + return; + case "frame": + layer.frame = value; + return; + case "type": + layer.type = value; + return; + case "paused": + layer.paused = value; + return; + case "buffer": + layer.buffer = value; + return; + case "loop": + layer.loop = value; + return; + case "profiler": + switch (parts[6]) { + case "time": + layer.profiler = {time: value}; + return; + } + return; + case "host": + const host = layer.host = layer.host || {}; + switch (parts[6]) { + case "width": + host.width = value; + return; + case "height": + host.height = value; + return; + case "path": + if (value.indexOf("\\\\") >= 0) { + value = value.split("\\\\")[1]; + } + + host.path = value; + return; + case "fps": + host.fps = value; + return; + } + break; + case "file": + const file = layer.file = layer.file || {}; + switch (parts[6]) { + case "time": + file.time = value; + return; + case "frame": + file.frame = value; + return; + case "fps": + file.fps = value; + return; + case "path": + file.path = value; + return; + case "loop": + file.loop = value; + return; + } + break; + } + } + } + + if (parts[2] === "mixer") { + if (parts[3] === "audio") { + if (parts[4] === "nb_channels") { + channel.nbChannels = value; + return; + } + + const audioChannel = channel.audioChannels[parts[4]] = channel.audioChannels[parts[4]] || {}; + + switch (parts[5]) { + case "pFS": + audioChannel.pFS = value; + return; + case "dBFS": + audioChannel.dBFS = value; + return; + } + } + } + } +}; diff --git a/package.json b/package.json index 1cae934..c73ed79 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test": "grunt mochaTest" }, "dependencies": { + "node-osc": "git+ssh://git@github.com/respectTheCode/node-osc.git", "underscore": "~1.8.3", "sax": "~1.1.1" }, From 1cce4c35e5965f192decca5feccba9e86eee430b Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 15 Apr 2016 21:28:24 -0400 Subject: [PATCH 2/4] fix travis --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e6f0ec..d44921d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "grunt mochaTest" }, "dependencies": { - "node-osc": "git+ssh://git@github.com/respectTheCode/node-osc.git", + "node-osc": "respectTheCode/node-osc", "underscore": "~1.8.3", "sax": "~1.1.1" }, From 1f83a13479fdfdd4004ff809e67fa65a68203244 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Sun, 17 Apr 2016 08:43:00 -0400 Subject: [PATCH 3/4] clear channels after `oscTimeout` and report file `totalTime` and `totalFrames` --- index.js | 1 + lib/osc.js | 38 +++++++++++++++++++++++++++++++++----- package.json | 2 +- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 5cc406e..c324dea 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,7 @@ ccg.prototype.options = { osc: false, oscPort: 6250, oscThrottle: 250, + oscTimeout: 1000, debug: false }; diff --git a/lib/osc.js b/lib/osc.js index 2e2e82e..000a87d 100644 --- a/lib/osc.js +++ b/lib/osc.js @@ -8,7 +8,9 @@ module.exports = function (ccg) { const oscServer = new osc.Server(ccg.options.oscPort, "0.0.0.0"); - const channels = {}; + let channels = {}; + let channelTimeouts = {}; + const oscTimeout = ccg.options.oscTimeout; let emit = ccg.emit.bind(ccg); @@ -19,12 +21,12 @@ module.exports = function (ccg) { oscServer.on("message", function (msg, rinfo) { try { msg.shift(); // bundle - msg.shift(); // number? + msg.shift(); // OSC Time Tag (not parsed correctly) let message; while (msg.length > 0) { message = msg.shift(); - parseMessage(message[0], message[1]); + parseMessage(message[0], message[1], message[2] || false); } emit("status", channels); @@ -33,7 +35,26 @@ module.exports = function (ccg) { } }); - function parseMessage(message, value) { + // clear all when disconnected + ccg.on("disconnected", () => { + channels = {}; + channelTimeouts = {}; + }); + + function parseMessage(message, value, value2) { + const now = new Date().getTime(); + Object.keys(channelTimeouts).forEach(channelNumber => { + const channelTimeout = channelTimeouts[channelNumber]; + Object.keys(channelTimeout).forEach(layerNumber => { + if (channelTimeout[layerNumber] + oscTimeout < now) { + console.log("layer timeout", channelNumber, layerNumber); + delete channels[channelNumber][layerNumber]; + delete channelTimeout[layerNumber]; + return; + } + }); + }); + const parts = message.split("/"); parts.shift(); @@ -44,11 +65,13 @@ module.exports = function (ccg) { layers: {}, audioChannels: {} }; + const channelTimeout = channelTimeouts[parts[1]] = channelTimeouts[parts[1]] || {}; if (parts[2] === "stage") { if (parts[3] === "layer") { if (parts.length < 6) return console.log("too short", parts); const layer = channel.layers[parts[4]] = channel.layers[parts[4]] || {}; + channelTimeout[parts[4]] = now; switch (parts[5]) { case "time": @@ -72,7 +95,10 @@ module.exports = function (ccg) { case "profiler": switch (parts[6]) { case "time": - layer.profiler = {time: value}; + layer.profiler = { + actual: value, + expected: value2 + }; return; } return; @@ -102,9 +128,11 @@ module.exports = function (ccg) { switch (parts[6]) { case "time": file.time = value; + file.totalTime = value2; return; case "frame": file.frame = value; + file.totalFrames = value2; return; case "fps": file.fps = value; diff --git a/package.json b/package.json index d44921d..68fb919 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "grunt-contrib-clean": "~0.4.0", "grunt-contrib-watch": "~0.3.1", "grunt-mocha-test": "~0.6.3", - "grunt-contrib-jshint": "~0.7.1", + "grunt-contrib-jshint": "1.0.0", "grunt-jscs": "2.8.0", "jshint-stylish": "~0.1.3", "load-grunt-tasks": "~0.2.0", From 5b4fd539333fc6fc054ce0d515b007b6521e7d61 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Sun, 17 Apr 2016 09:26:15 -0400 Subject: [PATCH 4/4] detect empty file and host --- lib/osc.js | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/osc.js b/lib/osc.js index 000a87d..55d35a7 100644 --- a/lib/osc.js +++ b/lib/osc.js @@ -29,6 +29,32 @@ module.exports = function (ccg) { parseMessage(message[0], message[1], message[2] || false); } + const now = new Date().getTime(); + Object.keys(channelTimeouts).forEach(channelNumber => { + const channelTimeout = channelTimeouts[channelNumber]; + Object.keys(channelTimeout).forEach(layerNumber => { + const layerTimeout = channelTimeout[layerNumber]; + + if (layerTimeout.ts + oscTimeout < now) { + delete channels[channelNumber].layers[layerNumber]; + delete channelTimeout[layerNumber]; + return; + } + + if (layerTimeout.file + oscTimeout < now) { + delete channels[channelNumber].layers[layerNumber].file; + delete channelTimeout[layerNumber].file; + return; + } + + if (layerTimeout.file + oscTimeout < now) { + delete channels[channelNumber].layers[layerNumber].host; + delete channelTimeout[layerNumber].host; + return; + } + }); + }); + emit("status", channels); } catch (err) { ccg.log("OSC Parsing Error", err); @@ -43,18 +69,6 @@ module.exports = function (ccg) { function parseMessage(message, value, value2) { const now = new Date().getTime(); - Object.keys(channelTimeouts).forEach(channelNumber => { - const channelTimeout = channelTimeouts[channelNumber]; - Object.keys(channelTimeout).forEach(layerNumber => { - if (channelTimeout[layerNumber] + oscTimeout < now) { - console.log("layer timeout", channelNumber, layerNumber); - delete channels[channelNumber][layerNumber]; - delete channelTimeout[layerNumber]; - return; - } - }); - }); - const parts = message.split("/"); parts.shift(); @@ -71,7 +85,8 @@ module.exports = function (ccg) { if (parts[3] === "layer") { if (parts.length < 6) return console.log("too short", parts); const layer = channel.layers[parts[4]] = channel.layers[parts[4]] || {}; - channelTimeout[parts[4]] = now; + channelTimeout[parts[4]] = channelTimeout[parts[4]] || {}; + channelTimeout[parts[4]].ts = now; switch (parts[5]) { case "time": @@ -104,6 +119,7 @@ module.exports = function (ccg) { return; case "host": const host = layer.host = layer.host || {}; + channelTimeout[parts[4]].host = now; switch (parts[6]) { case "width": host.width = value; @@ -125,6 +141,7 @@ module.exports = function (ccg) { break; case "file": const file = layer.file = layer.file || {}; + channelTimeout[parts[4]].file = now; switch (parts[6]) { case "time": file.time = value;