Created
November 11, 2022 15:54
-
-
Save smitmartijn/2d91ca465092e8104c1ed86778bb5625 to your computer and use it in GitHub Desktop.
MuteDeck Stream Deck Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* global $CC, Utils, $SD */ | |
/* THIRD PARTY */ | |
// https://github.com/joewalnes/reconnecting-websocket/blob/master/reconnecting-websocket.js | |
(function(global, factory) { | |
if (typeof define === 'function' && define.amd) { | |
define([], factory); | |
} else if (typeof module !== 'undefined' && module.exports) { | |
module.exports = factory(); | |
} else { | |
global.ReconnectingWebSocket = factory(); | |
} | |
})(this, function() { | |
if (!('WebSocket' in window)) { | |
return; | |
} | |
function ReconnectingWebSocket(url, protocols, options) { | |
// Default settings | |
var settings = { | |
/** Whether this instance should log debug messages. */ | |
debug: false, | |
/** Whether or not the websocket should attempt to connect immediately upon instantiation. */ | |
automaticOpen: true, | |
/** The number of milliseconds to delay before attempting to reconnect. */ | |
reconnectInterval: 1000, | |
/** The maximum number of milliseconds to delay a reconnection attempt. */ | |
maxReconnectInterval: 30000, | |
/** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */ | |
reconnectDecay: 1.5, | |
/** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */ | |
timeoutInterval: 2000, | |
/** The maximum number of reconnection attempts to make. Unlimited if null. */ | |
maxReconnectAttempts: null, | |
/** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */ | |
binaryType: 'blob' | |
} | |
if (!options) { options = {}; } | |
// Overwrite and define settings with options if they exist. | |
for (var key in settings) { | |
if (typeof options[key] !== 'undefined') { | |
this[key] = options[key]; | |
} else { | |
this[key] = settings[key]; | |
} | |
} | |
// These should be treated as read-only properties | |
/** The URL as resolved by the constructor. This is always an absolute URL. Read only. */ | |
this.url = url; | |
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */ | |
this.reconnectAttempts = 0; | |
/** | |
* The current state of the connection. | |
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED | |
* Read only. | |
*/ | |
this.readyState = WebSocket.CONNECTING; | |
/** | |
* A string indicating the name of the sub-protocol the server selected; this will be one of | |
* the strings specified in the protocols parameter when creating the WebSocket object. | |
* Read only. | |
*/ | |
this.protocol = null; | |
// Private state variables | |
var self = this; | |
var ws; | |
var forcedClose = false; | |
var timedOut = false; | |
var eventTarget = document.createElement('div'); | |
// Wire up "on*" properties as event handlers | |
eventTarget.addEventListener('open', function(event) { self.onopen(event); }); | |
eventTarget.addEventListener('close', function(event) { self.onclose(event); }); | |
eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); }); | |
eventTarget.addEventListener('message', function(event) { self.onmessage(event); }); | |
eventTarget.addEventListener('error', function(event) { self.onerror(event); }); | |
// Expose the API required by EventTarget | |
this.addEventListener = eventTarget.addEventListener.bind(eventTarget); | |
this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget); | |
this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget); | |
/** | |
* This function generates an event that is compatible with standard | |
* compliant browsers and IE9 - IE11 | |
* | |
* This will prevent the error: | |
* Object doesn't support this action | |
* | |
* http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563 | |
* @param s String The name that the event should use | |
* @param args Object an optional object that the event will use | |
*/ | |
function generateEvent(s, args) { | |
var evt = document.createEvent("CustomEvent"); | |
evt.initCustomEvent(s, false, false, args); | |
return evt; | |
}; | |
this.open = function(reconnectAttempt) { | |
ws = new WebSocket(self.url, protocols || []); | |
ws.binaryType = this.binaryType; | |
if (reconnectAttempt) { | |
if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) { | |
return; | |
} | |
} else { | |
eventTarget.dispatchEvent(generateEvent('connecting')); | |
this.reconnectAttempts = 0; | |
} | |
if (self.debug || ReconnectingWebSocket.debugAll) { | |
console.debug('ReconnectingWebSocket', 'attempt-connect', self.url); | |
} | |
var localWs = ws; | |
var timeout = setTimeout(function() { | |
if (self.debug || ReconnectingWebSocket.debugAll) { | |
console.debug('ReconnectingWebSocket', 'connection-timeout', self.url); | |
} | |
timedOut = true; | |
localWs.close(); | |
timedOut = false; | |
}, self.timeoutInterval); | |
ws.onopen = function(event) { | |
clearTimeout(timeout); | |
if (self.debug || ReconnectingWebSocket.debugAll) { | |
console.debug('ReconnectingWebSocket', 'onopen', self.url); | |
} | |
self.protocol = ws.protocol; | |
self.readyState = WebSocket.OPEN; | |
self.reconnectAttempts = 0; | |
var e = generateEvent('open'); | |
e.isReconnect = reconnectAttempt; | |
reconnectAttempt = false; | |
eventTarget.dispatchEvent(e); | |
}; | |
ws.onclose = function(event) { | |
clearTimeout(timeout); | |
ws = null; | |
if (forcedClose) { | |
self.readyState = WebSocket.CLOSED; | |
eventTarget.dispatchEvent(generateEvent('close')); | |
} else { | |
self.readyState = WebSocket.CONNECTING; | |
var e = generateEvent('connecting'); | |
e.code = event.code; | |
e.reason = event.reason; | |
e.wasClean = event.wasClean; | |
eventTarget.dispatchEvent(e); | |
if (!reconnectAttempt && !timedOut) { | |
if (self.debug || ReconnectingWebSocket.debugAll) { | |
console.debug('ReconnectingWebSocket', 'onclose', self.url); | |
} | |
eventTarget.dispatchEvent(generateEvent('close')); | |
} | |
var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts); | |
setTimeout(function() { | |
self.reconnectAttempts++; | |
self.open(true); | |
}, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout); | |
} | |
}; | |
ws.onmessage = function(event) { | |
if (self.debug || ReconnectingWebSocket.debugAll) { | |
console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data); | |
} | |
var e = generateEvent('message'); | |
e.data = event.data; | |
eventTarget.dispatchEvent(e); | |
}; | |
ws.onerror = function(event) { | |
if (self.debug || ReconnectingWebSocket.debugAll) { | |
console.debug('ReconnectingWebSocket', 'onerror', self.url, event); | |
} | |
eventTarget.dispatchEvent(generateEvent('error')); | |
}; | |
} | |
// Whether or not to create a websocket upon instantiation | |
if (this.automaticOpen == true) { | |
this.open(false); | |
} | |
/** | |
* Transmits data to the server over the WebSocket connection. | |
* | |
* @param data a text string, ArrayBuffer or Blob to send to the server. | |
*/ | |
this.send = function(data) { | |
if (ws) { | |
if (self.debug || ReconnectingWebSocket.debugAll) { | |
console.debug('ReconnectingWebSocket', 'send', self.url, data); | |
} | |
return ws.send(data); | |
} else { | |
throw 'INVALID_STATE_ERR : Pausing to reconnect websocket'; | |
} | |
}; | |
/** | |
* Closes the WebSocket connection or connection attempt, if any. | |
* If the connection is already CLOSED, this method does nothing. | |
*/ | |
this.close = function(code, reason) { | |
// Default CLOSE_NORMAL code | |
if (typeof code == 'undefined') { | |
code = 1000; | |
} | |
forcedClose = true; | |
if (ws) { | |
ws.close(code, reason); | |
} | |
}; | |
/** | |
* Additional public API method to refresh the connection if still open (close, re-open). | |
* For example, if the app suspects bad data / missed heart beats, it can try to refresh. | |
*/ | |
this.refresh = function() { | |
if (ws) { | |
ws.close(); | |
} | |
}; | |
} | |
/** | |
* An event listener to be called when the WebSocket connection's readyState changes to OPEN; | |
* this indicates that the connection is ready to send and receive data. | |
*/ | |
ReconnectingWebSocket.prototype.onopen = function(event) {}; | |
/** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */ | |
ReconnectingWebSocket.prototype.onclose = function(event) {}; | |
/** An event listener to be called when a connection begins being attempted. */ | |
ReconnectingWebSocket.prototype.onconnecting = function(event) {}; | |
/** An event listener to be called when a message is received from the server. */ | |
ReconnectingWebSocket.prototype.onmessage = function(event) {}; | |
/** An event listener to be called when an error occurs. */ | |
ReconnectingWebSocket.prototype.onerror = function(event) {}; | |
/** | |
* Whether all instances of ReconnectingWebSocket should log debug messages. | |
* Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true. | |
*/ | |
ReconnectingWebSocket.debugAll = false; | |
ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING; | |
ReconnectingWebSocket.OPEN = WebSocket.OPEN; | |
ReconnectingWebSocket.CLOSING = WebSocket.CLOSING; | |
ReconnectingWebSocket.CLOSED = WebSocket.CLOSED; | |
return ReconnectingWebSocket; | |
}); | |
/* END THIRD PARTY */ | |
/** | |
* The 'connected' event is sent to your plugin, after the plugin's instance | |
* is registered with Stream Deck software. It carries the current websocket | |
* and other information about the current environmet in a JSON object | |
* You can use it to subscribe to events you want to use in your plugin. | |
*/ | |
$SD.on("connected", (jsonObj) => connected(jsonObj)); | |
var actionUUIDs = [ | |
"togglemute", | |
"togglevideo", | |
"togglesharing", | |
"togglerecording", | |
"leavemeeting", | |
]; | |
// this instantiates the timer that checks the MuteDeck API and changes the button images | |
let mdwebsocket = null; //new MuteDeckWebsocket(); | |
let watcher = null; //new MuteDeckWatcher(); | |
setTimeout(function() { | |
mdwebsocket = new MuteDeckWebsocket(); | |
watcher = new MuteDeckWatcher(); | |
}, 500); | |
function connected(jsn) { | |
for (var i = 0; i < actionUUIDs.length; i++) { | |
var actionName = "com.mutedeck.plugin." + actionUUIDs[i]; | |
// Subscribe to the required events, that the `action` object will handle | |
// onWillAppear: fired when a button is added to the SD canvas | |
$SD.on(actionName + ".willAppear", (jsonObj) => | |
action.onWillAppear(jsonObj) | |
); | |
// onWillDisappear: fired when a button is removed from the SD canvas | |
$SD.on(actionName + ".willDisappear", (jsonObj) => | |
action.onWillDisappear(jsonObj) | |
); | |
// keyUp: when a key is pressed | |
$SD.on(actionName + ".keyUp", (jsonObj) => action.onKeyUp(jsonObj)); | |
// didReceiveSettings: settings of the key changed in the SD software | |
$SD.on(actionName + ".didReceiveSettings", (jsonObj) => | |
action.onDidReceiveSettings(jsonObj) | |
); | |
} | |
} | |
// this async function | |
async function postMuteDeckActionAsync(action) { | |
if (action == 'leave') { | |
action = 'leave_meeting'; | |
} else { | |
action = 'toggle_' + action; | |
} | |
// send via the websocket if it's open, otherwise fall back to regular API | |
if (mdwebsocket.socket.readyState === WebSocket.OPEN) { | |
const message = { | |
'source': 'streamdeck-plugin', | |
'action': action | |
}; | |
console.log("Sending message via websocket to MuteDeck: " + message); | |
mdwebsocket.socket.send(JSON.stringify(message)); | |
return; | |
} else { | |
var url = "http://127.0.0.1:3491/v1/" + action; | |
console.log("Performing POST to MuteDeck: " + url); | |
const resp = await fetch(url, { | |
method: "POST", | |
}); | |
console.log("Response: "); | |
console.log(resp); | |
if (resp.status != 200) { | |
return null; | |
} | |
const payload = await resp.json(); | |
return payload; | |
} | |
} | |
// The action object. Here's where all button actions to go. | |
const action = { | |
// this is a static object, so these vars will persist through the plugin runtime | |
// buttonCache will contain all the button objects, i.e. to be used when updating the MuteDeck status | |
buttonCache: {}, | |
// stateCache contains the current state of an action. Used to prevent mindless updates to buttons and only change them when necessary | |
stateCache: {}, | |
getButtonFromCache: function(context) { | |
//console.log("getButtonFromCache: " + context); | |
return this.buttonCache[context]; | |
}, | |
getButtons: function() { | |
return this.buttonCache; | |
}, | |
addButtonToCache: function(context, obj) { | |
//console.log("addButtonToCache: " + context + " obj: "); | |
//console.log(obj); | |
this.buttonCache[context] = obj; | |
this.stateCache[context] = "unknown"; | |
}, | |
removeButtonFromCache: function(context) { | |
//console.log("removeButtonFromCache: " + context); | |
if (this.getButtonFromCache(context)) { | |
delete this.buttonCache[context]; | |
} | |
}, | |
getStateCache: function(context) { | |
return this.stateCache[context]; | |
}, | |
setStateCache: function(context, val) { | |
this.stateCache[context] = val; | |
}, | |
onDidReceiveSettings: function(jsn) { | |
console.log( | |
"%c%s", | |
"color: white; background: red; font-size: 15px;", | |
"[app.js]onDidReceiveSettings:" | |
); | |
}, | |
// button removed from the SD canvas, so remove it from our cache and don't try to update it anymore | |
onWillDisappear: function(jsn) { | |
this.removeButtonFromCache(jsn.context); | |
}, | |
// button added to the SD canvas, add it to the cache and start updating it | |
onWillAppear: function(jsn) { | |
this.addButtonToCache(jsn.context, jsn); | |
}, | |
// user interaction, woo! | |
onKeyUp: function(jsn) { | |
var btn_action = jsn.action; | |
console.log("onKeyUp for: " + btn_action); | |
var action_url = ""; | |
if (btn_action == "com.mutedeck.plugin.togglemute") { | |
action_url = "mute"; | |
} else if (btn_action == "com.mutedeck.plugin.togglevideo") { | |
action_url = "video"; | |
} else if (btn_action == "com.mutedeck.plugin.togglesharing") { | |
action_url = "share"; | |
} else if (btn_action == "com.mutedeck.plugin.togglerecording") { | |
action_url = "record"; | |
} else if (btn_action == "com.mutedeck.plugin.leavemeeting") { | |
action_url = "leave"; | |
} | |
// shouldn't be possible, but just to be safe | |
if (action_url) { | |
// this is an async function, so we won't wait on it | |
postMuteDeckActionAsync(action_url); | |
} | |
}, | |
}; | |
function MuteDeckWatcher() { | |
var timer = 0; | |
function start() { | |
// don't start the timer twice | |
if (timer !== 0) { | |
return; | |
} | |
console.log("Starting MuteDeck poller"); | |
// call the MuteDeck status API right away and start a 500 ms timer | |
refreshMuteDeckStatusAsync(); | |
timer = setInterval(function(sx) { | |
refreshMuteDeckStatusAsync(); | |
}, 500); | |
} | |
function stop() { | |
// don't stop if it's already stopped | |
if (timer === 0) { | |
return; | |
} | |
console.log("Stopping MuteDeck poller"); | |
window.clearInterval(timer); | |
timer = 0; | |
} | |
async function refreshMuteDeckStatusAsync() { | |
// skip the manual API poll if the websocket is open | |
if (mdwebsocket.socket.readyState === WebSocket.OPEN) { | |
return; | |
} | |
try { | |
let newState = await fetchLastStateAsync(); | |
// don't process empty results | |
if (newState === null) { | |
return; | |
} | |
// now that we have the updated MuteDeck status, go through all buttons and update their image and titles | |
var buttons = action.getButtons(); | |
for (const context in buttons) { | |
var newImageURL = ""; | |
var btn_action = buttons[context].action; | |
var updateButton = false; | |
if (btn_action == "com.mutedeck.plugin.togglemute") { | |
if (newState.mute == "inactive") { | |
newImageURL = "images/microphone-solid.png"; | |
} else if (newState.mute == "active") { | |
newImageURL = "images/microphone-solid-slash.png"; | |
} else { | |
newImageURL = "images/microphone-solid-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.mute) { | |
updateButton = true; | |
action.setStateCache(context, newState.mute); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglevideo") { | |
if (newState.video == "active") { | |
newImageURL = "images/video-solid.png"; | |
} else if (newState.video == "inactive") { | |
newImageURL = "images/video-solid-slash.png"; | |
} else { | |
newImageURL = "images/video-solid-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.video) { | |
updateButton = true; | |
action.setStateCache(context, newState.video); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglesharing") { | |
if (newState.share == "inactive") { | |
newImageURL = "images/share-solid.png"; | |
} else if (newState.share == "active") { | |
newImageURL = "images/share-solid-slash.png"; | |
} else { | |
newImageURL = "images/share-solid-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.share) { | |
updateButton = true; | |
action.setStateCache(context, newState.share); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglerecording") { | |
if (newState.record == "inactive") { | |
newImageURL = "images/record-stop-circle-regular-inactive.png"; | |
} else if (newState.record == "active") { | |
newImageURL = "images/record-stop-circle-solid-active.png"; | |
} else { | |
newImageURL = "images/record-stop-circle-regular-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.record) { | |
updateButton = true; | |
action.setStateCache(context, newState.record); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.leavemeeting") { | |
if (newState.control == "system") { | |
newImageURL = "images/portal-exit-disabled.png"; | |
} else { | |
newImageURL = "images/portal-exit.png"; | |
} | |
if (action.getStateCache(context) != newState.video) { | |
updateButton = true; | |
action.setStateCache(context, newState.video); | |
} | |
} | |
if (updateButton) { | |
Utils.loadImage(newImageURL, function(newImageBase64) { | |
$SD.api.setImage(context, newImageBase64); | |
$SD.api.setTitle(context, ""); | |
}); | |
} | |
} | |
} catch (error) { | |
console.log("Error when fetching the MuteDeck API:"); | |
console.log(error); | |
// this happens when MuteDeck is not running and the API call fails | |
// loop through all the buttons and set them to disabled and that MuteDeck is not found | |
var buttons = action.getButtons(); | |
for (const context in buttons) { | |
var btn_action = buttons[context].action; | |
var updateButton = false; | |
if (btn_action == "com.mutedeck.plugin.togglemute") { | |
newImageURL = "images/microphone-solid-disabled.png"; | |
if (action.getStateCache(context) != "disabled") { | |
updateButton = true; | |
action.setStateCache(context, "disabled"); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglevideo") { | |
newImageURL = "images/video-solid-disabled.png"; | |
if (action.getStateCache(context) != "disabled") { | |
updateButton = true; | |
action.setStateCache(context, "disabled"); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglesharing") { | |
newImageURL = "images/share-solid-disabled.png"; | |
if (action.getStateCache(context) != "disabled") { | |
updateButton = true; | |
action.setStateCache(context, "disabled"); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglerecording") { | |
newImageURL = "images/record-stop-circle-regular-disabled.png"; | |
if (action.getStateCache(context) != "disabled") { | |
updateButton = true; | |
action.setStateCache(context, "disabled"); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.leavemeeting") { | |
newImageURL = "images/portal-exit-disabled.png"; | |
if (action.getStateCache(context) != "disabled") { | |
updateButton = true; | |
action.setStateCache(context, "disabled"); | |
} | |
} | |
if (updateButton) { | |
Utils.loadImage(newImageURL, function(newImageBase64) { | |
$SD.api.setImage(context, newImageBase64); | |
$SD.api.setTitle(context, "MuteDeck\nnot found"); | |
}); | |
} | |
} | |
return; | |
} | |
} // end async function refreshMuteDeckStatusAsync() { | |
// this function gets the latest MuteDeck status via the API | |
async function fetchLastStateAsync() { | |
const resp = await fetch(`http://127.0.0.1:3491/v1/status`); | |
if (resp.status != 200) { | |
return null; | |
} | |
// convert it to | |
const payload = await resp.json(); | |
return payload; | |
} | |
// start the timer when instantiating the object | |
start(); | |
// export these functions | |
return { | |
timer: timer, | |
refreshMuteDeckStatusAsync: refreshMuteDeckStatusAsync, | |
stop: stop, | |
}; | |
} | |
function updateButtonStates(newState) { | |
//console.log('updateButtonStates:'); | |
//console.log(newState); | |
// now that we have the updated MuteDeck status, go through all buttons and update their image and titles | |
var buttons = action.getButtons(); | |
for (const context in buttons) { | |
var newImageURL = ""; | |
var btn_action = buttons[context].action; | |
var updateButton = false; | |
if (btn_action == "com.mutedeck.plugin.togglemute") { | |
if (newState.mute == "inactive") { | |
newImageURL = "images/microphone-solid.png"; | |
} else if (newState.mute == "active") { | |
newImageURL = "images/microphone-solid-slash.png"; | |
} else { | |
newImageURL = "images/microphone-solid-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.mute) { | |
updateButton = true; | |
action.setStateCache(context, newState.mute); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglevideo") { | |
if (newState.video == "active") { | |
newImageURL = "images/video-solid.png"; | |
} else if (newState.video == "inactive") { | |
newImageURL = "images/video-solid-slash.png"; | |
} else { | |
newImageURL = "images/video-solid-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.video) { | |
updateButton = true; | |
action.setStateCache(context, newState.video); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglesharing") { | |
if (newState.share == "inactive") { | |
newImageURL = "images/share-solid.png"; | |
} else if (newState.share == "active") { | |
newImageURL = "images/share-solid-slash.png"; | |
} else { | |
newImageURL = "images/share-solid-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.share) { | |
updateButton = true; | |
action.setStateCache(context, newState.share); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.togglerecording") { | |
if (newState.record == "inactive") { | |
newImageURL = "images/record-stop-circle-regular-inactive.png"; | |
} else if (newState.record == "active") { | |
newImageURL = "images/record-stop-circle-solid-active.png"; | |
} else { | |
newImageURL = "images/record-stop-circle-regular-disabled.png"; | |
} | |
if (action.getStateCache(context) != newState.record) { | |
updateButton = true; | |
action.setStateCache(context, newState.record); | |
} | |
} else if (btn_action == "com.mutedeck.plugin.leavemeeting") { | |
if (newState.video == "disabled") { | |
newImageURL = "images/portal-exit-disabled.png"; | |
} else { | |
newImageURL = "images/portal-exit.png"; | |
} | |
if (action.getStateCache(context) != newState.video) { | |
updateButton = true; | |
action.setStateCache(context, newState.video); | |
} | |
} | |
if (updateButton) { | |
Utils.loadImage(newImageURL, function(newImageBase64) { | |
$SD.api.setImage(context, newImageBase64); | |
$SD.api.setTitle(context, ""); | |
}); | |
} | |
} | |
} | |
function MuteDeckWebsocket() { | |
var socket; | |
function connect() { | |
console.log("Connecting to MuteDeck websocket"); | |
// get the websocket host and port from localstorage and reinitialise the websocket | |
var mutedeck_host = 'localhost'; | |
var mutedeck_port = 3492; | |
let websocket_url = 'ws://' + mutedeck_host + ':' + mutedeck_port; | |
console.log(websocket_url); | |
socket = new ReconnectingWebSocket(websocket_url); | |
socket.addEventListener('open', () => { | |
console.log(`[open] Connected to MuteDeck`); | |
// identify as a streamdeck plugin, so MD can send messages back | |
const identify = { | |
'source': 'streamdeck-plugin', | |
'action': 'identify' | |
}; | |
socket.send(JSON.stringify(identify)); | |
}); | |
socket.onclose = function(event) { | |
if (event.wasClean) { | |
console.log(`[close] Connection to MuteDeck closed cleanly, code=${event.code} reason=${event.reason}`); | |
} else { | |
console.log(`[close] Connection to MuteDeck closed unexpected, code=${event.code} reason=${event.reason}`); | |
} | |
}; | |
socket.onerror = function(error) { | |
console.log(`[error] ${error.message}`); | |
}; | |
socket.addEventListener('message', function(event) { | |
console.log(`[websocket] received event: ${event.data}`); | |
var message = JSON.parse(event.data); | |
if (message.action === 'update-status') { | |
updateButtonStates(message); | |
} else { | |
console.log('Dont know this action: ' + message.action); | |
} | |
}); | |
} | |
// connect when instantiating the object | |
connect(); | |
// export these functions | |
return { | |
socket: socket | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment