Skip to content

Commit

Permalink
Add a streamdeck plugin
Browse files Browse the repository at this point in the history
Requires the jslib from the web UI copying in as 'StreamingRemote.js'
  • Loading branch information
fredemmott committed Jan 10, 2019
1 parent 878ec06 commit 37bffe6
Show file tree
Hide file tree
Showing 12 changed files with 3,173 additions and 0 deletions.
309 changes: 309 additions & 0 deletions com.fredemmott.streamingremote.sdPlugin/app.html
@@ -0,0 +1,309 @@
<!DOCTYPE HTML>
<html>

<head>
<title>com.fredemmott.streamingRemote</title>
<script src="StreamingRemote.js"></script>
<meta charset="utf-8" />
</head>

<body>
<script>
async function getOutputs(uri, password) {
const {rpc, ws} = await createClient(uri, password);
const outputs = await rpc.getOutputs();
ws.close();
return outputs;
}

function createClient(uri, password) {
const ws = new WebSocket(uri);
ws.binaryType = 'arraybuffer';
return new Promise((resolve, reject) => {
ws.addEventListener('open', async () => {
const handshakeState = await StreamingRemote.handshake(ws, password);
const rpc = new StreamingRemote.RPCClient(ws, handshakeState);
rpc.onHelloNotification(() => resolve({rpc, ws}));
});
ws.addEventListener('close', () => {
reject();
});
});
}

var websocket = null;
var pluginUUID = null;
var images = null;
const rpcStore = {};
const outputStore = {};
const settingsStore = {};

var DestinationEnum = Object.freeze({ "HARDWARE_AND_SOFTWARE": 0, "HARDWARE_ONLY": 1, "SOFTWARE_ONLY": 2 })

var myPlugin = {

type: "com.fredemmott.streamingremote.action",

onKeyDown: function (context, settings, coordinates, userDesiredState) {
},

onKeyUp: function (context, settings, coordinates, userDesiredState) {
const {rpc} = rpcStore[context];
const state = outputStore[context].state;
if (state == 'stopped') {
rpc.startOutput(settings.output);
} else {
rpc.stopOutput(settings.output);
}
},

updateImage: async function (context, settings) {
const {rpc} = rpcStore[context];
const outputs = await rpc.getOutputs();
outputStore[context] = outputs[settings.output];
const {type, state} = outputs[settings.output];
if (!type) {
return;
}
this.setImage(context, type, state);
},

setImage: function (context, type, state) {
var suffix;
switch (state) {
case 'stopped':
suffix = 'Stopped';
break;
case 'starting':
case 'stopping':
suffix = 'Changing';
break;
case 'active':
suffix = 'Active';
break;
}
const key = (type == 'local_recording' ? 'recording' : 'streaming')+suffix;
const image = images[key];
websocket.send(JSON.stringify({
event: 'setImage',
context: context,
payload: { image },
}));
},

onWillAppear: async function (context, settings, coordinates) {
settingsStore[context] = settings;
websocket.send(JSON.stringify({
event: 'showAlert',
context: context,
}));
if (settings.output) {
await this.connectRemote(context, settings);
await this.updateImage(context, settings);
}
},

connectRemote: async function (context, settings) {
const {rpc, ws} = await createClient(settings.uri, settings.password);
rpcStore[context] = {rpc, ws};
outputStore[context] = undefined;
ws.addEventListener('close', () => {
websocket.send(JSON.stringify({
event: 'showAlert',
context: context,
}));
rpcStore[context] = undefined;
outputStore[context] = undefined;
});
ws.addEventListener('error', () => {
websocket.send(JSON.stringify({
event: 'showAlert',
context: context,
}));
rpcStore[context] = undefined;
outputStore[context] = undefined;
});
rpc.onOutputStateChanged((id, state) => {
if (id == settings.output) {
this.setImage(context, outputStore[context].type, state);
}
outputStore[context].state = state;
});
},

displayAndRetryBadConnections: async function() {
Object.keys(settingsStore).map(async context => {
if (rpcStore[context] && outputStore[context]) {
return;
}
websocket.send(JSON.stringify({
event: 'showAlert',
context: context,
}));
if (rpcStore[context]) {
return;
}
try {
const settings = settingsStore[context];
await this.connectRemote(context, settings);
await this.updateImage(context, settings);
} catch (e) {
}
});
},

onSendToPlugin: async function (context, jsonPayload) {
const event = jsonPayload.event;
if (event == "getData") {
const settings = settingsStore[context];
var outputs;
try {
outputs = await getOutputs(settings.uri, settings.password);
} catch (e) {
outputs = {};
}

const json = {
event: "sendToPropertyInspector",
context: context,
payload: { event: "data", outputs, settings }
};
websocket.send(JSON.stringify(json));
return;
}
if (event == 'saveSettings') {
const settings = jsonPayload.settings;
if (settingsStore[context] != settings) {
settingsStore[context] = jsonPayload.settings;
await this.connectRemote(context, settings);
}
websocket.send(JSON.stringify({
event: 'setSettings',
context,
payload: jsonPayload.settings
}));
this.updateImage(context, settings);
var outputs;
try {
outputs = await getOutputs(settings.uri, settings.password);
} catch (e) {
outputs = {};
}
websocket.send(JSON.stringify({
event: 'sendToPropertyInspector',
context,
payload: { event: 'data', outputs, settings }
}));
}
}
};

