android: the library's file stats on the cards, the artist's own plays, the thumbnail prewarm

The desktop Library's per-record file stats on the phone (library_album_stats,
one call for the whole library): FLAC 24/96 or MP3 320k badges on the cards,
the size on the list rows and the record page. The artist page's own
listening (artist_play_stats): the time played as a chip, the most played
track under "On this phone". The whole-library thumbnail warm a moment after
boot, at the cards' edge, one decode at a time behind the tiles' gate.
This commit is contained in:
KeelrLeek committed 2026-09-13 20:37:51 -05:00
1 parent 26928b706d
commit 991d52d0cc
8 files changed
+63 -11

No files matched your search

+21
View File
@@ -9,6 +9,8 @@ export const lib = $state({
recents: [], // library rows only, with an album_id
follows: [],
playlists: [],
/** album id → { secs, bytes, avg_kbps, bit_depth, sample_rate, formats, last_played_at } — the desktop's library_album_stats */
stats: {},
version: "",
username: null,
loaded: false,
@@ -104,6 +106,7 @@ export async function watchLibrary() {
timer = setTimeout(async () => {
try { lib.albums = await api.libraryAlbums(); } catch { /* keep */ }
await refreshRecents();
void refreshStats();
}, 600);
});
/* a playlist mutation (the desktop's commands emit it) re-lists them */
@@ -122,6 +125,24 @@ export async function loadLibrary() {
try { lib.follows = (await api.followedArtists()) || []; } catch { lib.follows = []; }
try { lib.playlists = (await api.listPlaylists()) || []; } catch { lib.playlists = []; }
lib.loaded = true;
void refreshStats();
}
/** The files' own aggregates per record — one call for the whole library
* (the desktop's RECENTS_FLOOR_SECS, 45, is the floor a play must reach
* to count as the last one). */
export async function refreshStats() {
try {
const rows = (await api.libraryAlbumStats(45)) || [];
lib.stats = Object.fromEntries(rows.map((r) => [r.album_id, r]));
} catch { /* the cards stay quiet */ }
}
/** "FLAC 24/96" for a lossless record, "MP3 320k" for a lossy one. */
export function statLabel(st) {
if (!st || !st.formats?.length) return "";
const f = String(st.formats[0]).toUpperCase();
if (st.bit_depth && st.sample_rate) return f + " " + st.bit_depth + "/" + (st.sample_rate % 1000 === 0 ? st.sample_rate / 1000 : (st.sample_rate / 1000).toFixed(1));
return st.avg_kbps ? f + " " + st.avg_kbps + "k" : f;
}
export async function refreshPlaylists() {