feat(playback): move a session between devices
One device holds the audio; the rest watch the same session over an event stream and act as remotes. Picking a device hands the audio over at the position the previous one reported, and that device stops. Also centre the command palette with margins instead of a translate: animate-rise sets its own transform and dropped the offset on mobile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -819,4 +819,36 @@ export const MIGRATIONS: Migration[] = [
|
||||
WHERE t.id = ph.track_id AND ph.track_title IS NULL;
|
||||
`,
|
||||
},
|
||||
{
|
||||
// One listener, many browsers. `playback_state` is the single authority for
|
||||
// what is playing and which device owns the audio, so a phone can take over
|
||||
// from a desktop mid-track. The queue is stored as whole track objects, not
|
||||
// ids: the device taking over needs to render the queue immediately, and a
|
||||
// snapshot of what was queued at handoff time is the honest thing to move.
|
||||
id: '20260808_playback_devices',
|
||||
sql: `
|
||||
CREATE TABLE IF NOT EXISTS playback_devices (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_playback_devices_user
|
||||
ON playback_devices (user_id, last_seen_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS playback_state (
|
||||
user_id UUID PRIMARY KEY,
|
||||
device_id UUID REFERENCES playback_devices(id) ON DELETE SET NULL,
|
||||
track_id UUID REFERENCES tracks(id) ON DELETE SET NULL,
|
||||
queue JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
queue_index INTEGER NOT NULL DEFAULT -1,
|
||||
position_ms INTEGER NOT NULL DEFAULT 0,
|
||||
is_playing BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
-- Monotonic per user. A device applies a snapshot only when it is newer
|
||||
-- than the last one it saw, so a delayed delivery cannot rewind anyone.
|
||||
version BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user