Audio Store
A global, reactive Svelte 5 store for managing audio playback state and queue.
Installation
Copy and paste the following code into your project.
Import
Import the global store and helper types directly into your Svelte components:
<script lang="ts">
import {
audioStore,
calculateNextIndex,
calculatePreviousIndex,
canUseDOM,
type RepeatMode,
type InsertMode,
} from "$lib/audio-store.svelte";
</script> Core Concepts
The audioStore Singleton
We leverage Svelte 5's powerful $state runes to create a highly reactive, class-based global store. Instead of messy subscriptions or cumbersome contexts, you just import audioStore and read its properties anywhere. Svelte handles the magic.
<script lang="ts">
import { audioStore } from "$lib/audio-store.svelte";
</script>
<p>
{audioStore.currentTrack?.title} ({audioStore.duration}s) — {audioStore.isPlaying
? "▶"
: "⏸"}
</p> Architecture
The AudioStore focuses purely on state management. It doesn't touch the <audio> element directly. Instead, the AudioProvider component listens to the store and orchestrates the browser's Audio API.
Store State
The store exposes properties organized by concern. All of these are deeply reactive ($state).
Types
Utility Functions
calculateNextIndex
Calculate the next track index based on playback mode, queue length, and shuffle state.
const nextIndex = calculateNextIndex({
queue: audioStore.queue,
currentQueueIndex: audioStore.currentQueueIndex,
shuffleEnabled: audioStore.shuffleEnabled,
repeatMode: audioStore.repeatMode,
});
// Returns: number (track index or -1 if none) calculatePreviousIndex
Calculate the previous track index based on playback mode.
const prevIndex = calculatePreviousIndex({
queue: audioStore.queue,
currentQueueIndex: audioStore.currentQueueIndex,
shuffleEnabled: audioStore.shuffleEnabled,
repeatMode: audioStore.repeatMode,
});
// Returns: number (track index or -1 if none) canUseDOM()
Check if code runs in a DOM environment (safe for SSR).
if (canUseDOM()) {
// Safe to access window or localStorage
} Actions
Access actions directly on the audioStore instance.
Examples
Basic Usage
Building a custom minimal player is insanely easy. Just wire up the store.
<script lang="ts">
import { audioStore } from "$lib/audio-store.svelte";
function formatDuration(seconds: number) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, "0")}`;
}
</script>
<div class="rounded-md border p-4">
<p class="font-bold">Track: {audioStore.currentTrack?.title ?? "None"}</p>
<p class="text-sm text-gray-500">
Time: {formatDuration(audioStore.currentTime)} / {formatDuration(
audioStore.duration
)}
</p>
<p class="mb-2">Status: {audioStore.isPlaying ? "▶ Playing" : "⏸ Paused"}</p>
<div class="flex gap-2">
<button onclick={() => audioStore.previous()}>◀ Prev</button>
<button onclick={() => audioStore.togglePlay()}>
{audioStore.isPlaying ? "⏸" : "▶"}
</button>
<button onclick={() => audioStore.next()}>Next ▶</button>
</div>
</div> Queue Management
Easily manipulate the queue with just a few method calls.
<script lang="ts">
import { audioStore } from "$lib/audio-store.svelte";
const newTrack = { id: "123", title: "Indie Banger", src: "/music.mp3" };
</script>
<div>
<div class="mb-4 flex gap-2">
<button onclick={() => audioStore.addToQueue(newTrack, "last")}
>Add Track</button
>
<button onclick={() => audioStore.clearQueue()}>Clear</button>
</div>
<div class="space-y-1">
{#each audioStore.queue as track (track.id)}
<div class="flex justify-between border p-2">
<span>{track.title}</span>
<button onclick={() => audioStore.removeFromQueue(track.id)}
>Remove</button
>
</div>
{/each}
</div>
</div> Outside of Components
Need to kick off music from a vanilla .ts file or a SvelteKit load function? Since audioStore is a class singleton, it works anywhere.
import { audioStore } from "$lib/audio-store.svelte";
// Read state
console.log(audioStore.isPlaying);
// Call actions
audioStore.play();
// Update volume
audioStore.setVolume({ volume: 0.5 }); Persistence
We hate it when a refresh kills the vibe. The store automatically hydrates and syncs a subset of its state to localStorage on the fly.
Storage Key: audio:ui:store
The user can resume their queue seamlessly after a page refresh, as if they never left.
Related
- Audio Library — Core HTMLAudio integration
- Audio Provider — Handles the HTML
<audio>bindings. - Audio Player — Drop-in UI components.
- Audio Queue — Queue UI management.
Notes
audioStorefocuses solely on state. TheAudioProvidercomponent handles the actual loading and syncing of the<audio>element.- Ensure the
AudioProviderwraps your app, or at least the part where playback happens, for the state to translate to real sound. - Async events (buffering, track end) are driven by the
AudioProviderwhich calls methods likehandleTrackEnd()or setssyncTime()on theaudioStore. - The
playbackRateautomatically resets to1when loading live streams to prevent awful chipmunk audio.