perf(radio): bound queue backlog and exclude list
An infinite radio appends forever: cap the queue at QUEUE_CAP, trimming only already-played entries (keep KEEP_BEHIND for prev-nav), and send only the most recent EXCLUDE_WINDOW ids as "don't repeat" so the request and server NOT IN don't grow unbounded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,11 @@ import { pickToEntry } from '../../lib/radio';
|
||||
// the end of the queue.
|
||||
const LOOKAHEAD = 3;
|
||||
|
||||
// Only the most recent tracks are sent as "don't repeat these" — bounding the
|
||||
// request so a long radio session doesn't ship an ever-growing exclude list
|
||||
// (and a huge NOT IN on the server). Recent-repeat avoidance is what matters.
|
||||
const EXCLUDE_WINDOW = 150;
|
||||
|
||||
/**
|
||||
* Headless controller: while a radio drives the queue, top it up as it drains
|
||||
* (§6.5 / B6 infinite feed). Mounted once in AppShell. A failed prefetch just
|
||||
@@ -34,7 +39,7 @@ export function RadioController() {
|
||||
fromLikes: radio.fromLikes,
|
||||
exploration: radio.exploration,
|
||||
count: 10,
|
||||
excludeIds: entries.map((e) => e.trackId),
|
||||
excludeIds: entries.slice(-EXCLUDE_WINDOW).map((e) => e.trackId),
|
||||
}).unwrap();
|
||||
if (res.picks.length > 0) {
|
||||
dispatch(appendToQueue(res.picks.map(pickToEntry)));
|
||||
|
||||
@@ -38,6 +38,12 @@ export interface QueueState {
|
||||
radio: RadioContext | null;
|
||||
}
|
||||
|
||||
// An infinite radio keeps appending, so cap the backlog: once the queue grows
|
||||
// past QUEUE_CAP, drop the oldest *already-played* entries (never the current
|
||||
// track or the look-ahead), keeping at least KEEP_BEHIND for prev-navigation.
|
||||
const QUEUE_CAP = 300;
|
||||
const KEEP_BEHIND = 50;
|
||||
|
||||
export const queueInitialState: QueueState = {
|
||||
entries: [],
|
||||
currentIndex: -1,
|
||||
@@ -83,6 +89,17 @@ export const queueSlice = createSlice({
|
||||
},
|
||||
appendToQueue(state, action: PayloadAction<QueueEntry[]>) {
|
||||
state.entries.push(...action.payload);
|
||||
if (state.entries.length > QUEUE_CAP) {
|
||||
// Only trim entries safely behind the current one.
|
||||
const drop = Math.min(
|
||||
state.entries.length - QUEUE_CAP,
|
||||
Math.max(0, state.currentIndex - KEEP_BEHIND),
|
||||
);
|
||||
if (drop > 0) {
|
||||
state.entries.splice(0, drop);
|
||||
state.currentIndex -= drop;
|
||||
}
|
||||
}
|
||||
},
|
||||
setExploration(state, action: PayloadAction<number>) {
|
||||
if (state.radio) state.radio.exploration = action.payload;
|
||||
|
||||
Reference in New Issue
Block a user