diff --git a/src/app.css b/src/app.css index 85209d1..4502dd0 100644 --- a/src/app.css +++ b/src/app.css @@ -174,6 +174,7 @@ html { font-size: calc(var(--font-size) * env(preferred-text-scale, 1)); font-family: var(--font-family); + -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; overscroll-behavior: none; diff --git a/src/lib/offline/offlineManager.svelte.ts b/src/lib/offline/offlineManager.svelte.ts index 85d3474..14f5a47 100644 --- a/src/lib/offline/offlineManager.svelte.ts +++ b/src/lib/offline/offlineManager.svelte.ts @@ -288,11 +288,17 @@ class OfflineManager { } } + private touchSong(song: Song) { + song.accessed = Date.now(); + store.putSong(song).catch(() => {}); + } + async getAudioUrl(song: Song): Promise { + if (!settings.downloads) return null; + const cached = offlineAudios.get(song.id); if (cached) { - song.accessed = Date.now(); - await store.putSong(song); + this.touchSong(song); return cached; } @@ -307,8 +313,7 @@ class OfflineManager { const key = `song_${song.id}`; const blob = await storage.readBlob(key); if (blob) { - song.accessed = Date.now(); - await store.putSong(song); + this.touchSong(song); const type = song.contentType || blob.type || "audio/mpeg"; return setObjectUrl(offlineAudios, song.id, new Blob([blob], { type })); } diff --git a/src/lib/player.svelte.ts b/src/lib/player.svelte.ts index ec6202e..0e5c1e0 100644 --- a/src/lib/player.svelte.ts +++ b/src/lib/player.svelte.ts @@ -16,6 +16,16 @@ const audio = new Audio(); audio.crossOrigin = "anonymous"; audio.preload = "auto"; const faviconUrl = `${import.meta.env.BASE_URL}favicon.svg`; + +const setMediaAction = ( + action: MediaSessionAction, + handler: MediaSessionActionHandler | null, +) => { + try { + navigator.mediaSession.setActionHandler(action, handler); + } catch {} +}; + audio.onpause = () => { player.paused = true; updateMediaPosition(); @@ -225,6 +235,7 @@ export const load = async (song: Song, time = 0, autoplay = false) => { pendingTime = streamOffset ? 0 : time; const localAudioUrl = await offlineManager.getAudioUrl(song); + if (player.track !== song) return; player.audio.src = localAudioUrl || playbackUrl(song.id, streamOffset); if (localAudioUrl) { @@ -266,7 +277,10 @@ export const stop = () => { streamOffset = 0; player.audio.pause(); player.audio.src = ""; - Object.assign(player, { track: null, paused: true, time: 0, duration: 0 }); + player.track = null; + player.paused = true; + player.time = 0; + player.duration = 0; player.loading = false; queue.pos = -1; updateMediaPosition(); @@ -302,14 +316,15 @@ export const setupMediaSession = () => { const mediaSession = navigator.mediaSession; if (!mediaSession) return; - mediaSession.setActionHandler("play", () => { + setMediaAction("play", () => { if (player.paused) toggle(); }); - mediaSession.setActionHandler("pause", () => { + setMediaAction("pause", () => { if (!player.paused) toggle(); }); - mediaSession.setActionHandler("previoustrack", prev); - mediaSession.setActionHandler("nexttrack", next); + setMediaAction("previoustrack", prev); + setMediaAction("nexttrack", next); + setMediaAction("stop", stop); }; $effect.root(() => { @@ -318,10 +333,8 @@ $effect.root(() => { let streamSettingsTimeout: ReturnType | undefined; $effect(() => { - const mediaSession = navigator.mediaSession; - if (!mediaSession) return; const track = player.track; - mediaSession.setActionHandler( + setMediaAction( "seekto", track && !isRadio(track) ? (e) => e.seekTime !== undefined && seek(e.seekTime) @@ -373,7 +386,6 @@ $effect.root(() => { $effect(() => { const { track } = player; const link = document.querySelector('link[rel="icon"]') as HTMLLinkElement; - const mediaSession = navigator.mediaSession; if (track) { const artId = songArtId(track); @@ -386,14 +398,13 @@ $effect.root(() => { link.href = useDynamic ? artUrl : faviconUrl; link.type = useDynamic ? "image/jpeg" : "image/svg+xml"; } + const mediaSession = navigator.mediaSession; if (mediaSession) { mediaSession.metadata = new MediaMetadata({ title: track.title, artist: songArtist(track), album: track.album, - artwork: artUrl - ? [{ src: artUrl, sizes: "512x512", type: "image/jpeg" }] - : [], + artwork: artUrl ? [{ src: artUrl, sizes: "512x512" }] : [], }); } } else { @@ -403,6 +414,7 @@ $effect.root(() => { link.href = faviconUrl; link.type = "image/svg+xml"; } + const mediaSession = navigator.mediaSession; if (mediaSession) mediaSession.metadata = null; } });