setInterval(() => myPlugin.displayAndRetryBadConnections(), 1000);

function connectSocket(inPort, inPluginUUID, inRegisterEvent, inInfo) {
pluginUUID = inPluginUUID

// Open the web socket
websocket = new WebSocket("ws://localhost:" + inPort);

function registerPlugin(inPluginUUID) {
var json = {
"event": inRegisterEvent,
"uuid": inPluginUUID
};

websocket.send(JSON.stringify(json));
};

websocket.onopen = function () {
// WebSocket is connected, send message
registerPlugin(pluginUUID);
};

websocket.onmessage = async function (evt) {
// Received message from Stream Deck
var jsonObj = JSON.parse(evt.data);
var event = jsonObj['event'];
var action = jsonObj['action'];
var context = jsonObj['context'];
var jsonPayload = jsonObj['payload'] || {};

if (event == "keyDown") {
var settings = jsonPayload['settings'];
var coordinates = jsonPayload['coordinates'];
var userDesiredState = jsonPayload['userDesiredState'];
myPlugin.onKeyDown(context, settings, coordinates, userDesiredState);
}
else if (event == "keyUp") {
var settings = jsonPayload['settings'];
var coordinates = jsonPayload['coordinates'];
var userDesiredState = jsonPayload['userDesiredState'];
myPlugin.onKeyUp(context, settings, coordinates, userDesiredState);
}
else if (event == "willAppear") {
var settings = jsonPayload['settings'];
var coordinates = jsonPayload['coordinates'];
myPlugin.onWillAppear(context, settings, coordinates);
}
else if (event == "sendToPlugin") {
myPlugin.onSendToPlugin(context, jsonPayload);

}
};

websocket.onclose = function () {
// Websocket is closed
};
};


function loadImageAsDataUri(url) {
return new Promise((resolve, _) => {
var image = new Image();

image.onload = function () {
var canvas = document.createElement("canvas");

canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;

var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
resolve(canvas.toDataURL("image/png"));
};

image.src = url;
});
};

async function loadImages() {
const [
streamingStopped,
streamingChanging,
streamingActive,
recordingStopped,
recordingChanging,
recordingActive
] = await Promise.all([
loadImageAsDataUri('keys/streaming-stopped.png'),
loadImageAsDataUri('keys/streaming-changing.png'),
loadImageAsDataUri('keys/streaming-active.png'),
loadImageAsDataUri('keys/recording-stopped.png'),
loadImageAsDataUri('keys/recording-changing.png'),
loadImageAsDataUri('keys/recording-active.png'),
]);
images = {
streamingStopped,
streamingChanging,
streamingActive,
recordingStopped,
recordingChanging,
recordingActive,
};
}
loadImages();
</script>

</body>

</html>
3 changes: 3 additions & 0 deletions com.fredemmott.streamingremote.sdPlugin/caret.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 37bffe6

Please sign in to comment.