import { api } from "./api.js"; let bad_monitor = false; let last_controller = undefined; function show_error(msg) { const err = document.getElementById("error"); err.textContent = msg; err.classList.remove("invisible"); } function reset_error() { const err = document.getElementById("error"); err.classList.add("invisible"); } function create_device(device) { const element = document.createElement("li"); element.id = `dev_${device.address}`; const text = document.createElement("span"); text.classList.add("device_text"); element.appendChild(text); const play = document.createElement("button"); play.classList.add("device_play"); play.classList.add("material-symbols-outlined"); play.textContent = "play_arrow"; play.addEventListener("click", () => { api.device_action(device.address, "play") .then(() => { reset_error(); if (bad_monitor) { fetch_device(device.address); } }) .catch((err) => { show_error(`Unable to start playing: ${err}`); }); }); element.appendChild(play); const pause = document.createElement("button"); pause.classList.add("device_pause"); pause.classList.add("material-symbols-outlined"); pause.textContent = "pause"; pause.addEventListener("click", () => { api.device_action(device.address, "pause") .then(() => { reset_error(); if (bad_monitor) { fetch_device(device.address); } }) .catch((err) => { show_error(`Unable to pause: ${err}`); }); }); element.appendChild(pause); return element; } function update_device(child, device) { const text = child.children.item(0); const play = child.children.item(1); const pause = child.children.item(2); if (device.playing !== null) { text.textContent = `${device.name} ${device.playing.status} ${device.playing.title}`; play.classList.remove("invisible"); play.disabled = device.playing.status === "playing"; pause.classList.remove("invisible"); pause.disabled = !play.disabled; } else { text.textContent = device.name; play.classList.add("invisible"); pause.classList.add("invisible"); } } function upsert_device(list, index, device) { let child; if (index >= list.childElementCount) { child = create_device(device); list.appendChild(child); } else { child = list.children.item(index); } child.id = `dev_${device.address}`; update_device(child, device); } function remove_devices(list, index) { while (index < list.childElementCount) { list.removeChild(list.children.item(index)); } } function update_playing(controller) { const list = document.getElementById("playing"); let index = 0; controller.devices.forEach((device) => { if (device.paired && device.connected && device.playing !== null) { upsert_device(list, index++, device); } }); remove_devices(list, index); } function update_connected(controller) { const list = document.getElementById("connected"); let index = 0; controller.devices.forEach((device) => { if (device.paired && device.connected && device.playing === null) { upsert_device(list, index++, device); } }); remove_devices(list, index); } function update_paired(controller) { const list = document.getElementById("paired"); let index = 0; controller.devices.forEach((device) => { if (device.paired && !device.connected) { upsert_device(list, index++, device); } }); remove_devices(list, index); } function fetch_controller() { api.controller() .then((controller) => { reset_error(); last_controller = controller; const enable_pairing = document.getElementById("enable_pairing"); const disable_pairing = document.getElementById("disable_pairing"); const pairing_status = document.getElementById("pairing_status"); if (controller.discoverable === true) { enable_pairing.classList.add("invisible"); enable_pairing.disabled = true; disable_pairing.classList.remove("invisible"); disable_pairing.disabled = false; pairing_status.textContent = `Pair with ${controller.name}...`; pairing_status.classList.remove("invisible"); } else { enable_pairing.classList.remove("invisible"); enable_pairing.disabled = controller.pairable !== true; disable_pairing.classList.add("invisible"); disable_pairing.disabled = true; pairing_status.classList.add("invisible"); } update_playing(controller); update_connected(controller); update_paired(controller); }) .catch((err) => { console.err(err); show_error("Unable to get controller from api"); }); } function fetch_device(address) { api.device(address) .then((device) => { reset_error(); let parent; if (device.paired && device.connected) { if (device.playing !== null) { parent = document.getElementById("playing"); } else { parent = document.getElementById("connected"); } } else if (device.paired) { parent = document.getElementById("paired"); } else { parent = null; } let child = document.getElementById(`dev_${device.address}`); if (child === null) { if (parent === null) return; child = create_device(device); } update_device(child, device); if (parent === null) { child.parentElement.removeChild(child); } else if (parent !== child.parentElement) { parent.appendChild(child); } }) .catch(() => { // Fallback to updating whole controller if device fails fetch_controller(); }); } function monitor() { const schema = location.protocol === "https:" ? "wss:" : "ws:"; let path = location.pathname; if (path.endsWith("/index.html")) { path = path.substring(0, path.length - 10); } const url = `${schema}//${location.host}${path}api/v1/events`; const socket = new WebSocket(url); socket.addEventListener("message", (event) => { if (event.data === "controller/update") { fetch_controller(); } else if (event.data.startsWith("device/update/")) { const device_address = event.data.substring(14); fetch_device(device_address); } }); socket.addEventListener("error", (event) => { bad_monitor = true; console.error(event); }); socket.addEventListener("close", () => { bad_monitor = true; }); socket.addEventListener("open", () => { bad_monitor = false; }); } function init() { const enable_pairing = document.getElementById("enable_pairing"); const disable_pairing = document.getElementById("disable_pairing"); enable_pairing.addEventListener("click", () => { enable_pairing.disabled = true; api.controller_discoverable("true") .then(() => { if (bad_monitor) { fetch_controller(); if ( last_controller !== undefined && last_controller.discover_timeout_seconds > 0 ) { setTimeout(() => { fetch_controller(); }, last_controller.discover_timeout_seconds * 1000); } } }) .catch((err) => { show_error(`Unable to enable pairing: ${err}`); }); }); disable_pairing.addEventListener("click", () => { disable_pairing.disabled = true; api.controller_discoverable("false") .then(() => { if (bad_monitor) fetch_controller(); }) .catch((err) => { show_error(`Unable to disable pairing: ${err}`); }); }); api.status() .then(() => { fetch_controller(); monitor(); document.addEventListener("visibilitychange", () => { if (bad_monitor && !document.hidden) { // Try to reconnect websocket when page is activated. monitor(); } }); }) .catch((err) => { show_error(`Unable to connect to api: ${err}`); }); } if (document.readyState === "loading") { addEventListener("DOMContentLoaded", () => { init(); }); } else { init(); }