aboutsummaryrefslogtreecommitdiffstats
path: root/static/modules
diff options
context:
space:
mode:
Diffstat (limited to 'static/modules')
-rw-r--r--static/modules/obs.js104
1 files changed, 104 insertions, 0 deletions
diff --git a/static/modules/obs.js b/static/modules/obs.js
new file mode 100644
index 0000000..c6a3ebc
--- /dev/null
+++ b/static/modules/obs.js
@@ -0,0 +1,104 @@
+const clamp = (v) => Math.max(0, Math.min(1, v));
+
+async function post(action, body = {}) {
+ try {
+ await fetch(`/api/obs/${action}`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ });
+ } catch {
+ // the next poll shows the real state
+ }
+}
+
+function button(label) {
+ const b = document.createElement("button");
+ b.textContent = label;
+ return b;
+}
+
+export default {
+ title: "OBS",
+ interval: 1,
+
+ mount(el) {
+ const wrap = document.createElement("div");
+ wrap.className = "obs";
+ wrap.innerHTML = `
+ <div class="row obs-controls"></div>
+ <div class="obs-scenes"></div>
+ <div class="obs-audio"></div>
+ <div class="obs-sources"></div>`;
+ el.append(wrap);
+ },
+
+ update(state, el) {
+ const controls = el.querySelector(".obs-controls");
+ const scenes = el.querySelector(".obs-scenes");
+ const audio = el.querySelector(".obs-audio");
+ const sources = el.querySelector(".obs-sources");
+
+ if (!state.connected) {
+ controls.textContent = "OBS offline";
+ scenes.textContent = audio.textContent = sources.textContent = "";
+ return;
+ }
+
+ controls.textContent = "";
+ const rec = button(state.recording ? "Stop recording" : "Record");
+ rec.classList.toggle("on", state.recording);
+ rec.onclick = () => post("record", { on: !state.recording });
+ const stream = button(state.streaming ? "Stop stream" : "Stream");
+ stream.classList.toggle("on", state.streaming);
+ stream.onclick = () => post("stream", { on: !state.streaming });
+ controls.append(rec, stream);
+
+ if (state.transitions?.length) {
+ const sel = document.createElement("select");
+ for (const t of state.transitions) {
+ const o = document.createElement("option");
+ o.value = o.textContent = t;
+ if (t === state.transition) o.selected = true;
+ sel.append(o);
+ }
+ sel.onchange = () => post("transition", { name: sel.value });
+ const trigger = button("Transition");
+ trigger.onclick = () => post("trigger");
+ controls.append(sel, trigger);
+ }
+
+ scenes.textContent = "";
+ for (const name of state.scenes ?? []) {
+ const b = button(name);
+ b.classList.toggle("on", name === state.currentScene);
+ b.onclick = () => post("scene", { scene: name });
+ scenes.append(b);
+ }
+
+ audio.textContent = "";
+ for (const inp of state.inputs ?? []) {
+ const row = document.createElement("div");
+ row.className = "row";
+ const label = document.createElement("span");
+ label.textContent = inp.name;
+ const mute = button(inp.muted ? "Unmute" : "Mute");
+ mute.classList.toggle("on", inp.muted);
+ mute.onclick = () => post("mute", { input: inp.name, muted: !inp.muted });
+ const down = button("−");
+ down.onclick = () => post("volume", { input: inp.name, level: clamp(inp.volume - 0.1) });
+ const up = button("+");
+ up.onclick = () => post("volume", { input: inp.name, level: clamp(inp.volume + 0.1) });
+ row.append(label, mute, down, up);
+ audio.append(row);
+ }
+
+ sources.textContent = "";
+ for (const s of state.sources ?? []) {
+ const b = button(s.name);
+ b.classList.toggle("on", s.on);
+ b.onclick = () => post("source", { scene: state.currentScene, id: s.id, on: !s.on });
+ sources.append(b);
+ }
+ },
+};