import { browser } from '$app/environment'; import { SvelteSet, SvelteMap } from 'svelte/reactivity'; import type { StreamSummary } from './types.ts'; const STORAGE_KEY = 'cachedStreams'; export const cached = new SvelteSet( browser ? JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') : [] ); export const downloading = new SvelteMap(); if (browser) { $effect.root(() => { $effect(() => { localStorage.setItem(STORAGE_KEY, JSON.stringify([...cached])); }); }); } export async function download(stream: StreamSummary) { const res = await fetch(`/media/tracks/${stream.filename}`); if (!res.ok || !res.body) throw new Error('Download failed'); const total = Number(res.headers.get('content-length') || 0); const reader = res.body.getReader(); const chunks: Uint8Array[] = []; let received = 0; downloading.set(stream.id, 0); while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); received += value.length; if (total) downloading.set(stream.id, Math.round((received / total) * 100)); } const root = await navigator.storage.getDirectory(); const handle = await root.getFileHandle(stream.id, { create: true }); const writable = await handle.createWritable(); await writable.write(new Blob(chunks)); await writable.close(); downloading.delete(stream.id); cached.add(stream.id); } export async function remove(streamId: string) { const root = await navigator.storage.getDirectory(); await root.removeEntry(streamId); cached.delete(streamId); } export async function getUrl(streamId: string): Promise { if (!cached.has(streamId)) return null; try { const root = await navigator.storage.getDirectory(); const handle = await root.getFileHandle(streamId); const file = await handle.getFile(); return URL.createObjectURL(file); } catch { cached.delete(streamId); return null; } }