Web UI: player progress bar with seek
Follows /api/player's position, moves on locally between polls while playing, seeks on release; disabled when the source can't seek. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+19
-3
@@ -39,6 +39,9 @@ pre{overflow-x:auto}
|
||||
<button onclick="ctl('toggle')">⏯</button>
|
||||
<button onclick="ctl('next')">⏭</button>
|
||||
<button onclick="ctl('stop')">⏹</button>
|
||||
<label><span>Position</span><input id="pos" type="range" min="0" max="0" value="0" step="1000" disabled
|
||||
oninput="seekDrag=true; showPos()" onchange="seekDrag=false; ctl('seek',{ms:+this.value})">
|
||||
<small id="postime"></small></label>
|
||||
<label><span>Volume</span><input id="vol" type="range" min="0" max="100"
|
||||
onchange="ctl('volume',{value:+this.value})"></label>
|
||||
<label><span>Force source</span><select id="force" onchange="ctl('source',{source:this.value})">
|
||||
@@ -237,12 +240,25 @@ const PROJECT = {
|
||||
async function ctl(cmd, body) {
|
||||
try { showPlayer(await api('POST', '/api/player/' + cmd, body)); } catch {}
|
||||
}
|
||||
// Progress bar: follows the polled position, moves on locally while playing, seeks on release.
|
||||
let np = null, npAt = 0, seekDrag = false;
|
||||
const mmss = ms => `${Math.floor(ms/60000)}:${String(Math.floor(ms/1000)%60).padStart(2,'0')}`;
|
||||
function showPos() {
|
||||
const pos = document.getElementById('pos'), dur = np?.duration_ms;
|
||||
pos.disabled = !(dur && np.can?.seek);
|
||||
if (!seekDrag) {
|
||||
const at = !dur ? 0 : np.state === 'playing' ? Math.min(np.position_ms + Date.now() - npAt, dur) : np.position_ms;
|
||||
pos.max = dur || 0;
|
||||
pos.value = at;
|
||||
}
|
||||
document.getElementById('postime').textContent = dur ? `${mmss(+pos.value)} / ${mmss(dur)}` : '';
|
||||
}
|
||||
setInterval(showPos, 500);
|
||||
function showPlayer(p) {
|
||||
if (!p || !p.state) return;
|
||||
const t = s => s == null ? '' : `${Math.floor(s/60000)}:${String(Math.floor(s/1000)%60).padStart(2,'0')}`;
|
||||
np = p; npAt = Date.now(); showPos();
|
||||
document.getElementById('np').textContent =
|
||||
`[${p.source}] ${p.state}: ${[p.artist, p.title].filter(Boolean).join(' – ') || '–'}` +
|
||||
(p.duration_ms ? ` ${t(p.position_ms)} / ${t(p.duration_ms)}` : '');
|
||||
`[${p.source}] ${p.state}: ${[p.artist, p.title].filter(Boolean).join(' – ') || '–'}`;
|
||||
const v = document.getElementById('vol');
|
||||
if (document.activeElement !== v) v.value = p.volume;
|
||||
document.getElementById('force').value = p.forced || 'config';
|
||||
|
||||
Reference in New Issue
Block a user