Files
video-shader-toys/web/app.js
2026-05-02 14:20:38 +10:00

93 lines
2.8 KiB
JavaScript

const state = {
shaders: []
};
const el = (id) => document.getElementById(id);
async function api(path, options = {}) {
const response = await fetch(path, {
headers: { "content-type": "application/json" },
...options
});
if (!response.ok) {
const text = await response.text();
throw new Error(text || response.statusText);
}
return response.json();
}
function renderStatus(status) {
el("device").textContent = status.deviceName || "No DeckLink device selected";
el("running").textContent = status.running ? "Running" : "Stopped";
el("mode").textContent = status.mode || "No signal";
el("outputFormat").textContent = status.outputFormat || "Unavailable";
el("frames").textContent = `${status.framesCaptured ?? 0} / ${status.framesOutput ?? 0}`;
el("frameRate").textContent = Number(status.frameRate || 0).toFixed(2);
el("dropped").textContent = status.framesDropped ?? 0;
el("error").textContent = status.error || "";
}
function renderShaders(payload) {
state.shaders = payload.shaders || [];
const host = el("shaders");
host.innerHTML = "";
for (const shader of state.shaders) {
const card = document.createElement("div");
card.className = "shader";
const amount = shader.parameters.find((p) => p.id === "amount");
card.innerHTML = `
<header>
<strong>${shader.name}</strong>
<span>${shader.type}</span>
</header>
<label>
<span>${amount.label}</span>
<input type="range" min="${amount.min}" max="${amount.max}" step="0.01" value="${amount.value}">
<output>${Number(amount.value).toFixed(2)}</output>
</label>
`;
const input = card.querySelector("input");
const output = card.querySelector("output");
input.addEventListener("input", async () => {
output.textContent = Number(input.value).toFixed(2);
await api(`/api/shaders/${shader.id}/parameters`, {
method: "PATCH",
body: JSON.stringify({ amount: Number(input.value) })
});
});
host.appendChild(card);
}
}
async function refresh() {
renderStatus(await api("/api/status"));
renderShaders(await api("/api/shaders"));
}
function connectWs() {
const ws = new WebSocket(`ws://${location.host}/ws`);
ws.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.type === "state") {
renderStatus(message.status);
renderShaders(message.shaders);
}
});
ws.addEventListener("close", () => setTimeout(connectWs, 1000));
}
el("start").addEventListener("click", async () => {
try {
renderStatus(await api("/api/pipeline/start", { method: "POST" }));
} catch (error) {
el("error").textContent = error.message;
}
});
el("stop").addEventListener("click", async () => {
renderStatus(await api("/api/pipeline/stop", { method: "POST" }));
});
refresh();
connectWs();