Keep progress bars aligned
CI / check (push) Canceled after 0s

The server now sends each fresh position on every poll that has one, and
sends positions advanced to send time; clients count only the time since
they received it. Bars realign every poll and no longer drift by however
far the viewer's clock is off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
2026-10-06 12:48:29 -04:00
co-authored by Claude Opus 5.5
parent be0a00a2c1
commit 13df568348
7 changed files with 93 additions and 67 deletions
+11 -8
View File
@@ -5,22 +5,25 @@ import { formatDuration, playbackPosition, type NowPlayingDTO } from '@musicshar
/** Where they are in the song. Renders nothing when the source doesn't report a position. */
export function ProgressBar({ np }: { np: NowPlayingDTO }) {
const [now, setNow] = useState(() => Date.now());
const pos = playbackPosition(np, now);
const ticking = np.isPlaying && pos !== null;
if (!np.isPlaying || playbackPosition(np, 0) === null) return null;
// Each fresh position from the server restarts the clock, realigning the bar.
return <Bar key={`${np.track.id}|${np.progressMs}|${np.updatedAt}`} np={np} />;
}
function Bar({ np }: { np: NowPlayingDTO }) {
const [receivedAt] = useState(() => Date.now());
const [now, setNow] = useState(receivedAt);
useEffect(() => {
if (!ticking) return;
const id = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(id);
}, [ticking]);
}, []);
if (!pos || !np.isPlaying) return null;
const pct = (pos.positionMs / pos.durationMs) * 100;
const pos = playbackPosition(np, now - receivedAt);
if (!pos) return null;
return (
<div className="progress" aria-label={`${formatDuration(pos.positionMs)} of ${formatDuration(pos.durationMs)}`}>
<div className="progress-track">
<div className="progress-fill" style={{ width: `${pct}%` }} />
<div className="progress-fill" style={{ width: `${(pos.positionMs / pos.durationMs) * 100}%` }} />
</div>
<div className="progress-times" aria-hidden>
<span>{formatDuration(pos.positionMs)}</span>