···33import type { AtUriString } from '@atproto/lex';
44import type { NowPlaying, RecentPlay } from '$lib/types';
55import { cache, MIN_MS } from '$lib/server/cache';
66-import { createClient, getRepoInfo, repoId } from './client';
66+import { createClient, getRepoInfo, repoId, type RepoInfo } from './client';
77import { resolveCoverArt } from './coverArt';
8899const RECENT_PLAYS_LIMIT = 50;
···2727 return results;
2828}
29293030+/**
3131+ * A teal status is only "live" until its expiry — which defaults to 10 minutes
3232+ * past the start time when the record omits one. Past that, the actor isn't
3333+ * actively listening and we should fall back to their last play.
3434+ */
3535+function isStatusLive(status: { time: string; expiry?: string }): boolean {
3636+ const expiryMs = status.expiry
3737+ ? Date.parse(status.expiry)
3838+ : Date.parse(status.time) + 10 * MIN_MS;
3939+ return Number.isFinite(expiryMs) && expiryMs > Date.now();
4040+}
4141+4242+/** Most recently listened track, shaped as a NowPlaying fallback (playedTime set). */
4343+async function fetchLastPlay(
4444+ fetch: typeof globalThis.fetch,
4545+ repo: RepoInfo
4646+): Promise<NowPlaying | null> {
4747+ const client = createClient(repo.pds);
4848+ // Fetch a small batch and pick the newest defensively. (This PDS returns
4949+ // records newest-first by default; passing `reverse: true` returns none.)
5050+ const res = await client.list(fm.teal.alpha.feed.play, {
5151+ repo: repoId(repo.did),
5252+ limit: 5
5353+ });
5454+ const records = res.records as ReadonlyArray<{ value: PlayRecord }>;
5555+ if (records.length === 0) return null;
5656+5757+ const value = [...records].sort((a, b) =>
5858+ (b.value.playedTime ?? '').localeCompare(a.value.playedTime ?? '')
5959+ )[0].value;
6060+ // History uses the release MBID directly — no MusicBrainz ISRC lookup.
6161+ const coverArt = await resolveCoverArt(fetch, {
6262+ releaseMbId: value.releaseMbId ?? null,
6363+ releaseName: value.releaseName ?? null
6464+ });
6565+6666+ return {
6767+ trackName: value.trackName,
6868+ artists: value.artists.map((a) => a.artistName).join(', '),
6969+ releaseName: value.releaseName ?? null,
7070+ url: value.originUrl ?? null,
7171+ coverArt,
7272+ playedTime: value.playedTime ?? null
7373+ };
7474+}
7575+3076export async function fetchNowPlaying(fetch: typeof globalThis.fetch): Promise<NowPlaying | null> {
3177 const cached = cache.get('nowPlaying') as { value: NowPlaying | null } | null;
3278 if (cached) return cached.value;
···36823783 try {
3884 const client = createClient(repo.pds);
3939- const res = await client.get(fm.teal.alpha.actor.status, {
4040- repo: repoId(repo.did)
4141- });
4242- const item = res.value.item;
8585+ const status = await client
8686+ .get(fm.teal.alpha.actor.status, { repo: repoId(repo.did) })
8787+ .then((res) => res.value)
8888+ .catch(() => null); // no status record yet — treat as not currently listening
43894444- const coverArt = await resolveCoverArt(fetch, {
4545- isrc: item.isrc ?? null,
4646- releaseMbId: item.releaseMbId ?? null,
4747- releaseName: item.releaseName ?? null,
4848- useIsrcLookup: true
4949- });
5050-5151- const nowPlaying: NowPlaying = {
5252- trackName: item.trackName,
5353- artists: item.artists.map((a) => a.artistName).join(', '),
5454- releaseName: item.releaseName ?? null,
5555- url: item.originUrl ?? null,
5656- coverArt
5757- };
9090+ let nowPlaying: NowPlaying | null;
9191+ if (status && isStatusLive(status)) {
9292+ const item = status.item;
9393+ const coverArt = await resolveCoverArt(fetch, {
9494+ isrc: item.isrc ?? null,
9595+ releaseMbId: item.releaseMbId ?? null,
9696+ releaseName: item.releaseName ?? null,
9797+ useIsrcLookup: true
9898+ });
9999+ nowPlaying = {
100100+ trackName: item.trackName,
101101+ artists: item.artists.map((a) => a.artistName).join(', '),
102102+ releaseName: item.releaseName ?? null,
103103+ url: item.originUrl ?? null,
104104+ coverArt,
105105+ playedTime: null
106106+ };
107107+ } else {
108108+ // Nothing playing right now — show the most recently listened track instead.
109109+ nowPlaying = await fetchLastPlay(fetch, repo);
110110+ }
5811159112 cache.setWithExpiry('nowPlaying', { value: nowPlaying }, MIN_MS);
60113 return nowPlaying;
+3
src/lib/types.ts
···2525 releaseName: string | null;
2626 url: string | null;
2727 coverArt: string | null;
2828+ // null when this is a live "now playing" status; set to the play's time when
2929+ // we've fallen back to the most recently listened track instead.
3030+ playedTime: string | null;
2831};
29323033export type RecentPlay = {