import { get, writable } from 'svelte/store'; import { fetch_radio_page, RADIO_PAGE_SIZE, type RadioTrack, } from './funkwhale'; import { radio_player } from './radio-store'; /** * State of the browsable library (all tracks of the radio). */ export interface LibraryState { tracks: RadioTrack[]; /** Total number of tracks on the radio (known after the first page). */ total: number | null; loaded_pages: number; total_pages: number | null; is_loading: boolean; is_done: boolean; error: string | null; } const initial_state: LibraryState = { tracks: [], total: null, loaded_pages: 0, total_pages: null, is_loading: false, is_done: false, error: null, }; export const library = writable(initial_state); /** * Loads every page of the radio's track list, one after another. * Idempotent: does nothing if already loading or fully loaded. */ export async function load_library(): Promise { const state = get(library); if (state.is_loading || state.is_done) return; library.set({ ...state, is_loading: true, error: null }); try { // First page: tells us how many tracks / pages exist. const first = await fetch_radio_page(1); const total_pages = Math.ceil(first.count / RADIO_PAGE_SIZE); library.set({ tracks: first.tracks, total: first.count, loaded_pages: 1, total_pages, is_loading: true, is_done: total_pages <= 1, error: null, }); // Load remaining pages in small parallel batches. const CONCURRENCY = 4; for (let start = 2; start <= total_pages; start += CONCURRENCY) { // Pause while playback is loading a track: streaming and // playback-related requests have priority over the library. while (get(radio_player).is_loading) { await new Promise((resolve) => setTimeout(resolve, 300)); } const pages = await Promise.all( Array.from( { length: Math.min(CONCURRENCY, total_pages - start + 1) }, (_, i) => fetch_radio_page(start + i), ), ); library.update((prev) => ({ ...prev, tracks: [...prev.tracks, ...pages.flatMap((p) => p.tracks)], loaded_pages: Math.min(start + CONCURRENCY - 1, total_pages), })); } library.update((prev) => ({ ...prev, is_loading: false, is_done: true, })); } catch (error) { library.update((prev) => ({ ...prev, is_loading: false, error: error instanceof Error ? error.message : String(error), })); } }