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:
@@ -27,20 +27,32 @@ export async function getNowPlaying(userIds: string[]): Promise<NowPlayingDTO[]>
|
||||
.innerJoin(schema.users, eq(schema.users.id, schema.nowPlaying.userId))
|
||||
.where(inArray(schema.nowPlaying.userId, userIds));
|
||||
const tracks = await getTrackDTOs(rows.map((r) => r.np.trackId));
|
||||
const now = Date.now();
|
||||
return rows
|
||||
.map(({ np, user }) => ({
|
||||
.map(({ np, user }) => ({ np, user, track: tracks.get(np.trackId)! }))
|
||||
.map(({ np, user, track }) => ({
|
||||
user: toUserSummary(user),
|
||||
track: tracks.get(np.trackId)!,
|
||||
isPlaying: np.isPlaying && !(PUSH_SOURCES.has(np.source) && Date.now() - np.updatedAt.getTime() > DEVICE_STALE_MS),
|
||||
track,
|
||||
isPlaying: np.isPlaying && !(PUSH_SOURCES.has(np.source) && now - np.updatedAt.getTime() > DEVICE_STALE_MS),
|
||||
source: np.source,
|
||||
sourceApp: np.sourceApp,
|
||||
sourceUrl: np.sourceUrl,
|
||||
progressMs: np.progressMs,
|
||||
progressMs: positionNow(np, track.durationMs, now),
|
||||
updatedAt: np.updatedAt.toISOString(),
|
||||
}))
|
||||
.sort((a, b) => Number(b.isPlaying) - Number(a.isPlaying) || b.updatedAt.localeCompare(a.updatedAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* The position as of right now, so clients only count time since they received
|
||||
* it (their clock can be off from ours by any amount).
|
||||
*/
|
||||
function positionNow(np: { progressMs: number | null; isPlaying: boolean; updatedAt: Date }, durationMs: number | null, now: number) {
|
||||
if (np.progressMs == null || !np.isPlaying) return np.progressMs;
|
||||
const position = np.progressMs + Math.max(0, now - np.updatedAt.getTime());
|
||||
return durationMs ? Math.min(position, durationMs) : position;
|
||||
}
|
||||
|
||||
/** Plays kept per user. Profiles show the latest few; the rest leaves room for a future history page. */
|
||||
export const LISTEN_HISTORY_LIMIT = 50;
|
||||
/** Coming back to the same track after this long counts as a new play. */
|
||||
@@ -94,8 +106,6 @@ export async function publishNowPlaying(userId: string) {
|
||||
if (dto) await redis.publish(nowPlayingChannel(userId), JSON.stringify(dto));
|
||||
}
|
||||
|
||||
const SEEK_THRESHOLD_MS = 5_000;
|
||||
|
||||
/** Apply one provider's poll result to the user's now-playing state. Returns whether they're playing. */
|
||||
export async function applyPollResult(
|
||||
userId: string,
|
||||
@@ -121,13 +131,9 @@ export async function applyPollResult(
|
||||
|
||||
const track = await upsertTrack(result.track);
|
||||
const changed = !current || current.trackId !== track.id || !current.isPlaying;
|
||||
// Same song, but not where it should be by now: a seek (or a restart). Tell
|
||||
// watchers, or their progress bars would drift.
|
||||
const jumped =
|
||||
!changed &&
|
||||
current.progressMs != null &&
|
||||
result.progressMs != null &&
|
||||
Math.abs(result.progressMs - (current.progressMs + (now.getTime() - current.updatedAt.getTime()))) > SEEK_THRESHOLD_MS;
|
||||
// A fresh position from the source: send it so viewers' progress bars realign
|
||||
// (seeks, buffering, pauses we missed between polls).
|
||||
const realign = !changed && result.progressMs != null;
|
||||
const newPlay =
|
||||
!current || current.trackId !== track.id || (!current.isPlaying && now.getTime() - current.updatedAt.getTime() > REPLAY_GAP_MS);
|
||||
const values = {
|
||||
@@ -146,7 +152,7 @@ export async function applyPollResult(
|
||||
.onConflictDoUpdate({ target: schema.nowPlaying.userId, set: values });
|
||||
|
||||
if (newPlay) await recordListen(userId, track.id, source, now);
|
||||
if (jumped) await publishNowPlaying(userId);
|
||||
if (realign) await publishNowPlaying(userId);
|
||||
if (changed) {
|
||||
await publishNowPlaying(userId);
|
||||
const [t] = await db
|
||||
|
||||
Reference in New Issue
Block a user