perf(vibe): stop building a plasma and reading the library on a phone
Two things made the Vibe page crawl on mobile. The ambient aura pushed three counter-rotating layers through an SVG turbulence and displacement pass whose noise field was animated in SMIL. SVG filters rasterise on the CPU, so that regenerated the whole field every frame, under a blur, across a 420x380 element. Below the breakpoint the filter is no longer mounted and the layers churn behind a plain blur instead. A phone gets the motion, not the noise pass. The start screen read all 5,000 tracks to show fifty of them. The sample is now drawn in the database, which keeps Surprise me uniform over the whole library while sending only what is on screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KENqSChfyqWnor6ud2WWH6
This commit is contained in:
@@ -16,6 +16,13 @@ export default async function libraryRoutes(fastify: FastifyInstance, options: {
|
||||
return tracks;
|
||||
});
|
||||
|
||||
// Seeds for the Vibe start screen. Static path, so it is matched ahead of
|
||||
// /tracks/:trackId.
|
||||
fastify.get('/tracks/seeds', async (request) => {
|
||||
const query = request.query as any;
|
||||
return await dbService.getSeedTracks(query.limit ? parseInt(query.limit) : undefined);
|
||||
});
|
||||
|
||||
fastify.get('/artists', async (request) => {
|
||||
const query = request.query as any;
|
||||
return await dbService.getArtists({
|
||||
|
||||
@@ -211,6 +211,33 @@ export class DbService {
|
||||
return this.attachArtists(res.rows as Track[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A random handful of playable tracks, with the size of the pool they came
|
||||
* from. The Vibe start screen offers seeds; it used to read the whole library
|
||||
* to pick fifty of them, which on a phone is megabytes of JSON to draw one
|
||||
* list. Sampling in the database keeps "Surprise me" uniform over everything
|
||||
* playable while sending only what is shown.
|
||||
*/
|
||||
async getSeedTracks(limit = 50): Promise<{ total: number; tracks: Track[] }> {
|
||||
const playable = `t.state NOT IN ('HIDDEN', 'DELETED')`;
|
||||
const [sample, total] = await Promise.all([
|
||||
this.pgClient.query(
|
||||
`SELECT t.*, al.artwork_id
|
||||
FROM tracks t
|
||||
LEFT JOIN albums al ON al.id = t.album_id
|
||||
WHERE ${playable}
|
||||
ORDER BY RANDOM()
|
||||
LIMIT $1`,
|
||||
[Math.min(Math.max(1, limit), 200)]
|
||||
),
|
||||
this.pgClient.query(`SELECT COUNT(*)::int AS total FROM tracks t WHERE ${playable}`),
|
||||
]);
|
||||
return {
|
||||
total: total.rows[0]?.total ?? 0,
|
||||
tracks: await this.attachArtists(sample.rows as Track[]),
|
||||
};
|
||||
}
|
||||
|
||||
/** Attach artists array (from track_artists join) to a list of tracks. */
|
||||
private async attachArtists(tracks: Track[]): Promise<Track[]> {
|
||||
if (tracks.length === 0) return tracks;
|
||||
|
||||
Reference in New Issue
Block a user