From 4639b2d19cbd8b79ad9115542e8f3509123d933c Mon Sep 17 00:00:00 2001 From: Wim Brand Date: Wed, 2 Sep 2026 00:13:32 +0200 Subject: [PATCH] Add prefetching for images and audio. Add cover image to mediasession --- svelte/src/filesystem/viewers/Audio.svelte | 87 +++++++++++++++++----- svelte/src/filesystem/viewers/Image.svelte | 13 +++- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/svelte/src/filesystem/viewers/Audio.svelte b/svelte/src/filesystem/viewers/Audio.svelte index b9d0a93..d2b4241 100644 --- a/svelte/src/filesystem/viewers/Audio.svelte +++ b/svelte/src/filesystem/viewers/Audio.svelte @@ -14,6 +14,54 @@ let playing = $state(false) let media_session = false let siblings: FSNode[] = $state([]) +// The player source is managed manually instead of with a src attribute so +// that change_track can swap it without svelte reloading it afterwards +let current_src = "" +$effect(() => { + const url = fs_path_url($nav.base.path) + if (url !== current_src) { + current_src = url + player.src = url + player.play().catch(() => {}) + } +}) + +// Preloads the next track into the browser cache so that switching to it +// doesn't have to wait for the network. Files are served with a one year cache +// lifetime, so the player picks the data straight out of the cache +const preloader = new Audio() +preloader.preload = "auto" +$effect(() => { + const index = siblings.findIndex(s => s.path === $nav.base.path) + const next = siblings[index + 1] + if (index !== -1 && next !== undefined && next.type === "file" && !nav.shuffle) { + preloader.src = fs_path_url(next.path) + } +}) + +// Switching tracks starts playback of the new file immediately and only then +// navigates to it. Firefox on Android freezes a background tab as soon as it +// stops playing audio, so anything waiting on a network request after the +// ended event would never run +const change_track = (offset: number) => { + const index = siblings.findIndex(s => s.path === $nav.base.path) + if (index === -1) { + return + } + + const next = nav.shuffle ? + siblings[Math.floor(Math.random() * siblings.length)] : + siblings[index + offset] + if (next === undefined || next.type === "dir") { + return + } + + current_src = fs_path_url(next.path) + player.src = current_src + player.play().catch(() => {}) + nav.navigate(next.path, true) +} + export const toggle_playback = () => playing ? player.pause() : player.play() export const toggle_mute = () => player.muted = !player.muted @@ -29,24 +77,29 @@ export const seek = (delta: number) => { var background_div: HTMLDivElement export const update = async () => { + siblings = await nav.get_siblings() + + let cover = "" + for (const sib of siblings) { + const lower = sib.name.toLowerCase() + if (lower === "cover.jpg" || lower === "cover.png" || lower === "cover.webp") { + cover = fs_path_url(sib.path) + break + } + } + + // Always assigned, so that the cover of the previous album is cleared when + // the new one doesn't have one + background_div.style.backgroundImage = cover === "" ? "" : `url("${cover}")` + if (media_session) { navigator.mediaSession.metadata = new MediaMetadata({ title: nav.base.name, artist: "Nova", album: "unknown", + artwork: cover === "" ? [] : [{ src: cover }], }); } - - siblings = await nav.get_siblings() - - for(const sib of siblings) { - const lower = sib.name.toLowerCase() - if (lower === "cover.jpg" || lower === "cover.png" || lower === "cover.webp") { - console.debug("Found album cover image", sib) - background_div.style.backgroundImage = `url("/api/filesystem/${sib.path}")` - break - } - } } onMount(() => { @@ -55,8 +108,8 @@ onMount(() => { navigator.mediaSession.setActionHandler('play', () => player.play()); navigator.mediaSession.setActionHandler('pause', () => player.pause()); navigator.mediaSession.setActionHandler('stop', () => player.pause()); - navigator.mediaSession.setActionHandler('previoustrack', () => nav.open_sibling(-1)); - navigator.mediaSession.setActionHandler('nexttrack', () => nav.open_sibling(1)); + navigator.mediaSession.setActionHandler('previoustrack', () => change_track(-1)); + navigator.mediaSession.setActionHandler('nexttrack', () => change_track(1)); } }) @@ -68,16 +121,14 @@ onMount(() => {
- + - +

Tracklist

diff --git a/svelte/src/filesystem/viewers/Image.svelte b/svelte/src/filesystem/viewers/Image.svelte index 736c37f..fec7a30 100644 --- a/svelte/src/filesystem/viewers/Image.svelte +++ b/svelte/src/filesystem/viewers/Image.svelte @@ -1,6 +1,6 @@