diff --git a/plugins/eos/cue.ejs b/plugins/eos/cue.ejs index e8fa4ca..e302e20 100644 --- a/plugins/eos/cue.ejs +++ b/plugins/eos/cue.ejs @@ -11,7 +11,9 @@ <% } %> - <% if(isActive){ %> + <% if(isRunning){ %> + + <% }else if(isComplete || isActive){ %> <% }else{ %> @@ -53,7 +55,8 @@ <%= part.prettyDuration(part.beamTimeDuration, true) %> - <%= part.prettyDuration(part.duration) %> + <% var durValue = (isRunning && remainingDuration !== null && remainingDuration !== undefined) ? remainingDuration : part.duration %> + <%= part.prettyDuration(durValue) %> <% }else{ %> @@ -63,13 +66,23 @@ <% } %> - <%= part.mark %> - <%= part.block %> - <%= part.assert %> - - <%= (part.follow>=0)? "F"+part.prettyDuration(part.follow) : "" %> - <%= (part.hang>=0)? "H"+part.prettyDuration(part.hang) : "" %> - + <%= part.mark %> + <%= part.block %> + <%= part.assert %> + <% var followDisplay = (part.follow>=0)? "F"+part.prettyDuration(part.follow) : "" %> + <% var hangDisplay = (part.hang>=0)? "H"+part.prettyDuration(part.hang) : "" %> + <% var followHangDisplay = followDisplay + hangDisplay %> + <% var isFollowHangOnlyRunning = (followHangRemainingDuration !== null && followHangRemainingDuration !== undefined && followHangRemainingDuration > 0 && !isRunning) %> + <% if(followHangRemainingDuration !== null && followHangRemainingDuration !== undefined && followHangRemainingDuration > 0){ %> + <% if(part.follow >= 0){ %> + <% followHangDisplay = "F"+part.prettyDuration(followHangRemainingDuration) %> + <% }else if(part.hang >= 0 && !isRunning){ %> + <% followHangDisplay = "H"+part.prettyDuration(followHangRemainingDuration) %> + <% } %> + <% } %> + + <%= followHangDisplay %> + <%= (partNumber==0 ? "": "   ") %> <%= part.label %> diff --git a/plugins/eos/main.js b/plugins/eos/main.js index f16b406..c444514 100644 --- a/plugins/eos/main.js +++ b/plugins/eos/main.js @@ -27,6 +27,9 @@ exports.config = { action(_device) { const device = _device; device.data.cueListFilter = device.fields.cueListFilter; + if (device.data.EOS) { + subscribeToCueList(device); + } device.draw(); }, }, @@ -40,10 +43,14 @@ exports.ready = function ready(_device) { cue: _.template(fs.readFileSync(path.join(__dirname, `cue.ejs`))), }; device.data.cueListFilter = device.fields.cueListFilter; + device.data.EOS.progressCueList = resolveConfiguredCueList(device); device.send('/eos/get/cuelist/count'); device.send('/eos/get/version'); device.send('/eos/subscribe', [{ type: 'i', value: 1 }]); + + // subscribe to the selected cue list specifically + subscribeToCueList(device); }; exports.data = function data(_device, osc) { @@ -80,6 +87,7 @@ exports.data = function data(_device, osc) { device.update('cueData', { cue: device.data.EOS.cueLists[addressParts[4]][addressParts[5]], + cueListNumber: addressParts[4], cueNumber: addressParts[5], uid: osc.args[1], }); @@ -92,9 +100,88 @@ exports.data = function data(_device, osc) { if (osc.args.length === 3) { device.data.EOS.cueLists[addressParts[4]][addressParts[5]][0].extLinks = osc.args[2]; } + } else if (match(addressParts, ['eos', 'out', 'cuelist', '8001', '1'])) { + const cueListNumber = resolveConfiguredCueList(device); + const cueNumber = cueNumberValue(osc.args[1]); + const totalMs = nonNegativeInt(osc.args[6]); + const cueRemainingMs = nonNegativeInt(osc.args[7]); + const previousCueNumber = cueNumberValue(device.data.EOS.activeCueByList[cueListNumber]); + + if (cueNumber !== undefined) { + device.data.EOS.activeCueByList[cueListNumber] = cueNumber; + device.data.EOS.activeCue = cueNumber; + } + + if (cueNumber !== undefined || cueRemainingMs !== null || totalMs !== null) { + const progress = device.data.EOS.progressByList[cueListNumber] || {}; + if (cueNumber !== undefined) { + progress.cueNumber = cueNumber; + } + if (cueRemainingMs !== null) { + progress.cueRemainingMs = cueRemainingMs; + } + if (totalMs !== null) { + progress.totalMs = totalMs; + } + device.data.EOS.progressByList[cueListNumber] = progress; + } + + if (previousCueNumber !== undefined && previousCueNumber !== cueNumber) { + device.update('cueState', { + cueListNumber, + cueNumber: previousCueNumber, + }); + } + + if (cueNumber !== undefined) { + device.update('cueState', { + cueListNumber, + cueNumber, + }); + } + } else if (match(addressParts, ['eos', 'out', 'cuelist', '8001'])) { + const cueListNumber = resolveConfiguredCueList(device); + const followHangRemainingMs = nonNegativeInt(osc.args[2]); + if (followHangRemainingMs !== null) { + const progress = device.data.EOS.progressByList[cueListNumber] || {}; + const activeCueForList = cueNumberValue(device.data.EOS.activeCueByList[cueListNumber]); + if (progress.cueNumber === undefined && activeCueForList !== undefined) { + progress.cueNumber = activeCueForList; + } + progress.followHangRemainingMs = followHangRemainingMs; + device.data.EOS.progressByList[cueListNumber] = progress; + if (progress.cueNumber !== undefined) { + device.update('cueState', { + cueListNumber, + cueNumber: progress.cueNumber, + }); + } + } } else if (match(addressParts, ['eos', 'out', 'event', 'cue', '*', '*', 'fire'])) { - device.data.EOS.activeCue = addressParts[5]; - device.draw(); + const cueListNumber = cueNumberValue(addressParts[4]); + const cueNumber = cueNumberValue(addressParts[5]); + const previousCueNumber = cueNumberValue(device.data.EOS.activeCueByList[cueListNumber]); + + device.data.EOS.activeCue = cueNumber; + device.data.EOS.activeCueByList[cueListNumber] = cueNumber; + device.data.EOS.progressByList[cueListNumber] = { + cueNumber, + cueRemainingMs: null, + followHangRemainingMs: null, + totalMs: null, + }; + + if (previousCueNumber !== undefined && previousCueNumber !== cueNumber) { + device.update('cueState', { + cueListNumber, + cueNumber: previousCueNumber, + }); + } + device.update('cueState', { + cueListNumber, + cueNumber, + }); + const cues = device.data.EOS.cueLists[addressParts[4]][addressParts[5]]; if (cues) { device.update('activeCue', { @@ -115,18 +202,48 @@ exports.data = function data(_device, osc) { exports.update = function update(device, doc, updateType, data) { if (updateType === 'cueData') { const $elem = doc.getElementById(data.uid); + const cueState = cueRenderState(device, data.cueListNumber, data.cueNumber); if ($elem) { $elem.outerHTML = device.templates.cue({ q: data.cue, cueNumber: data.cueNumber, - isActive: false, + isActive: cueState.isComplete, + isComplete: cueState.isComplete, + isRunning: cueState.isRunning, + remainingDuration: cueState.remainingDuration, + followHangRemainingDuration: cueState.followHangRemainingDuration, + }); + } else { + device.draw(); + } + } else if (updateType === 'cueState') { + const cueListNumber = cueNumberValue(data.cueListNumber); + const cueNumber = cueNumberValue(data.cueNumber); + const cues = device.data.EOS.cueLists?.[cueListNumber]?.[cueNumber]; + if (!cues || !cues[0]) { + device.draw(); + return; + } + const $elem = doc.getElementById(cues[0].uid); + if ($elem) { + const cueState = cueRenderState(device, cueListNumber, cueNumber); + $elem.outerHTML = device.templates.cue({ + q: cues, + cueNumber, + isActive: cueState.isComplete, + isComplete: cueState.isComplete, + isRunning: cueState.isRunning, + remainingDuration: cueState.remainingDuration, + followHangRemainingDuration: cueState.followHangRemainingDuration, }); } else { device.draw(); } } else if (updateType === 'activeCue') { const $elem = doc.getElementById(data.uid); - $elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); + if ($elem) { + $elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } } }; @@ -134,6 +251,62 @@ exports.heartbeat = function heartbeat(device) { device.send('/eos/ping'); }; +function resolveConfiguredCueList(device) { + const cueListFilter = cueNumberValue(device.data.cueListFilter); + return cueListFilter || '1'; +} + +function subscribeToCueList(device) { + const cueListNumber = resolveConfiguredCueList(device); + const eosState = device.data.EOS; + eosState.progressCueList = cueListNumber; + device.send(`/eos/cuelist/8001/config/${cueListNumber}/0/0`); +} + +function cueRenderState(device, cueListNumber, cueNumber) { + const list = cueNumberValue(cueListNumber); + const cue = cueNumberValue(cueNumber); + const progress = device.data.EOS.progressByList[list]; + const activeCue = cueNumberValue(device.data.EOS.activeCueByList[list]); + const cueRemainingMs = progress?.cueRemainingMs; + const followHangRemainingMs = progress?.followHangRemainingMs; + const isComplete = cue === activeCue; + const isRunning = + isComplete && + progress !== undefined && + cue === cueNumberValue(progress.cueNumber) && + cueRemainingMs !== null && + cueRemainingMs > 0; + const hasFollowHangCountdown = isComplete && followHangRemainingMs !== null && followHangRemainingMs > 0; + + return { + isRunning, + isComplete, + remainingDuration: isRunning ? cueRemainingMs : null, + followHangRemainingDuration: hasFollowHangCountdown ? followHangRemainingMs : null, + }; +} + +function cueNumberValue(value) { + if (value === undefined || value === null) { + return undefined; + } + return `${value}`.trim(); +} + +function nonNegativeInt(value) { + if (value === undefined || value === null) { + return null; + } + + const numericValue = Number(value); + if (!Number.isFinite(numericValue)) { + return null; + } + + return Math.max(0, Math.round(numericValue)); +} + function match(testArray, patternArray) { let out = true; if (testArray.length !== patternArray.length) { @@ -152,5 +325,8 @@ class EOS { this.showName = ''; this.cueLists = {}; this.activeCue = undefined; + this.activeCueByList = {}; + this.progressByList = {}; + this.progressCueList = '1'; } } diff --git a/plugins/eos/styles.css b/plugins/eos/styles.css index 2d3db83..2f5682d 100644 --- a/plugins/eos/styles.css +++ b/plugins/eos/styles.css @@ -63,6 +63,25 @@ tr.active-cue .time { border-color: #c78b07; } +tr.running-cue { + color: #ae0009; +} +tr.running-cue td.num div { + margin: 0px; + padding: 1px 6px; + background-color: #ae0009; + border-radius: 4px; + color: white; +} +tr.running-cue .time { + border-color: #ae0009; +} + +td.running-follow-hang { + color: #ae0009; + font-weight: 700; +} + .list_name { color: lightgray; margin-left: 5px; diff --git a/plugins/eos/template.ejs b/plugins/eos/template.ejs index ab6873e..8c279cc 100644 --- a/plugins/eos/template.ejs +++ b/plugins/eos/template.ejs @@ -25,15 +25,30 @@ <% var cues = Object.keys(data.EOS.cueLists[i]).sort(function(a, b){return Number(a)-Number(b)}) %> + <% var activeCueByList = data.EOS.activeCueByList || {} %> + <% var progressByList = data.EOS.progressByList || {} %> + <% var activeCueNumber = (activeCueByList[i] !== undefined) ? (activeCueByList[i] + "") : undefined %> + <% var progressState = progressByList[i] || {} %> + <% var progressCueNumber = (progressState.cueNumber !== undefined && progressState.cueNumber !== null) ? (progressState.cueNumber + "") : undefined %> + <% var hasCueRemaining = (progressState.cueRemainingMs !== undefined && progressState.cueRemainingMs !== null) %> + <% var hasFollowHangRemaining = (progressState.followHangRemainingMs !== undefined && progressState.followHangRemainingMs !== null) %> <% for(var j=0; j <% var q = data.EOS.cueLists[i][cues[j]] %> - + <% var isComplete = (activeCueNumber !== undefined && cues[j] == activeCueNumber) %> + <% var isRunning = isComplete && hasCueRemaining && progressCueNumber === cues[j] && progressState.cueRemainingMs > 0 %> + <% var remainingDuration = isRunning ? progressState.cueRemainingMs : null %> + <% var followHangRemainingDuration = (isComplete && !isRunning && hasFollowHangRemaining && progressState.followHangRemainingMs > 0) ? progressState.followHangRemainingMs : null %> + <%= templates.cue({ q: q, cues: cues, cueNumber: cues[j], - isActive: (cues[j]==data.EOS.activeCue+"") + isActive: isComplete, + isComplete: isComplete, + isRunning: isRunning, + remainingDuration: remainingDuration, + followHangRemainingDuration: followHangRemainingDuration }) %> <% } %>