Merge pull request #269 from stagehacks/plugin-livestream-studio

add support for livestream studio
This commit is contained in:
2024-10-28 07:12:46 -05:00
committed by GitHub
7 changed files with 397 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+5
View File
@@ -0,0 +1,5 @@
<h3>Livestream Studio Configuration</h3>
<ul>
<li>enable Third-party Controllers under Settings->Hardware Control->Allow Incoming Connections</li>
<li>allow the connection from Cue View the first time it connects under the same settings menu</li>
</ul>
+147
View File
@@ -0,0 +1,147 @@
exports.config = {
defaultName: 'Livestream Studio',
connectionType: 'TCPsocket',
remotePort: 9923,
mayChangePort: false,
searchOptions: {
type: 'TCPport',
searchBuffer: Buffer.from(''),
testPort: 9923,
validateResponse(msg, info) {
return msg.toString().startsWith('ILCC:');
},
},
};
exports.ready = function ready(_device) {
console.log('livestream studio ready');
const device = _device;
device.draw();
setInterval(() => {
device.update('inputs', device.data);
device.update('fadeToBlack', device.data);
}, 1000);
};
exports.update = function update(device, _document, updateType, data) {
const document = _document;
if (updateType === 'programInput' || updateType === 'previewInput' || updateType === 'inputs') {
device.data.inputs.forEach((input) => {
if (device.data.program === input.number) {
document.getElementById(`input-${input.number}`).classList.add('lss-red');
} else {
document.getElementById(`input-${input.number}`).classList.remove('lss-red');
}
if (device.data.preview === input.number) {
document.getElementById(`input-${input.number}`).classList.add('lss-green');
} else {
document.getElementById(`input-${input.number}`).classList.remove('lss-green');
}
});
} else if (updateType === 'fadeToBlack') {
const ftbId = 'ftb';
if (device.data.fadeToBlack) {
document.getElementById(ftbId).classList.add('lss-ftb-glow');
} else {
document.getElementById(ftbId).classList.remove('lss-ftb-glow');
}
} else if (updateType === 'tbar') {
const tbarId = `tbar-div`;
const tbarHandleId = `tbar-handle-div`;
if (document.getElementById(tbarId)) {
document.getElementById(tbarId).style.height = `${device.data.tBar.percent * 1.4 + 10}px`;
}
if (document.getElementById(tbarHandleId)) {
document.getElementById(tbarHandleId).style.bottom = `${device.data.tBar.percent * 1.4 + 10}px`;
}
if (device.data.tBar.status === 'Automatic') {
document.getElementById(`auto`).classList.add('lss-red');
} else {
document.getElementById(`auto`).classList.remove('lss-red');
}
} else if (updateType === 'cut') {
document.getElementById('cut').classList.add('lss-red');
setTimeout(() => {
document.getElementById('cut').classList.remove('lss-red');
}, 250);
}
};
exports.data = function data(_device, msg) {
const device = _device;
this.deviceInfoUpdate(device, 'status', 'ok');
const packets = msg.toString().split('\n');
packets.forEach((packet) => {
const packetParts = packet.split(':');
const packetType = packetParts[0];
if (packetType === 'ILC') {
if (device.data.inputs === undefined) {
device.data.inputs = [];
}
device.data.inputs[packetParts[1]] = {
number: Number.parseInt(packetParts[1], 10) + 1,
name: packetParts[2].replaceAll('"', ''),
audio: {
level: parseFloat(packetParts[3]) / 1000,
gain: parseFloat(packetParts[4]) / 1000,
mute: packetParts[5] === '1',
solo: packetParts[6] === '1',
programLock: packetParts[7] === '1',
},
type: packetParts[8],
};
device.draw();
device.update('inputs', device.data);
} else if (packetType === 'ILCC') {
if (device.data.inputCount !== undefined) {
device.data.inputs = [];
}
device.data.inputCount = Number.parseInt(packetParts[1], 10);
} else if (packetType === 'PmIS') {
device.data.program = Number.parseInt(packetParts[1], 10) + 1;
device.update('programInput', device.data);
} else if (packetType === 'PwIS') {
device.data.preview = Number.parseInt(packetParts[1], 10) + 1;
device.update('previewInput', device.data);
} else if (packetType === 'Cut') {
[device.data.preview, device.data.program] = [device.data.program, device.data.preview];
device.data.tBar.percent = 0;
device.data.tBar.status = 'Stop';
device.update('cut');
device.update('inputs', device.data);
device.update('tbar', device.data);
} else if (packetType === 'FOut') {
device.data.fadeToBlack = true;
device.update('fadeToBlack', device.data);
} else if (packetType === 'FIn') {
device.data.fadeToBlack = false;
device.update('fadeToBlack', device.data);
} else if (packetType === 'TrASp' || packetType === 'TrMSp') {
if (device.data.tBar === undefined) {
device.data.tBar = {};
}
device.data.tBar.status = packetType === 'TrASp' ? 'Automatic' : 'Manual';
device.data.tBar.percent = Number.parseInt(packetParts[1], 10) / 10;
if (device.data.tBar.percent === 0) {
device.data.tBar.status = 'Stop';
}
device.update('tbar', device.data);
} else if (packetType === 'TrAStart' || packetType === 'TrAStop') {
if (device.data.tBar === undefined) {
device.data.tBar = {};
}
device.data.tBar.status = packetType.slice(3);
if (packetType === 'TrAStop') {
device.data.tBar.percent = 0;
}
device.update('tbar', device.data);
}
});
};
+179
View File
@@ -0,0 +1,179 @@
body {
background-color: #191919;
}
h1 {
font-family: 'Open Sans', sans-serif;
}
h5 {
color: white;
margin: 0px 0px 10px 3px;
font-weight: bold;
width: 100%;
/* font-family: 'Open Sans', sans-serif; */
}
.lss-input {
width: 120px;
height: 40px;
background-color: #242424;
color: white;
border: #242424 6px solid;
outline: black 1px solid;
outline-offset: -1px;
}
.lss-button {
width: 80px;
height: 30px;
background: linear-gradient(#363636, #282828);
border-radius: 5px;
border: black 2px solid;
color: gray;
margin-bottom: 5px;
}
.source-wrapper {
display: flex;
flex-wrap: wrap;
background-color: #000000;
width: 122px;
border: black 1px solid;
}
.lss-red {
border-color: #6f1607;
outline-color: #ff0000;
}
.lss-button.lss-red {
color: #6f1607;
}
.lss-green {
border-color: #395a13;
outline-color: #558522;
}
.lss-disabled {
color: #3a3a3a;
}
.lss-ftb-glow {
border: gray 2px solid;
animation: ftb-glow 0.75s infinite alternate;
}
@keyframes ftb-glow {
0% {
border-color: #666666;
color: #666666;
}
100% {
border-color: #fc584b;
color: #fc584b;
}
}
.source-label {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 11px;
font-weight: bold;
}
.inputs-container {
width: 200px;
}
.transition-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.transition-settings {
display: flex;
flex-direction: column;
justify-content: space-between;
margin-right: 30px;
}
.tbar-container {
display: flex;
flex-direction: column-reverse;
background-color: red;
border: 2px black solid;
width: 80px;
height: 176px;
position: relative;
margin-bottom: 10px;
}
.tbar-bg {
position: absolute;
background-image: url('img/tbar_bg.png');
width: 80px;
height: 176px;
}
.tbar-handle {
position: absolute;
background-image: url('img/tbar_handle.png');
width: 42px;
height: 20px;
left: 19px;
bottom: 10px;
}
.tbar-div {
width: 100%;
background-color: #7aff58;
overflow: hidden;
}
.fade-to-black-container {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.fade-to-black .source-wrapper {
display: flex;
width: 100%;
padding: 5px;
justify-content: center;
}
.fade-to-black h3 {
text-align: center;
}
.clear {
background: transparent;
border-color: transparent;
background-color: transparent;
}
.hide {
display: none;
}
.no-wrap {
flex-wrap: nowrap;
}
.show-small {
display: none;
}
@media screen and (min-width: 0px) and (max-width: 480px) {
.hide-small {
display: none;
}
.show-small {
display: block;
}
}
.float-left {
float: left;
}
+66
View File
@@ -0,0 +1,66 @@
<header>
<h1><%= listName %></h1>
</header>
<% if (data.inputCount) { %>
<div class="inputs-container float-left">
<h5>INPUTS</h5>
<div class="source-wrapper">
<% data.inputs.forEach((input)=>{ %>
<div id="input-<%= input.number %>" class="lss-input">
<div class="source-label"><%= input.name %></div>
</div>
<% }) %>
</div>
</div>
</div>
<div class="transition-container float-left">
<div class="transition-settings">
<!-- <div class="next-transition">
<h3>Next Transition</h3>
<div class="source-wrapper" style="width: 260px;">
<div class="lss-input clear"></div>
<div id="key-1-onair" class="lss-input">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="key-2-onair" class="lss-input">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="key-3-onair" class="lss-input">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="key-4-onair" class="lss-input">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
</div>
</div> -->
<div class="transition-style">
<h5>TRANSITION</h5>
<div id="cut" class="lss-button">
<div class="source-label">CUT</div>
</div>
<div id="auto" class="lss-button">
<div class="source-label">AUTO</div>
</div>
<div class="tbar-container">
<div class="tbar-bg"></div>
<div id="tbar-handle-div" class="tbar-handle"></div>
<div id="tbar-div" class="tbar-div" ></div>
</div>
<div id="ftb" class="lss-button">
<div class="source-label">FTB</div>
</div>
</div>
</div>
</div>
<% } %>