|
- 'use strict';
-
- const api = (path, opts = {}) =>
- fetch(path, opts).then(r => r.json()).catch(() => null);
-
- // ── State ─────────────────────────────────────────────────────────────────────
- let currentVolume = 180; // 0–255
- let pollTimer = null;
-
- // ── DOM refs ──────────────────────────────────────────────────────────────────
- const $ = id => document.getElementById(id);
- const statusDot = $('winamp-status');
- const stateLabel = $('state-label');
- const trackTitle = $('track-title');
- const playlistPos = $('playlist-pos');
- const progressFill = $('progress-fill');
- const timeCurrent = $('time-current');
- const timeLength = $('time-length');
- const volumeFill = $('volume-fill');
- const btnPlay = $('btn-play');
- const killistPanel = $('killist-panel');
- const killistItems = $('killist-items');
-
- // ── Playback controls ─────────────────────────────────────────────────────────
- btnPlay.addEventListener('click', async () => {
- const st = await api('/api/status');
- if (!st) return;
- if (st.state === 'playing') {
- await api('/api/pause', { method: 'POST' });
- } else {
- await api('/api/play', { method: 'POST' });
- }
- poll();
- });
-
- $('btn-stop').addEventListener('click', async () => {
- await api('/api/stop', { method: 'POST' }); poll();
- });
- $('btn-next').addEventListener('click', async () => {
- await api('/api/next', { method: 'POST' }); poll();
- });
- $('btn-prev').addEventListener('click', async () => {
- await api('/api/prev', { method: 'POST' }); poll();
- });
-
- // ── Seek buttons ──────────────────────────────────────────────────────────────
- document.querySelectorAll('.btn-seek').forEach(btn => {
- btn.addEventListener('click', async () => {
- const delta = parseInt(btn.dataset.delta, 10);
- await api(`/api/seek?delta=${delta}`, { method: 'POST' });
- poll();
- });
- });
-
- // ── Progress bar click-to-seek ────────────────────────────────────────────────
- $('progress-bar').addEventListener('click', async e => {
- const rect = e.currentTarget.getBoundingClientRect();
- const frac = (e.clientX - rect.left) / rect.width;
- const st = await api('/api/status');
- if (!st || !st.length) return;
- const target = Math.round(frac * st.length);
- const delta = target - st.position;
- await api(`/api/seek?delta=${delta}`, { method: 'POST' });
- poll();
- });
-
- // ── Volume ────────────────────────────────────────────────────────────────────
- $('btn-vol-up').addEventListener('click', async () => {
- currentVolume = Math.min(255, currentVolume + 13); // ~5%
- await api(`/api/volume?level=${currentVolume}`, { method: 'POST' });
- updateVolumeFill();
- });
- $('btn-vol-down').addEventListener('click', async () => {
- currentVolume = Math.max(0, currentVolume - 13);
- await api(`/api/volume?level=${currentVolume}`, { method: 'POST' });
- updateVolumeFill();
- });
- $('volume-bar').addEventListener('click', async e => {
- const rect = e.currentTarget.getBoundingClientRect();
- currentVolume = Math.round((e.clientX - rect.left) / rect.width * 255);
- await api(`/api/volume?level=${currentVolume}`, { method: 'POST' });
- updateVolumeFill();
- });
- function updateVolumeFill() {
- volumeFill.style.width = (currentVolume / 255 * 100) + '%';
- }
-
- // ── KillList ──────────────────────────────────────────────────────────────────
- $('btn-kill').addEventListener('click', async () => {
- const res = await api('/api/killist', { method: 'POST' });
- if (res?.added) {
- showToast(`🚫 ${res.added}`);
- }
- });
- $('btn-show-killist').addEventListener('click', async () => {
- await refreshKillist();
- killistPanel.classList.remove('hidden');
- });
- $('btn-close-killist').addEventListener('click', () => {
- killistPanel.classList.add('hidden');
- });
- async function refreshKillist() {
- const list = await api('/api/killist');
- if (!list) return;
- killistItems.innerHTML = '';
- list.forEach(title => {
- const li = document.createElement('li');
- li.innerHTML = `<span>${escHtml(title)}</span>`;
- const btn = document.createElement('button');
- btn.textContent = '✕';
- btn.onclick = async () => {
- await api(`/api/killist?title=${encodeURIComponent(title)}`, { method: 'DELETE' });
- await refreshKillist();
- };
- li.appendChild(btn);
- killistItems.appendChild(li);
- });
- }
-
- // ── Polling ───────────────────────────────────────────────────────────────────
- async function poll() {
- const st = await api('/api/status');
- if (!st) {
- statusDot.className = 'err';
- statusDot.textContent = '●';
- stateLabel.textContent = 'Keine Verbindung';
- trackTitle.textContent = '–';
- return;
- }
-
- if (!st.running) {
- statusDot.className = 'err';
- stateLabel.textContent = 'Winamp nicht gestartet';
- trackTitle.textContent = '–';
- return;
- }
-
- statusDot.className = 'ok';
- const stateMap = { playing: '▶ Spielt', paused: '⏸ Pause', stopped: '⏹ Stop' };
- stateLabel.textContent = stateMap[st.state] ?? st.state;
-
- trackTitle.textContent = st.title || '–';
- playlistPos.textContent = st.playlist_length
- ? `${st.playlist_pos} / ${st.playlist_length}`
- : '';
-
- if (st.length > 0) {
- progressFill.style.width = (st.position / st.length * 100).toFixed(1) + '%';
- timeCurrent.textContent = fmtTime(st.position);
- timeLength.textContent = fmtTime(st.length);
- } else {
- progressFill.style.width = '0%';
- }
-
- // Reflect play/pause state on button
- btnPlay.textContent = st.state === 'playing' ? '⏸' : '▶';
- }
-
- function startPolling(intervalMs = 2000) {
- if (pollTimer) clearInterval(pollTimer);
- poll();
- pollTimer = setInterval(poll, intervalMs);
- }
-
- // ── Helpers ───────────────────────────────────────────────────────────────────
- function fmtTime(secs) {
- const m = Math.floor(secs / 60);
- const s = String(Math.floor(secs % 60)).padStart(2, '0');
- return `${m}:${s}`;
- }
-
- function escHtml(str) {
- return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
- }
-
- let toastTimer;
- function showToast(msg) {
- let el = document.getElementById('toast');
- if (!el) {
- el = document.createElement('div');
- el.id = 'toast';
- el.style.cssText = `
- position:fixed;bottom:24px;left:50%;transform:translateX(-50%);
- background:#333;color:#fff;padding:10px 20px;border-radius:8px;
- font-size:14px;z-index:999;opacity:0;transition:opacity .2s;
- `;
- document.body.appendChild(el);
- }
- el.textContent = msg;
- el.style.opacity = '1';
- clearTimeout(toastTimer);
- toastTimer = setTimeout(() => { el.style.opacity = '0'; }, 2500);
- }
-
- // ── Boot ──────────────────────────────────────────────────────────────────────
- startPolling(2000);
|