feat: Adding audio player + music to play
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// A small cassette player on the desk: a cassette window with two reels that turn while the music plays,
|
||||
// the track title on the cassette label, and five vintage keys (previous, play, pause, stop, next).
|
||||
|
||||
import { t } from "../i18n/i18n.js";
|
||||
import { $ } from "./desk.js";
|
||||
|
||||
const KEYS = [
|
||||
["prev", `<path d="M12 1v14L3 8zM22 1v14l-9-7z"/>`],
|
||||
["play", `<path d="M7 1v14l11-7z"/>`],
|
||||
["pause", `<path d="M6 1h4v14H6zM14 1h4v14h-4z"/>`],
|
||||
["stop", `<path d="M6 2h12v12H6z"/>`],
|
||||
["next", `<path d="M2 1v14l9-7zM12 1v14l9-7z"/>`],
|
||||
];
|
||||
|
||||
const reel = (side) => `
|
||||
<svg class="walkman__reel walkman__reel--${side}" viewBox="0 0 40 40" aria-hidden="true" focusable="false">
|
||||
<circle class="walkman__tape" cx="20" cy="20" r="17"/>
|
||||
<g class="walkman__spokes">${[0, 60, 120].map((a) => `<line x1="20" y1="9" x2="20" y2="31" transform="rotate(${a} 20 20)"/>`).join("")}</g>
|
||||
<circle class="walkman__hub" cx="20" cy="20" r="6"/>
|
||||
</svg>`;
|
||||
|
||||
export function createWalkman(root, { music, onPress = () => {} }) {
|
||||
root.innerHTML = `
|
||||
<div class="walkman__body">
|
||||
<div class="walkman__badge"><b>VALDOR</b><span>${t("walkman.model")}</span><i class="walkman__led" aria-hidden="true"></i></div>
|
||||
<div class="walkman__window">
|
||||
${reel("left")}
|
||||
<div class="walkman__label"><span class="walkman__title" role="status"></span></div>
|
||||
${reel("right")}
|
||||
</div>
|
||||
<div class="walkman__keys" role="group" aria-label="${t("walkman.title")}">
|
||||
${KEYS.map(([act, icon]) => `
|
||||
<button class="walkman__key walkman__key--${act}" data-act="${act}" aria-label="${t("walkman." + act)}" title="${t("walkman." + act)}">
|
||||
<svg viewBox="0 0 24 16" aria-hidden="true" focusable="false">${icon}</svg>
|
||||
</button>`).join("")}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const title = $(root, ".walkman__title");
|
||||
const label = $(root, ".walkman__label");
|
||||
|
||||
root.addEventListener("click", (e) => {
|
||||
const b = e.target.closest("button[data-act]");
|
||||
if (!b || b.disabled) return;
|
||||
onPress();
|
||||
music[b.dataset.act]();
|
||||
});
|
||||
|
||||
function update() {
|
||||
const m = music.info();
|
||||
const playing = m.state === "playing";
|
||||
root.classList.toggle("is-playing", playing);
|
||||
root.classList.toggle("is-empty", !m.available);
|
||||
root.title = m.available ? "" : t("walkman.hint");
|
||||
|
||||
let text = t("walkman.empty");
|
||||
if (m.available) text = m.waitingForGesture ? t("walkman.tap") : m.state === "stopped" ? t("walkman.stopped") : m.title;
|
||||
if (title.textContent !== text) {
|
||||
title.textContent = text;
|
||||
title.classList.remove("is-scroll");
|
||||
// a long title scrolls across the label while the tape runs
|
||||
requestAnimationFrame(() => title.classList.toggle("is-scroll", title.scrollWidth > label.clientWidth + 2));
|
||||
}
|
||||
|
||||
for (const b of root.querySelectorAll("button[data-act]")) {
|
||||
b.disabled = !m.available;
|
||||
if (b.dataset.act === "play") b.setAttribute("aria-pressed", String(playing));
|
||||
if (b.dataset.act === "pause") b.setAttribute("aria-pressed", String(m.state === "paused"));
|
||||
if (b.dataset.act === "stop") b.setAttribute("aria-pressed", String(m.state === "stopped"));
|
||||
}
|
||||
}
|
||||
|
||||
music.setListener(update);
|
||||
return { update };
|
||||
}
|
||||
Reference in New Issue
Block a user