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
+32 -15
View File
@@ -3,7 +3,7 @@ import { afterEach, describe, expect, it } from 'vitest';
import { db, schema } from '../src/db';
import { createRedis, nowPlayingChannel } from '../src/lib/redis';
import { reportedPosition } from '../src/routes/devices';
import { applyPollResult } from '../src/services/nowPlaying';
import { applyPollResult, getNowPlaying } from '../src/services/nowPlaying';
import { nextPollDelay } from '../src/services/scheduler';
import { createUser, useTestApp } from './helpers';
@@ -54,27 +54,44 @@ describe('phone-reported positions', () => {
});
});
describe('seeking', () => {
it('tells watchers when the position jumps, but not as it ticks along', async () => {
describe('realigning progress bars', () => {
it('sends the fresh position on every poll that has one, advanced to send time', async () => {
const { user } = await createUser(app(), 'alice');
await applyPollResult(user.id, 'plex', playing(0));
// Pretend the last poll was 10 seconds ago.
await db
.update(schema.nowPlaying)
.set({ updatedAt: new Date(Date.now() - 10_000) })
.where(eq(schema.nowPlaying.userId, user.id));
const messages: string[] = [];
await sub.subscribe(nowPlayingChannel(user.id));
sub.on('message', (_channel, msg) => messages.push(msg));
await applyPollResult(user.id, 'plex', playing(10_500)); // right where it should be
await new Promise((r) => setTimeout(r, 150));
expect(messages).toHaveLength(0);
await applyPollResult(user.id, 'plex', playing(10_500));
await db
.update(schema.nowPlaying)
.set({ updatedAt: new Date(Date.now() - 3_000) }) // as if that poll was 3s ago
.where(eq(schema.nowPlaying.userId, user.id));
await applyPollResult(user.id, 'plex', playing(120_000)); // skipped ahead
await new Promise((r) => setTimeout(r, 150));
expect(messages).toHaveLength(1);
expect(JSON.parse(messages[0]!)).toMatchObject({ progressMs: 120_000 });
const sent = messages.map((m) => JSON.parse(m).progressMs as number);
expect(sent).toHaveLength(2);
expect(sent[0]).toBeGreaterThanOrEqual(10_500);
expect(sent[0]).toBeLessThan(11_500);
expect(sent[1]).toBeGreaterThanOrEqual(120_000);
expect(sent[1]).toBeLessThan(121_000);
// Fetched later, the position has moved on by the time since the poll.
const [np] = await getNowPlaying([user.id]);
await db.update(schema.nowPlaying).set({ updatedAt: new Date(Date.now() - 4_000) }).where(eq(schema.nowPlaying.userId, user.id));
const [later] = await getNowPlaying([user.id]);
expect(np!.progressMs).toBeGreaterThanOrEqual(120_000);
expect(later!.progressMs).toBeGreaterThanOrEqual(124_000);
});
it("doesn't send anything extra for sources without a position", async () => {
const { user } = await createUser(app(), 'alice');
await applyPollResult(user.id, 'lastfm', playing(null));
const messages: string[] = [];
await sub.subscribe(nowPlayingChannel(user.id));
sub.on('message', (_channel, msg) => messages.push(msg));
await applyPollResult(user.id, 'lastfm', playing(null));
await new Promise((r) => setTimeout(r, 150));
expect(messages).toHaveLength(0);
});
});