diff --git a/web/src/lib/player/audioLoader.test.ts b/web/src/lib/player/audioLoader.test.ts new file mode 100644 index 00000000..836c5f61 --- /dev/null +++ b/web/src/lib/player/audioLoader.test.ts @@ -0,0 +1,52 @@ +import { afterEach, describe, expect, test, vi } from 'vitest'; +import type { TrackRef } from '$lib/api/types'; +import { audioLoader, setAudioLoader, __resetAudioLoader } from './audioLoader'; + +const sampleTrack: TrackRef = { + id: 't1', + title: 'Sample', + album_id: 'a1', + album_title: 'Album', + artist_id: 'ar1', + artist_name: 'Artist', + duration_sec: 180, + stream_url: '/api/tracks/t1/stream' +}; + +afterEach(() => { + __resetAudioLoader(); +}); + +describe('audioLoader', () => { + test('default resolve returns the track stream_url', () => { + expect(audioLoader.resolve(sampleTrack)).toBe('/api/tracks/t1/stream'); + }); + + test('default prefetch is a no-op (does not throw)', () => { + expect(() => audioLoader.prefetch?.(sampleTrack)).not.toThrow(); + }); + + test('setAudioLoader swaps the resolver', () => { + setAudioLoader({ + resolve: (t) => `blob:cached-${t.id}` + }); + expect(audioLoader.resolve(sampleTrack)).toBe('blob:cached-t1'); + }); + + test('setAudioLoader swaps the prefetch hook', () => { + const spy = vi.fn(); + setAudioLoader({ + resolve: (t) => t.stream_url, + prefetch: spy + }); + audioLoader.prefetch?.(sampleTrack); + expect(spy).toHaveBeenCalledWith(sampleTrack); + }); + + test('__resetAudioLoader restores the default', () => { + setAudioLoader({ resolve: () => 'blob:swapped' }); + expect(audioLoader.resolve(sampleTrack)).toBe('blob:swapped'); + __resetAudioLoader(); + expect(audioLoader.resolve(sampleTrack)).toBe('/api/tracks/t1/stream'); + }); +}); diff --git a/web/src/lib/player/audioLoader.ts b/web/src/lib/player/audioLoader.ts new file mode 100644 index 00000000..7b804ab9 --- /dev/null +++ b/web/src/lib/player/audioLoader.ts @@ -0,0 +1,41 @@ +// Audio loader seam. Today: returns the server-relative stream URL +// directly. In the future Tauri shell, the loader can be swapped to +// return blob URLs backed by a Rust-side cache (mirroring the Flutter +// drift+LockCachingAudioSource pattern), without touching the player UI. +// +// Two operations: +// - resolve(track) — returns the URL the