fix(preview): Symphonia format sniff and ranged stream startup (#1006)

* fix(preview): Symphonia format sniff and cluster member stream URLs

Resolve preview container hints from HTTP headers, Subsonic suffix, and
magic-byte sniff after Symphonia 0.6. Route preview streams through
clusterBrowseServerId like main playback; guard CoverArtImage when the
preview cover ref is still loading.

* fix(preview): adapt Symphonia sniff branch for main without cluster routing

Keep formatSuffix and cover-ref guards from the cluster work; use
buildStreamUrl on the active server instead of clusterBrowseServerId.

* fix(preview): use ranged HTTP so preview starts without full-file download

Open preview via RangedHttpSource when the server supports byte ranges;
fall back to buffered download otherwise. Gate in-memory probe with
ProbeSeekGate so Symphonia 0.6 does not scan the entire file before audio.

* docs: CHANGELOG and credits for preview fix PR #1006

* chore: drop PR #1006 from settings credits (minor fix)

* fix(preview): allow clippy too_many_arguments on audio_preview_play

formatSuffix pushed the Tauri command to 8 args; matches other IPC commands.
This commit is contained in:
cucadmuh
2026-06-05 22:59:20 +03:00
committed by GitHub
parent f706336e58
commit a66d932afe
13 changed files with 395 additions and 73 deletions
+7 -1
View File
@@ -200,6 +200,12 @@ describe('previewStore — startPreview', () => {
expect(state.duration).toBe(30);
});
it('passes Subsonic suffix as formatSuffix for Symphonia container hints', async () => {
await usePreviewStore.getState().startPreview({ ...song('flac-1'), suffix: 'flac' }, 'albums');
const call = invokeMock.mock.calls.find(c => c[0] === 'audio_preview_play');
expect(call?.[1]).toMatchObject({ formatSuffix: 'flac' });
});
it('starts at 0 when the track is too short to need a mid-track seek', async () => {
// duration <= previewDuration * 1.5 → start at 0.
await usePreviewStore.getState().startPreview({ ...song(), duration: 30 }, 'suggestions');
@@ -277,7 +283,7 @@ describe('previewStore — startPreview', () => {
throw new Error('engine offline');
});
await expect(usePreviewStore.getState().startPreview(song('song-2'), 'suggestions')).rejects.toThrow(/engine offline/);
await usePreviewStore.getState().startPreview(song('song-2'), 'suggestions');
// Only rolls back when the rolled-back id is still the optimistic one.
const state = usePreviewStore.getState();