perf: reduce CPU usage and unify next-track buffering settings

- Throttle audio:progress from 100ms to 500ms; WaveformSeek and
  FsSeekbar use imperative DOM updates instead of React re-renders
- Fix all usePlayerStore() calls without selectors across pages and
  components (useShallow / individual selectors throughout)
- Remove filter:blur and transform:scale from Hero, AlbumDetail and
  FullscreenPlayer backgrounds — eliminated expensive software
  compositing layers on WebKitGTK
- Replace translate3d with 2D translate in FS mesh and portrait
  animations; remove will-change:transform from mesh blobs
- Move Hot Cache section from Storage tab to Audio tab; group Preload
  and Hot Cache under a shared 'Next Track Buffering' section with a
  mutual-exclusivity note and auto-disable logic
- Add 'off' toggle to Preload (replaces Off button with toggle switch);
  enabling either method now automatically disables the other

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-10 13:38:33 +02:00
parent c9dfbcc19f
commit c49f6af38b
21 changed files with 398 additions and 271 deletions
+27 -8
View File
@@ -331,11 +331,13 @@ function handleAudioProgress(current_time: number, duration: number) {
// Pre-buffer / pre-chain next track based on preload mode.
const { gaplessEnabled, preloadMode, preloadCustomSeconds } = useAuthStore.getState();
const remaining = dur - current_time;
const shouldPreload = preloadMode === 'early'
? current_time >= 5
: preloadMode === 'custom'
? remaining < preloadCustomSeconds && remaining > 0
: remaining < 30 && remaining > 0; // balanced (default)
const shouldPreload = preloadMode !== 'off' && (
preloadMode === 'early'
? current_time >= 5
: preloadMode === 'custom'
? remaining < preloadCustomSeconds && remaining > 0
: remaining < 30 && remaining > 0 // balanced (default)
);
if (shouldPreload) {
const { queue, queueIndex, repeatMode } = store;
const nextIdx = queueIndex + 1;
@@ -483,11 +485,28 @@ function handleAudioError(message: string) {
* set of listeners before creating the second, avoiding duplicate handlers.
*/
export function initAudioListeners(): () => void {
// Dev-only: warn when audio:progress events arrive faster than 10/s.
// This would indicate the Rust emit interval was accidentally lowered.
let _devEventCount = 0;
let _devWindowStart = 0;
const pending = [
listen<number>('audio:playing', ({ payload }) => handleAudioPlaying(payload)),
listen<{ current_time: number; duration: number }>('audio:progress', ({ payload }) =>
handleAudioProgress(payload.current_time, payload.duration)
),
listen<{ current_time: number; duration: number }>('audio:progress', ({ payload }) => {
if (import.meta.env.DEV) {
_devEventCount++;
const now = Date.now();
if (_devWindowStart === 0) _devWindowStart = now;
if (now - _devWindowStart >= 1000) {
if (_devEventCount > 10) {
console.warn(`[psysonic] audio:progress: ${_devEventCount} events/s (threshold: 10) — check Rust emit interval`);
}
_devEventCount = 0;
_devWindowStart = now;
}
}
handleAudioProgress(payload.current_time, payload.duration);
}),
listen<void>('audio:ended', () => handleAudioEnded()),
listen<string>('audio:error', ({ payload }) => handleAudioError(payload)),
listen<number>('audio:track_switched', ({ payload }) => handleAudioTrackSwitched(payload)),