diff --git a/src/lib/database.ts b/src/lib/database.ts index 6809b55..6b84f4c 100644 --- a/src/lib/database.ts +++ b/src/lib/database.ts @@ -18,7 +18,7 @@ export function getStreams(): StreamSummary[] { stream_date: Date.parse(stream.stream_date as string), filename: stream.filename as string, title: stream.title as string | null, - tags: JSON.parse(stream.tags as string) as string[], + tags: (JSON.parse(stream.tags as string) as string[]) ?? [], length_seconds: stream.length_seconds as number })); } @@ -40,7 +40,7 @@ export function getStreamInfo(streamId: string): Stream | null { format: streamData.format as string, title: streamData.title as string | null, description: streamData.description as string | null, - tags: JSON.parse(streamData.tags as string) as string[], + tags: (JSON.parse(streamData.tags as string) as string[]) ?? [], length_seconds: streamData.length_seconds as number, tracks: JSON.parse(streamData.tracks as string) }; diff --git a/src/lib/stores.svelte.ts b/src/lib/stores.svelte.ts index 7e1254b..37eb65d 100644 --- a/src/lib/stores.svelte.ts +++ b/src/lib/stores.svelte.ts @@ -1,12 +1,10 @@ -import { browser } from '$app/environment'; import { SvelteSet } from 'svelte/reactivity'; +import { readStored, writeStored } from './utils.ts'; -export const favoritedStreams = new SvelteSet( - JSON.parse((browser && localStorage.getItem('favoritedStreams')) || '[]') -); +export const favoritedStreams = new SvelteSet(readStored('favoritedStreams', [])); $effect.root(() => { $effect(() => { - localStorage.setItem('favoritedStreams', JSON.stringify(Array.from(favoritedStreams))); + writeStored('favoritedStreams', Array.from(favoritedStreams)); }); }); diff --git a/src/lib/streamCache.svelte.ts b/src/lib/streamCache.svelte.ts index b2963cc..63678b2 100644 --- a/src/lib/streamCache.svelte.ts +++ b/src/lib/streamCache.svelte.ts @@ -1,5 +1,6 @@ import { browser } from '$app/environment'; import { SvelteSet, SvelteMap } from 'svelte/reactivity'; +import { readStored, writeStored } from './utils.ts'; import type { StreamSummary, PartialDownload } from './types.ts'; const STORAGE_KEY = 'cachedStreams'; @@ -16,23 +17,21 @@ const CHUNK_SIZE = 8 * 1024 * 1024; // as the intent is to catch socket breakage during cellular network changes for example const CHUNK_TIMEOUT = 60_000; -export const cached = new SvelteSet( - browser ? JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') : [] -); +export const cached = new SvelteSet(readStored(STORAGE_KEY, [])); export const downloading = new SvelteMap(); // bytes already written to OPFS for each incomplete download export const partials = new SvelteMap( - browser ? Object.entries(JSON.parse(localStorage.getItem(PARTIALS_KEY) || '{}')) : [] + Object.entries(readStored>(PARTIALS_KEY, {})) ); if (browser) { $effect.root(() => { $effect(() => { - localStorage.setItem(STORAGE_KEY, JSON.stringify([...cached])); + writeStored(STORAGE_KEY, [...cached]); }); $effect(() => { - localStorage.setItem(PARTIALS_KEY, JSON.stringify(Object.fromEntries(partials))); + writeStored(PARTIALS_KEY, Object.fromEntries(partials)); }); }); } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 7b6cdac..24238e3 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,7 +1,26 @@ +import { browser } from '$app/environment'; import Sqids from 'sqids'; const sqids = new Sqids({ minLength: 6, alphabet: 'abcdefghijklmnopqrstuvwxyz0123456789' }); +// browser blocks site data => localStorage throws +// so we're wrapping our calls to ensure things running at module init +// don't bring down the site render entirely +export function readStored(key: string, fallback: T): T { + if (!browser) return fallback; + try { + return (JSON.parse(localStorage.getItem(key) ?? '') as T) ?? fallback; + } catch { + return fallback; + } +} + +export function writeStored(key: string, value: unknown) { + try { + localStorage.setItem(key, JSON.stringify(value)); + } catch {} +} + export function hashcode(str: string): number { let h = 9; for (let i = 0; i < str.length; ) h = Math.imul(h ^ str.charCodeAt(i++), 9 ** 9); diff --git a/src/routes/streams/[stream_id]/Player.svelte b/src/routes/streams/[stream_id]/Player.svelte index a9cf908..d1df251 100644 --- a/src/routes/streams/[stream_id]/Player.svelte +++ b/src/routes/streams/[stream_id]/Player.svelte @@ -16,6 +16,7 @@ import { onMount, untrack } from 'svelte'; import { fade } from 'svelte/transition'; import { getStreamContext } from '$lib/streamContext.svelte.ts'; + import { readStored, writeStored } from '$lib/utils.ts'; import type { Track } from '$lib/types'; const ctx = getStreamContext(); @@ -87,8 +88,7 @@ isSafari = isIOS || isDesktopSafari; // default volume - const volumeData = localStorage.getItem('volume'); - volume = volumeData ? parseFloat(volumeData) : 0.67; + volume = readStored('volume', 0.67); setVolume(volume); // set actions for previous/next track media buttons @@ -181,7 +181,7 @@ if (!volumeBar) return; volume = seek(event, volumeBar.getBoundingClientRect()); setVolume(volume); - localStorage.setItem('volume', volume.toString()); + writeStored('volume', volume); muted = false; }