feat: transformation en application Radyobòkaz Player (WIP sécurisé)
Fork de la librairie headless svelte-podcast transformé en lecteur : radio Funkwhale mizik.o-k-i.net (~2000 titres), page démo RSS Syntax.fm, file de lecture, préférences et progression persistées. État avant refonte playbook OKI (bugs connus : volume inerte, bouton démo RSS cassé en navigation client, cibles tactiles mobiles).
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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<LibraryState>(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<void> {
|
||||
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),
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user