diff --git a/.gitignore b/.gitignore
index 2f3c66b..30f4604 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
/docker/inbox
/docker/storage
.env
+.DS_Store
diff --git a/furumi-node-player/client/public/sw.js b/furumi-node-player/client/public/sw.js
new file mode 100644
index 0000000..6ef25be
--- /dev/null
+++ b/furumi-node-player/client/public/sw.js
@@ -0,0 +1,23 @@
+let bearerToken = null
+
+self.addEventListener('message', (e) => {
+ if (e.data?.type === 'SET_TOKEN') {
+ bearerToken = e.data.token
+ }
+})
+
+self.addEventListener('fetch', (e) => {
+ const url = new URL(e.request.url)
+ // Only intercept /api/ requests to the same origin
+ if (url.origin !== self.location.origin || !url.pathname.startsWith('/api/')) return
+ if (!bearerToken) return
+
+ const authedRequest = new Request(e.request, {
+ headers: new Headers(e.request.headers),
+ })
+ authedRequest.headers.set('Authorization', `Bearer ${bearerToken}`)
+ e.respondWith(fetch(authedRequest))
+})
+
+self.addEventListener('install', () => self.skipWaiting())
+self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()))
diff --git a/furumi-node-player/client/src/FurumiPlayer.tsx b/furumi-node-player/client/src/FurumiPlayer.tsx
index 4217803..2633975 100644
--- a/furumi-node-player/client/src/FurumiPlayer.tsx
+++ b/furumi-node-player/client/src/FurumiPlayer.tsx
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState, type MouseEvent as ReactMouseEvent } from 'react'
import './furumi-player.css'
-import { searchTracks, preloadStream, fetchCoverBlob, recordPlay } from './furumiApi'
+import { API_ROOT, searchTracks, recordPlay } from './furumiApi'
import { store, useAppDispatch, useAppSelector } from './store'
import { fetchArtists } from './store/slices/artistsSlice'
import { fetchArtistAlbums } from './store/slices/albumsSlice'
@@ -88,14 +88,12 @@ export function FurumiPlayer({ user }: { user: UserProfile }) {
document.title = `${nowPlayingTrack.title} — Furumi`
if ('mediaSession' in navigator) {
try {
- const meta = new window.MediaMetadata({
+ const coverUrl = `${API_ROOT}/tracks/${nowPlayingTrack.slug}/cover`
+ navigator.mediaSession.metadata = new window.MediaMetadata({
title: nowPlayingTrack.title,
artist: nowPlayingTrack.artist || '',
album: '',
- })
- navigator.mediaSession.metadata = meta
- fetchCoverBlob(nowPlayingTrack.slug).then((url) => {
- if (url) meta.artwork = [{ src: url, sizes: '512x512' }]
+ artwork: [{ src: coverUrl, sizes: '512x512' }],
})
} catch {
// ignore
@@ -392,7 +390,7 @@ export function FurumiPlayer({ user }: { user: UserProfile }) {
{ slug, title: '', artist: '', album_slug: null, duration: null },
true,
)
- void preloadStream(slug)
+ void playback.loadStreamForTrack(slug)
}
}
searchSelectRef.current = onSearchSelect
diff --git a/furumi-node-player/client/src/audioPlaybackService.ts b/furumi-node-player/client/src/audioPlaybackService.ts
index e064fbd..9d94ad2 100644
--- a/furumi-node-player/client/src/audioPlaybackService.ts
+++ b/furumi-node-player/client/src/audioPlaybackService.ts
@@ -1,4 +1,4 @@
-import { preloadStream } from './furumiApi'
+import { API_ROOT } from './furumiApi'
import { fmt } from './utils'
const MAX_PLAYBACK_ERROR_SKIPS = 5
@@ -109,8 +109,7 @@ export function attachAudioPlayback(
volSlider?.addEventListener('input', onVolInput)
async function loadStreamForTrack(slug: string) {
- const response = await preloadStream(slug)
- audio.src = URL.createObjectURL(response?.data)
+ audio.src = `${API_ROOT}/stream/${slug}`
await audio.play().catch(() => { })
}
diff --git a/furumi-node-player/client/src/components/NowPlaying.tsx b/furumi-node-player/client/src/components/NowPlaying.tsx
index 25e4793..ac323bc 100644
--- a/furumi-node-player/client/src/components/NowPlaying.tsx
+++ b/furumi-node-player/client/src/components/NowPlaying.tsx
@@ -1,12 +1,15 @@
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
+import { API_ROOT } from '../furumiApi'
import type { QueueItem } from './QueueList'
-import { useCoverUrl } from '../hooks/useCoverUrl'
-function Cover({ slug }: { slug: string }) {
+function Cover({ src }: { src: string }) {
const [errored, setErrored] = useState(false)
- const src = useCoverUrl(slug)
- if (!src || errored) return <>🎵>
+ useEffect(() => {
+ setErrored(false)
+ }, [src])
+
+ if (errored) return <>🎵>
return setErrored(true)} />
}
@@ -29,10 +32,12 @@ export function NowPlaying({ track }: { track: QueueItem | null }) {
)
}
+ const coverUrl = `${API_ROOT}/tracks/${track.slug}/cover`
+
return (