aboutsummaryrefslogtreecommitdiffstats
path: root/static/modules/music.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/modules/music.js')
-rw-r--r--static/modules/music.js63
1 files changed, 46 insertions, 17 deletions
diff --git a/static/modules/music.js b/static/modules/music.js
index de9df7f..e4388f7 100644
--- a/static/modules/music.js
+++ b/static/modules/music.js
@@ -1,9 +1,14 @@
-async function post(action) {
+function clock(s) {
+ s = Math.max(0, Math.floor(s || 0));
+ return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
+}
+
+async function post(action, body = {}) {
try {
await fetch(`/api/music/${action}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: "{}",
+ body: JSON.stringify(body),
});
} catch {
// the next poll shows the real state
@@ -22,27 +27,51 @@ export default {
interval: 2,
mount(el) {
- const box = document.createElement("div");
- box.className = "music";
- box.innerHTML = `
- <div class="now"><b class="title">—</b><span class="artist muted"></span></div>
- <div class="row controls"></div>
- <div class="row vol"></div>`;
- box.querySelector(".controls").append(
- button("⏮", "prev"), button("⏯", "play"), button("⏭", "next"));
- box.querySelector(".vol").append(
+ el.innerHTML = `
+ <div class="top">
+ <div class="art empty">♪</div>
+ <div class="meta">
+ <div class="title">Nothing playing</div>
+ <div class="artist muted"></div>
+ <div class="album muted"></div>
+ </div>
+ </div>
+ <div class="seek">
+ <input type="range" min="0" max="1" step="1" value="0" aria-label="Seek">
+ <span class="time num">0:00</span>
+ </div>
+ <div class="controls"></div>`;
+ el.querySelector(".controls").append(
+ button("⏮", "prev"), button("⏯", "play"), button("⏭", "next"),
button("Vol−", "voldown"), button("Mute", "mute"), button("Vol+", "volup"));
- el.append(box);
+ const seek = el.querySelector(".seek input");
+ seek.addEventListener("pointerdown", () => { el._dragging = true; });
+ seek.addEventListener("pointerup", () => { el._dragging = false; });
+ seek.addEventListener("change", () => post("seek", { position: Number(seek.value) }));
},
update(st, el) {
- const box = el.querySelector(".music");
- box.querySelector(".title").textContent = st.title || "Nothing playing";
- box.querySelector(".artist").textContent = st.artist || "";
- box.querySelector(".controls button:nth-child(2)").textContent =
+ if (st.art) {
+ let img = el.querySelector("img.art");
+ if (!img) {
+ img = document.createElement("img");
+ img.className = "art";
+ img.alt = "";
+ el.querySelector(".art").replaceWith(img);
+ }
+ if (img.getAttribute("src") !== st.art) img.src = st.art;
+ }
+ el.querySelector(".title").textContent = st.title || "Nothing playing";
+ el.querySelector(".artist").textContent = st.artist || "";
+ el.querySelector(".album").textContent = st.album || "";
+ el.querySelector(".controls button:nth-child(2)").textContent =
st.status === "Playing" ? "⏸" : "▶";
- const mute = box.querySelector(".vol button:nth-child(2)");
+ const mute = el.querySelector(".controls button:nth-child(5)");
mute.classList.toggle("on", Boolean(st.volume?.muted));
mute.textContent = st.volume?.muted ? "Unmute" : "Mute";
+ const seek = el.querySelector(".seek input");
+ seek.max = Math.max(1, Math.floor(st.length || 0));
+ if (!el._dragging) seek.value = Math.min(Number(seek.max), Math.floor(st.position || 0));
+ el.querySelector(".time").textContent = `${clock(st.position)} / ${clock(st.length)}`;
},
};