feat(waveform): real amplitude waveform via Symphonia + smooth crossfade

Add Rust command `compute_waveform` that downloads the audio file,
seek-samples 500 evenly-spaced frames with Symphonia (fast path) and
computes peak amplitudes. Percentile-based normalisation (p5→p95)
preserves visible dynamics even for heavily compressed music.

Frontend caches results per track (module-level Map), shows the
pseudo-random fallback immediately and crossfades to the real waveform
over 400 ms (ease-in-out) once the computation finishes.

Also: Settings input-tab reset buttons now use btn-danger styling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-07 23:31:15 +02:00
parent 5be7f7cabd
commit 504c53e71d
5 changed files with 255 additions and 20 deletions
+6 -1
View File
@@ -7,6 +7,7 @@ import {
import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore';
import { buildCoverArtUrl, coverArtCacheKey, star, unstar } from '../api/subsonic';
import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl';
import CachedImage from './CachedImage';
import WaveformSeek from './WaveformSeek';
import Equalizer from './Equalizer';
@@ -39,6 +40,7 @@ export default function PlayerBar() {
starredOverrides, setStarredOverride,
} = usePlayerStore();
const { lastfmSessionKey } = useAuthStore();
const serverId = useAuthStore(s => s.activeServerId ?? '');
const isRadio = !!currentRadio;
@@ -207,7 +209,10 @@ export default function PlayerBar() {
<>
<span className="player-time">{formatTime(currentTime)}</span>
<div className="player-waveform-wrap">
<WaveformSeek trackId={currentTrack?.id} />
<WaveformSeek
trackId={currentTrack?.id}
url={currentTrack ? resolvePlaybackUrl(currentTrack.id, serverId) : undefined}
/>
</div>
<span className="player-time">{formatTime(duration)}</span>
</>