music: per-device now-playing (browser vs phone)

Key music_now_playing on (user_id, device) instead of user_id alone so the
browser ('web') and phone ('' default) each keep their own resume snapshot
instead of sharing one row. Sidecar /now-playing threads device (?device=,
default '') through get/set/clearNowPlaying; the web client tags its calls
?device=web. Phone unchanged → '' bucket, inherits the existing row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 03:13:16 +00:00
co-authored by Claude Opus 4.8
parent 91ed03d514
commit f3dc4415bb
4 changed files with 47 additions and 36 deletions
+9 -6
View File
@@ -98,9 +98,9 @@ const asKeys = (v: unknown): string[] | null =>
// GET /favorites { tracks[], albums[], artists[] } (keys, newest first).
// POST /favorites { kind, key } add (idempotent). kind ∈ track|album|artist.
// DELETE /favorites?kind=&key= remove.
// GET /now-playing last snapshot, or null.
// PUT /now-playing { homePath, dir?, title?, artist?, album?, durationSec?, positionSec? } upsert.
// DELETE /now-playing clear.
// GET /now-playing[?device=] last snapshot for that device, or null. device '' = default/phone, 'web' = browser.
// PUT /now-playing[?device=] { homePath, dir?, title?, artist?, album?, durationSec?, positionSec? } upsert.
// DELETE /now-playing[?device=] clear that device's snapshot.
// GET /playlists [{ id, name, count, createdAt, updatedAt }] (recent first).
// POST /playlists { name } create → 201 row; 409 if name taken.
// GET /playlists/:id { id, name, items:[keys], … }; 404 if not the user's.
@@ -341,14 +341,17 @@ const server = Bun.serve({
}
if (P === '/now-playing') {
if (m === 'GET') return json(await getNowPlaying(uid));
// Per-client resume state: `?device=` tags the caller ('' = default/phone, 'web' = the browser)
// so each keeps its own now-playing instead of sharing one row. Missing → '' (back-compat).
const device = url.searchParams.get('device') ?? '';
if (m === 'GET') return json(await getNowPlaying(uid, device));
if (m === 'PUT') {
const b = (await req.json().catch(() => ({}))) as Record<string, unknown>;
if (typeof b.homePath !== 'string' || !b.homePath)
return json({ error: 'homePath required' }, { status: 400 });
const str = (v: unknown) => (typeof v === 'string' ? v : undefined);
const num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);
await setNowPlaying(uid, {
await setNowPlaying(uid, device, {
homePath: b.homePath,
dir: str(b.dir),
title: str(b.title),
@@ -360,7 +363,7 @@ const server = Bun.serve({
return json({ ok: true });
}
if (m === 'DELETE') {
await clearNowPlaying(uid);
await clearNowPlaying(uid, device);
return json({ ok: true });
}
return new Response('Method not allowed', { status: 405 });