music: backup play log from now-playing samples (origin nowplaying; 30 s or half the track, ignoring pauses and seeks); recently-played takes those rows over with its exact timestamp
All checks were successful
Deploy production / deploy (push) Successful in 1m9s
All checks were successful
Deploy production / deploy (push) Successful in 1m9s
This commit is contained in:
@@ -186,6 +186,35 @@ export async function pollPlayer(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* How far apart a backup play (end seen in now-playing) and Spotify's own played_at for the
|
||||
* same play may be: up to 90 s, but under half the track so a repeat is never merged.
|
||||
*/
|
||||
export const backupWindowS = (durationMs: number | null) =>
|
||||
Math.round(Math.min(90, Math.max(20, (durationMs ?? 180_000) / 2_000)));
|
||||
|
||||
/** Store a backup play (origin 'nowplaying') unless Spotify already has that play. */
|
||||
export async function recordBackupPlay(
|
||||
sql: Db,
|
||||
play: { trackId: number; endedAt: number; listenedMs: number; durationMs: number | null }
|
||||
) {
|
||||
const at = new Date(play.endedAt).toISOString();
|
||||
const w = backupWindowS(play.durationMs);
|
||||
const rows = await sql`
|
||||
insert into music.plays (track_id, played_at, source, origin, ms_played)
|
||||
select ${play.trackId}, ${at}::timestamptz, 'spotify', 'nowplaying',
|
||||
${Math.round(play.durationMs ? Math.min(play.listenedMs, play.durationMs) : play.listenedMs)}
|
||||
where not exists (
|
||||
select 1 from music.plays p join music.tracks t on t.id = p.track_id
|
||||
where p.source = 'spotify'
|
||||
and p.played_at between ${at}::timestamptz - make_interval(secs => ${w})
|
||||
and ${at}::timestamptz + make_interval(secs => ${w})
|
||||
and t.match_key = (select match_key from music.tracks where id = ${play.trackId}))
|
||||
on conflict do nothing
|
||||
returning id`;
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
/** Store plays newer than the cursor; returns how many were new. */
|
||||
export async function pollRecent(
|
||||
sql: Db,
|
||||
@@ -208,6 +237,29 @@ export async function pollRecent(
|
||||
const at = play.playedAt.getTime();
|
||||
if (cursor && at <= cursor) continue;
|
||||
const id = await upsertTrack(sql, play.track, true, cache);
|
||||
// The backup log may already have this play: take over that row with Spotify's
|
||||
// exact timestamp and context instead of adding a second one.
|
||||
const w = backupWindowS(play.track.durationMs ?? null);
|
||||
const iso = play.playedAt.toISOString();
|
||||
const claimed = await sql`
|
||||
update music.plays set played_at = ${iso}::timestamptz, origin = 'api',
|
||||
context_type = ${play.contextType ?? null}, context_uri = ${play.contextUri ?? null}
|
||||
where id = (
|
||||
select p.id from music.plays p join music.tracks t on t.id = p.track_id
|
||||
where p.source = 'spotify' and p.origin = 'nowplaying'
|
||||
and p.played_at between ${iso}::timestamptz - make_interval(secs => ${w})
|
||||
and ${iso}::timestamptz + make_interval(secs => ${w})
|
||||
and t.match_key = (select match_key from music.tracks where id = ${id})
|
||||
order by abs(extract(epoch from p.played_at - ${iso}::timestamptz)) limit 1)
|
||||
and not exists (select 1 from music.plays x
|
||||
where x.source = 'spotify' and x.track_id = music.plays.track_id
|
||||
and x.played_at = ${iso}::timestamptz)
|
||||
returning id`;
|
||||
if (claimed.length) {
|
||||
newPlays++;
|
||||
newest = Math.max(newest, at);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
await insertPlay(sql, {
|
||||
trackId: id,
|
||||
|
||||
Reference in New Issue
Block a user