From 40db6b08d26ebec4e053d64bb5a4da1116ca383b Mon Sep 17 00:00:00 2001 From: cucadmuh <49571317+cucadmuh@users.noreply.github.com> Date: Sat, 6 Jun 2026 01:01:08 +0300 Subject: [PATCH] chore(playback): remove Preload Next Track setting (#1007) * chore(playback): remove Preload Next Track setting The configurable next-track RAM preload duplicated hot cache and is obsolete now that ranged streaming starts playback sooner. Gapless and crossfade still use the internal audio_preload backup when hot cache is off. * docs: add CHANGELOG entry for Preload Next Track removal (PR #1007) --- CHANGELOG.md | 9 +++ .../psysonic-analysis/src/analysis_runtime.rs | 2 +- src/components/settings/StorageTab.tsx | 60 ------------------- src/components/settings/settingsTabs.ts | 2 +- src/hotCachePrefetch/analysisPrune.ts | 2 - src/locales/de/settings.ts | 10 +--- src/locales/en/settings.ts | 10 +--- src/locales/es/settings.ts | 10 +--- src/locales/fr/settings.ts | 10 +--- src/locales/nb/settings.ts | 10 +--- src/locales/nl/settings.ts | 10 +--- src/locales/ro/settings.ts | 10 +--- src/locales/ru/settings.ts | 10 +--- src/locales/zh/settings.ts | 10 +--- src/store/audioEventHandlers.ts | 41 +++---------- src/store/authPlumbingActions.ts | 8 +-- src/store/authStore.persistence.test.ts | 25 ++------ src/store/authStore.settings.test.ts | 10 +--- src/store/authStore.ts | 2 - src/store/authStoreRehydrate.ts | 9 +-- src/store/authStoreTypes.ts | 4 -- src/store/loudnessBackfillWindow.ts | 10 +--- 22 files changed, 39 insertions(+), 235 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b69d621..80646d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +### Playback — Preload Next Track setting removed + +**By [@cucadmuh](https://github.com/cucadmuh), PR [#1007](https://github.com/Psychotoxical/psysonic/pull/1007)** + +* The **Preload Next Track** toggle and timing modes under **Settings → Storage → Buffering** are gone — ranged streaming now starts playback without that extra RAM prefetch. +* Gapless and crossfade still prefetch the next track internally when Hot Cache is off; Hot Cache is unchanged. + + + ## Fixed ### Servers — complete border on the active server card diff --git a/src-tauri/crates/psysonic-analysis/src/analysis_runtime.rs b/src-tauri/crates/psysonic-analysis/src/analysis_runtime.rs index 3f7f5c1b..874e7a06 100644 --- a/src-tauri/crates/psysonic-analysis/src/analysis_runtime.rs +++ b/src-tauri/crates/psysonic-analysis/src/analysis_runtime.rs @@ -291,7 +291,7 @@ impl AnalysisBackfillQueueState { } } -/// Frontend-maintained set of queue-neighbour track ids (next ~5 + preload next). +/// Frontend-maintained set of queue-neighbour track ids (next ~5 in queue). #[derive(Default)] pub struct PlaybackPriorityHints { middle_track_ids: Mutex>, diff --git a/src/components/settings/StorageTab.tsx b/src/components/settings/StorageTab.tsx index 605eef68..6c1686b4 100644 --- a/src/components/settings/StorageTab.tsx +++ b/src/components/settings/StorageTab.tsx @@ -203,65 +203,6 @@ export function StorageTab() { icon={} >
-
- {t('settings.preloadHotCacheMutualExclusive')} -
- - {/* Preload mode */} -
-
-
{t('settings.preloadMode')}
-
{t('settings.preloadModeDesc')}
-
- -
- {auth.preloadMode !== 'off' && ( - <> -
- {(['balanced', 'early', 'custom'] as const).map(mode => ( - - ))} -
- {auth.preloadMode === 'custom' && ( -
- auth.setPreloadCustomSeconds(parseInt(e.target.value))} - style={{ flex: 1, minWidth: 80, maxWidth: 200 }} - /> - - {t('settings.preloadCustomSeconds', { n: auth.preloadCustomSeconds })} - -
- )} - - )} - -
- {/* Hot Cache */}
@@ -280,7 +221,6 @@ export function StorageTab() { auth.setHotCacheEnabled(false); } else { auth.setHotCacheEnabled(true); - if (auth.preloadMode !== 'off') auth.setPreloadMode('off'); invoke('get_hot_cache_size', { customDir: auth.hotCacheDownloadDir || null }) .then(setHotCacheBytes) .catch(() => setHotCacheBytes(0)); diff --git a/src/components/settings/settingsTabs.ts b/src/components/settings/settingsTabs.ts index 491b298c..a26e241b 100644 --- a/src/components/settings/settingsTabs.ts +++ b/src/components/settings/settingsTabs.ts @@ -57,7 +57,7 @@ export const SETTINGS_INDEX: SearchIndexEntry[] = [ { tab: 'library', titleKey: 'settings.ratingsSectionTitle', keywords: 'ratings stars skip threshold manual' }, { tab: 'storage', titleKey: 'settings.coverCacheStrategyTitle', keywords: 'cover art cache webp aggressive lazy disk per server' }, { tab: 'storage', titleKey: 'settings.offlineDirTitle', keywords: 'offline library download directory folder cache' }, - { tab: 'storage', titleKey: 'settings.nextTrackBufferingTitle', keywords: 'next track buffering preload hot cache streaming' }, + { tab: 'storage', titleKey: 'settings.nextTrackBufferingTitle', keywords: 'next track buffering hot cache streaming' }, { tab: 'storage', titleKey: 'settings.downloadsTitle', keywords: 'downloads zip export archive folder' }, { tab: 'appearance', titleKey: 'settings.theme', keywords: 'theme color palette dark light' }, { tab: 'appearance', titleKey: 'settings.themeSchedulerTitle', keywords: 'theme scheduler auto time dark mode sunset' }, diff --git a/src/hotCachePrefetch/analysisPrune.ts b/src/hotCachePrefetch/analysisPrune.ts index 194fb404..0ebc9168 100644 --- a/src/hotCachePrefetch/analysisPrune.ts +++ b/src/hotCachePrefetch/analysisPrune.ts @@ -20,7 +20,6 @@ type AnalysisPrunePendingResult = { export function scheduleAnalysisQueuePruneFromPlaybackQueue(): void { const { queueItems, currentTrack, queueIndex } = usePlayerStore.getState(); - const { preloadMode } = useAuthStore.getState(); const rawServerId = getPlaybackServerId() ?? ''; const server = useAuthStore.getState().servers.find(s => s.id === rawServerId); const serverId = server ? serverIndexKeyFromUrl(server.url) : rawServerId; @@ -42,7 +41,6 @@ export function scheduleAnalysisQueuePruneFromPlaybackQueue(): void { queueItems, queueIndex, currentTrack, - preloadMode, ); const sig = JSON.stringify({ keepTrackIds, middleTrackIds, serverId }); if (sig === lastAnalysisPruneSig) return; diff --git a/src/locales/de/settings.ts b/src/locales/de/settings.ts index 82210ee1..f059cdc5 100644 --- a/src/locales/de/settings.ts +++ b/src/locales/de/settings.ts @@ -490,15 +490,7 @@ export const settings = { trackPreviewLocation_favorites: 'In Favoriten', trackPreviewLocation_artist: 'In Künstler-Top-Tracks', trackPreviewLocation_randomMix: 'In Random Mix', - preloadMode: 'Nächsten Track vorpuffern', - preloadModeDesc: 'Wann mit dem Puffern des nächsten Tracks begonnen werden soll', nextTrackBufferingTitle: 'Pufferung', - preloadHotCacheMutualExclusive: 'Preload und Hot Cache erfüllen denselben Zweck – nur eine Methode kann gleichzeitig aktiv sein. Das Aktivieren einer deaktiviert die andere automatisch.', - preloadOff: 'Aus', - preloadBalanced: 'Ausgewogen (30 s vor Ende)', - preloadEarly: 'Früh (nach 5 s Wiedergabe)', - preloadCustom: 'Benutzerdefiniert', - preloadCustomSeconds: 'Sekunden vor Ende: {{n}}', infiniteQueue: 'Endlose Warteschlange', infiniteQueueDesc: 'Automatisch Zufallstitel anhängen wenn die Warteschlange leer wird', experimental: 'Experimentell', @@ -551,7 +543,7 @@ export const settings = { analyticsStrategyAdvancedDesc: 'Aggressiver Hintergrundscan. Nach Abschluss erhältst du BPM für alle Tracks sowie sofort verfügbare Loudness- und Waveform-Daten.', analyticsStrategyPriorityTitle: 'Analyse-Priorität (immer aktiv)', analyticsStrategyPriorityHigh: '1 — Aktuell laufender Track: zuerst herunterladen und analysieren (Playback-Bytes, Loudness-Backfill oder HTTP falls nötig).', - analyticsStrategyPriorityMiddle: '2 — Als Nächstes: die nächsten ~5 Queue-Tracks, Preload-next und Hot-Cache-Nachbarn — Analyse bevorzugt aus Cache/Downloads; HTTP-Backfill nur bei Miss.', + analyticsStrategyPriorityMiddle: '2 — Als Nächstes: die nächsten ~5 Queue-Tracks und Hot-Cache-Nachbarn — Analyse bevorzugt aus Cache/Downloads; HTTP-Backfill nur bei Miss.', analyticsStrategyPriorityLow: '3 — Bibliotheks-Batch (nur Aggressiv): automatische Hintergrund-Queue am Ende; wird per Watermark (~3× Worker) nachgefüllt, ohne auf eine leere Queue zu warten.', analyticsStrategyServerLabel: 'Server', analyticsStrategyProgressLabel: 'Analysefortschritt:', diff --git a/src/locales/en/settings.ts b/src/locales/en/settings.ts index 88f4542d..337e617b 100644 --- a/src/locales/en/settings.ts +++ b/src/locales/en/settings.ts @@ -334,7 +334,7 @@ export const settings = { analyticsStrategyAdvancedDesc: 'More aggressive background scan. Once finished, you get BPM for every track plus instant loudness + waveform loads.', analyticsStrategyPriorityTitle: 'Analysis priority (always enforced)', analyticsStrategyPriorityHigh: '1 — Currently playing: download + analyze first (playback bytes, loudness backfill, or HTTP when needed).', - analyticsStrategyPriorityMiddle: '2 — Up next: the next ~5 queue tracks, preload next, and hot-cache neighbours — analyze from cached/downloaded bytes when possible; HTTP backfill only on miss.', + analyticsStrategyPriorityMiddle: '2 — Up next: the next ~5 queue tracks and hot-cache neighbours — analyze from cached/downloaded bytes when possible; HTTP backfill only on miss.', analyticsStrategyPriorityLow: '3 — Library batch (Aggressive only): automatic background queue at the tail; refilled by watermark (~3× workers) without waiting for an empty queue.', analyticsStrategyServerLabel: 'Server', analyticsStrategyProgressLabel: 'Analysis progress:', @@ -577,15 +577,7 @@ export const settings = { trackPreviewLocation_favorites: 'In favorites', trackPreviewLocation_artist: 'In artist top tracks', trackPreviewLocation_randomMix: 'In Random Mix', - preloadMode: 'Preload Next Track', - preloadModeDesc: 'When to start buffering the next track in the queue', nextTrackBufferingTitle: 'Buffering', - preloadHotCacheMutualExclusive: 'Preload and Hot Cache serve the same purpose — only one can be active at a time. Enabling one will automatically disable the other.', - preloadOff: 'Off', - preloadBalanced: 'Balanced (30 s before end)', - preloadEarly: 'Early (after 5 s of playback)', - preloadCustom: 'Custom', - preloadCustomSeconds: 'Seconds before end: {{n}}', infiniteQueue: 'Infinite Queue', infiniteQueueDesc: 'Automatically append random tracks when the queue runs out', experimental: 'Experimental', diff --git a/src/locales/es/settings.ts b/src/locales/es/settings.ts index 00a9c5ad..d2488e1a 100644 --- a/src/locales/es/settings.ts +++ b/src/locales/es/settings.ts @@ -489,15 +489,7 @@ export const settings = { trackPreviewLocation_favorites: 'En favoritos', trackPreviewLocation_artist: 'En las mejores pistas del artista', trackPreviewLocation_randomMix: 'En Random Mix', - preloadMode: 'Precargar Siguiente Pista', - preloadModeDesc: 'Cuándo comenzar a cargar la siguiente pista en cola', nextTrackBufferingTitle: 'Buffer', - preloadHotCacheMutualExclusive: 'Precarga y Caché Activa sirven para lo mismo — solo uno puede estar activo a la vez. Habilitar uno desactivará automáticamente el otro.', - preloadOff: 'Apagado', - preloadBalanced: 'Balanceado (30 s antes del final)', - preloadEarly: 'Temprano (después de 5 s de reproducción)', - preloadCustom: 'Personalizado', - preloadCustomSeconds: 'Segundos antes del final: {{n}}', infiniteQueue: 'Cola Infinita', infiniteQueueDesc: 'Agregar automáticamente pistas aleatorias cuando la cola termina', experimental: 'Experimental', @@ -551,7 +543,7 @@ export const settings = { analyticsStrategyAdvancedDesc: 'Escaneo en segundo plano más agresivo. Al terminar, obtienes BPM para todas las pistas y carga inmediata de loudness y forma de onda.', analyticsStrategyPriorityTitle: 'Prioridad de análisis (siempre activa)', analyticsStrategyPriorityHigh: '1 — Reproducción actual: descargar y analizar primero (bytes de reproducción, backfill de loudness o HTTP cuando haga falta).', - analyticsStrategyPriorityMiddle: '2 — Siguientes: ~5 pistas de cola, precarga de la siguiente y vecinas de caché caliente; analiza desde caché/descarga cuando sea posible, con backfill HTTP solo en fallo.', + analyticsStrategyPriorityMiddle: '2 — Siguientes: ~5 pistas de cola y vecinas de caché caliente; analiza desde caché/descarga cuando sea posible, con backfill HTTP solo en fallo.', analyticsStrategyPriorityLow: '3 — Lote de biblioteca (solo Agresiva): cola automática en segundo plano al final; se rellena por watermark (~3× workers) sin esperar a que quede vacía.', analyticsStrategyServerLabel: 'Servidor', analyticsStrategyProgressLabel: 'Progreso del análisis:', diff --git a/src/locales/fr/settings.ts b/src/locales/fr/settings.ts index 463a0269..a56e1d86 100644 --- a/src/locales/fr/settings.ts +++ b/src/locales/fr/settings.ts @@ -487,15 +487,7 @@ export const settings = { trackPreviewLocation_favorites: 'Dans les favoris', trackPreviewLocation_artist: 'Dans les meilleurs morceaux de l\'artiste', trackPreviewLocation_randomMix: 'Dans Random Mix', - preloadMode: 'Précharger la piste suivante', - preloadModeDesc: 'Quand commencer à mettre en mémoire tampon la piste suivante', nextTrackBufferingTitle: 'Mise en mémoire tampon', - preloadHotCacheMutualExclusive: "Preload et Hot Cache remplissent le même rôle — un seul peut être actif à la fois. Activer l'un désactive automatiquement l'autre.", - preloadOff: 'Désactivé', - preloadBalanced: 'Équilibré (30 s avant la fin)', - preloadEarly: 'Tôt (après 5 s de lecture)', - preloadCustom: 'Personnalisé', - preloadCustomSeconds: 'Secondes avant la fin : {{n}}', infiniteQueue: 'File infinie', infiniteQueueDesc: 'Ajouter automatiquement des morceaux aléatoires quand la file est épuisée', experimental: 'Expérimental', @@ -551,7 +543,7 @@ export const settings = { analyticsStrategyAdvancedDesc: 'Analyse de fond plus agressive. Une fois terminée, vous obtenez le BPM pour toutes les pistes et un chargement immédiat de loudness et de formes d’onde.', analyticsStrategyPriorityTitle: 'Priorité d’analyse (toujours appliquée)', analyticsStrategyPriorityHigh: '1 — En cours de lecture : télécharger et analyser en premier (octets de lecture, backfill loudness ou HTTP si nécessaire).', - analyticsStrategyPriorityMiddle: '2 — À suivre : les ~5 pistes suivantes de la file, préchargement de la suivante et voisines du hot-cache — analyse depuis cache/téléchargements si possible, backfill HTTP seulement en cas d’absence.', + analyticsStrategyPriorityMiddle: '2 — À suivre : les ~5 pistes suivantes de la file et voisines du hot-cache — analyse depuis cache/téléchargements si possible, backfill HTTP seulement en cas d’absence.', analyticsStrategyPriorityLow: '3 — Lot bibliothèque (Agressive uniquement) : file d’arrière-plan automatique en fin de file ; réalimentée par watermark (~3× workers) sans attendre une file vide.', analyticsStrategyServerLabel: 'Server', analyticsStrategyProgressLabel: 'Progression de l’analyse :', diff --git a/src/locales/nb/settings.ts b/src/locales/nb/settings.ts index 1eb07481..ea2e7863 100644 --- a/src/locales/nb/settings.ts +++ b/src/locales/nb/settings.ts @@ -489,15 +489,7 @@ export const settings = { infiniteQueue: 'Uendelig kø', infiniteQueueDesc: 'Legg automatisk til tilfeldige spor når køen går tom', experimental: 'Eksperimentell', - preloadMode: 'Forhåndslast neste spor', - preloadModeDesc: 'Når buffering av neste spor i køen skal starte', nextTrackBufferingTitle: 'Bufring', - preloadHotCacheMutualExclusive: 'Forhåndslasting og Hot Cache tjener samme formål – bare én kan være aktiv om gangen. Å aktivere den ene deaktiverer automatisk den andre.', - preloadOff: 'Av', - preloadBalanced: 'Balansert (30 s før slutt)', - preloadEarly: 'Tidlig (etter 5 s avspilling)', - preloadCustom: 'Egendefinert', - preloadCustomSeconds: 'Sekunder før slutt: {{n}}', sidebarLyricsStyle: 'Rullestil for tekst', sidebarLyricsStyleClassic: 'Klassisk', sidebarLyricsStyleClassicDesc: 'Aktiv linje sentreres.', @@ -552,7 +544,7 @@ export const settings = { analyticsStrategyAdvancedDesc: 'Mer aggressiv bakgrunnsskanning. Når den er ferdig får du BPM for alle spor samt umiddelbar lasting av loudness og waveform.', analyticsStrategyPriorityTitle: 'Analyseprioritet (alltid aktiv)', analyticsStrategyPriorityHigh: '1 — Spiller nå: last ned og analyser først (avspillingsbytes, loudness-backfill eller HTTP ved behov).', - analyticsStrategyPriorityMiddle: '2 — Neste opp: de neste ~5 køsporene, preload av neste og hot-cache-naboer — analyser fra cache/nedlastede bytes når mulig; HTTP-backfill kun ved bom.', + analyticsStrategyPriorityMiddle: '2 — Neste opp: de neste ~5 køsporene og hot-cache-naboer — analyser fra cache/nedlastede bytes når mulig; HTTP-backfill kun ved bom.', analyticsStrategyPriorityLow: '3 — Bibliotekbatch (kun Aggressiv): automatisk bakgrunnskø bakerst; fylles på ved watermark (~3× workers) uten å vente på tom kø.', analyticsStrategyServerLabel: 'Server', analyticsStrategyProgressLabel: 'Analysefremdrift:', diff --git a/src/locales/nl/settings.ts b/src/locales/nl/settings.ts index 4459ec0b..32a4a4f0 100644 --- a/src/locales/nl/settings.ts +++ b/src/locales/nl/settings.ts @@ -487,15 +487,7 @@ export const settings = { trackPreviewLocation_favorites: 'In favorieten', trackPreviewLocation_artist: 'In top-tracks van artiest', trackPreviewLocation_randomMix: 'In Random Mix', - preloadMode: 'Volgend nummer vooraf laden', - preloadModeDesc: 'Wanneer met het bufferen van het volgende nummer wordt begonnen', nextTrackBufferingTitle: 'Buffering', - preloadHotCacheMutualExclusive: 'Preload en Hot Cache dienen hetzelfde doel — slechts één kan tegelijk actief zijn. Het inschakelen van de ene schakelt de andere automatisch uit.', - preloadOff: 'Uit', - preloadBalanced: 'Gebalanceerd (30 s voor einde)', - preloadEarly: 'Vroeg (na 5 s afspelen)', - preloadCustom: 'Aangepast', - preloadCustomSeconds: 'Seconden voor einde: {{n}}', infiniteQueue: 'Oneindige wachtrij', infiniteQueueDesc: 'Automatisch willekeurige nummers toevoegen als de wachtrij leeg raakt', experimental: 'Experimenteel', @@ -551,7 +543,7 @@ export const settings = { analyticsStrategyAdvancedDesc: 'Agressievere achtergrondscan. Na voltooiing krijg je BPM voor alle tracks plus directe loudness- en waveform-lading.', analyticsStrategyPriorityTitle: 'Analyseprioriteit (altijd actief)', analyticsStrategyPriorityHigh: '1 — Nu afspelen: eerst downloaden en analyseren (playback-bytes, loudness-backfill of HTTP indien nodig).', - analyticsStrategyPriorityMiddle: '2 — Hierna: de volgende ~5 wachtrijtracks, preload-next en hot-cache-buren — analyse vanuit cache/downloads waar mogelijk; HTTP-backfill alleen bij miss.', + analyticsStrategyPriorityMiddle: '2 — Hierna: de volgende ~5 wachtrijtracks en hot-cache-buren — analyse vanuit cache/downloads waar mogelijk; HTTP-backfill alleen bij miss.', analyticsStrategyPriorityLow: '3 — Bibliotheekbatch (alleen Agressief): automatische achtergrondwachtrij achteraan; wordt bijgevuld via watermark (~3× workers) zonder op een lege wachtrij te wachten.', analyticsStrategyServerLabel: 'Server', analyticsStrategyProgressLabel: 'Analysevoortgang:', diff --git a/src/locales/ro/settings.ts b/src/locales/ro/settings.ts index a5083399..7b8212f5 100644 --- a/src/locales/ro/settings.ts +++ b/src/locales/ro/settings.ts @@ -492,15 +492,7 @@ export const settings = { trackPreviewLocation_favorites: 'În favorite', trackPreviewLocation_artist: 'În top piese ale artistului', trackPreviewLocation_randomMix: 'În Mixul Aleatoriu', - preloadMode: 'Preîncarcă Următoarea Piesă', - preloadModeDesc: 'Când să înceapă preîncărcarea următoarei piese din coadă', nextTrackBufferingTitle: 'Preîncarcă', - preloadHotCacheMutualExclusive: 'Preîncarcarea și Hot Cache au același scop — doar una poate fi activată deodată. Pornirea unei opțiuni o dezactivează pe cealaltă.', - preloadOff: 'Oprit', - preloadBalanced: 'Balansat (30 s înainte de sfârșit)', - preloadEarly: 'Devreme (după 5 s de redare)', - preloadCustom: 'Personalizat', - preloadCustomSeconds: 'Secunde înainte de sfârșit: {{n}}', infiniteQueue: 'Coadă Infinită', infiniteQueueDesc: 'Adaugă automat piese aleatorii când coada se golește', experimental: 'Experimental', @@ -551,7 +543,7 @@ export const settings = { analyticsStrategyAdvancedDesc: 'Scanare de fundal mai agresivă. După finalizare, primești BPM pentru toate piesele și încărcare instantă pentru loudness și waveform.', analyticsStrategyPriorityTitle: 'Prioritate analiză (mereu activă)', analyticsStrategyPriorityHigh: '1 — Redare curentă: descarcă și analizează mai întâi (bytes de playback, loudness backfill sau HTTP când e nevoie).', - analyticsStrategyPriorityMiddle: '2 — Urmează: următoarele ~5 piese din coadă, preload pentru următoarea și vecinii din hot-cache — analiză din cache/descărcări când se poate; backfill HTTP doar la miss.', + analyticsStrategyPriorityMiddle: '2 — Urmează: următoarele ~5 piese din coadă și vecinii din hot-cache — analiză din cache/descărcări când se poate; backfill HTTP doar la miss.', analyticsStrategyPriorityLow: '3 — Lot bibliotecă (doar Agresivă): coadă automată în fundal la final; reumplere pe watermark (~3× workers) fără a aștepta golirea cozii.', analyticsStrategyServerLabel: 'Server', analyticsStrategyProgressLabel: 'Progres analiză:', diff --git a/src/locales/ru/settings.ts b/src/locales/ru/settings.ts index 7e942400..7a14f2a5 100644 --- a/src/locales/ru/settings.ts +++ b/src/locales/ru/settings.ts @@ -342,7 +342,7 @@ export const settings = { 'Запускает фоновый анализ всей библиотеки и скачивает треки для анализа.', analyticsStrategyPriorityTitle: 'Приоритет анализа (всегда)', analyticsStrategyPriorityHigh: '1 — Сейчас играет: скачивание и анализ в первую очередь (байты воспроизведения, loudness backfill или HTTP при необходимости).', - analyticsStrategyPriorityMiddle: '2 — Следующие: ~5 треков в очереди, preload next и соседи hot cache — анализ из уже скачанных байтов, HTTP backfill только при промахе.', + analyticsStrategyPriorityMiddle: '2 — Следующие: ~5 треков в очереди и соседи hot cache — анализ из уже скачанных байтов, HTTP backfill только при промахе.', analyticsStrategyPriorityLow: '3 — Библиотечный batch (только Агрессивный): автоматическая фоновая очередь в хвосте; пополняется по watermark (~3× потоков), не дожидаясь полного опустошения.', analyticsStrategyServerLabel: 'Сервер', analyticsStrategyProgressLabel: 'Прогресс анализа:', @@ -598,15 +598,7 @@ export const settings = { trackPreviewLocation_favorites: 'В избранном', trackPreviewLocation_artist: 'В топ-треках исполнителя', trackPreviewLocation_randomMix: 'В Random Mix', - preloadMode: 'Предзагрузка следующего трека', - preloadModeDesc: 'Когда начинать буферизацию следующего в очереди', nextTrackBufferingTitle: 'Буферизация', - preloadHotCacheMutualExclusive: 'Предзагрузка и Hot Cache выполняют одну функцию — одновременно может быть активна только одна. Включение одной автоматически отключает другую.', - preloadOff: 'Выкл.', - preloadBalanced: 'Умеренно (за 30 с до конца)', - preloadEarly: 'Рано (через 5 с после старта)', - preloadCustom: 'Свой интервал', - preloadCustomSeconds: 'Секунд до конца: {{n}}', infiniteQueue: 'Бесконечная очередь', infiniteQueueDesc: 'Подмешивать случайные треки, когда очередь закончится', experimental: 'Экспериментально', diff --git a/src/locales/zh/settings.ts b/src/locales/zh/settings.ts index e9467b8a..cb85b45b 100644 --- a/src/locales/zh/settings.ts +++ b/src/locales/zh/settings.ts @@ -486,15 +486,7 @@ export const settings = { trackPreviewLocation_favorites: '收藏中', trackPreviewLocation_artist: '艺人热门曲目中', trackPreviewLocation_randomMix: 'Random Mix 中', - preloadMode: '预加载下一曲目', - preloadModeDesc: '何时开始缓冲队列中的下一曲目', nextTrackBufferingTitle: '缓冲', - preloadHotCacheMutualExclusive: '预加载与热缓存用途相同——两者不能同时启用。启用其中一个会自动禁用另一个。', - preloadOff: '关闭', - preloadBalanced: '均衡(结束前30秒)', - preloadEarly: '提前(播放5秒后)', - preloadCustom: '自定义', - preloadCustomSeconds: '结束前秒数:{{n}}', infiniteQueue: '无限队列', infiniteQueueDesc: '队列播完时自动追加随机曲目', experimental: '实验性', @@ -550,7 +542,7 @@ export const settings = { analyticsStrategyAdvancedDesc: '更激进的后台扫描。完成后可获得所有歌曲 BPM,并可即时加载响度与波形。', analyticsStrategyPriorityTitle: '分析优先级(始终生效)', analyticsStrategyPriorityHigh: '1 — 当前播放:优先下载并分析(播放字节、响度回填,或在需要时走 HTTP)。', - analyticsStrategyPriorityMiddle: '2 — 即将播放:队列中接下来的约 5 首、预加载下一首以及热缓存邻近曲目——优先从缓存/已下载字节分析,未命中时才做 HTTP 回填。', + analyticsStrategyPriorityMiddle: '2 — 即将播放:队列中接下来的约 5 首及热缓存邻近曲目——优先从缓存/已下载字节分析,未命中时才做 HTTP 回填。', analyticsStrategyPriorityLow: '3 — 库批处理(仅“激进”):自动后台队列排在末尾;按水位线(约 3× workers)补充,无需等待队列清空。', analyticsStrategyServerLabel: '服务器', analyticsStrategyProgressLabel: '分析进度:', diff --git a/src/store/audioEventHandlers.ts b/src/store/audioEventHandlers.ts index ed4d1134..6472512d 100644 --- a/src/store/audioEventHandlers.ts +++ b/src/store/audioEventHandlers.ts @@ -207,46 +207,22 @@ export function handleAudioProgress( markStoreProgressCommit(nowCommit); } - // Pre-buffer / pre-chain next track based on preload mode and crossfade. + // Pre-buffer / pre-chain next track for gapless and crossfade. const { gaplessEnabled, - preloadMode, - preloadCustomSeconds, hotCacheEnabled, crossfadeEnabled, crossfadeSecs, } = useAuthStore.getState(); const remaining = dur - current_time; - // Gapless chain: always triggers at 30s regardless of preloadMode. const shouldChainGapless = gaplessEnabled && remaining < 30 && remaining > 0; - // Byte pre-download: skip when Hot Cache is active (it already handles buffering). - // Even with preload mode OFF, crossfade needs the next track bytes ready before - // we enter the fade window to avoid a hard gap after track boundary. - const shouldBytePreloadFromMode = preloadMode !== 'off' && ( - preloadMode === 'early' - ? current_time >= 5 - : preloadMode === 'custom' - ? remaining < preloadCustomSeconds && remaining > 0 - : remaining < 30 && remaining > 0 // balanced (default) - ); + // Crossfade needs the next track bytes ready before the fade window. const crossfadeWindowSecs = Math.max(8, Math.min(30, crossfadeSecs + 6)); const shouldBytePreloadForCrossfade = - !gaplessEnabled && crossfadeEnabled && remaining < crossfadeWindowSecs && remaining > 0; - const shouldBytePreload = !hotCacheEnabled && ( - shouldBytePreloadFromMode || - shouldBytePreloadForCrossfade - ); - // Hot/offline cache: seed enrichment from disk (playback also uses psysonic-local://). - const shouldPreloadLocalFileAnalysis = preloadMode !== 'off' && ( - preloadMode === 'early' - ? current_time >= 5 - : preloadMode === 'custom' - ? remaining < preloadCustomSeconds && remaining > 0 - : remaining < 30 && remaining > 0 - ); + !hotCacheEnabled && !gaplessEnabled && crossfadeEnabled && remaining < crossfadeWindowSecs && remaining > 0; - if (shouldChainGapless || shouldBytePreload || shouldPreloadLocalFileAnalysis || gaplessEnabled) { + if (shouldChainGapless || shouldBytePreloadForCrossfade || gaplessEnabled) { const { queueItems, queueIndex, repeatMode } = store; const nextIdx = queueIndex + 1; // Next track for preload/chain. The resolver bridge keeps the window around @@ -281,11 +257,10 @@ export function handleAudioProgress( const serverId = getPlaybackCacheServerKey(); const analysisServerId = getPlaybackIndexKey(); const nextUrl = resolvePlaybackUrl(nextTrack.id, serverId); - const nextIsLocalFile = nextUrl.startsWith('psysonic-local://'); - // Byte pre-download — runs early so bytes are cached by chain time. + // Byte pre-download — gapless backup or crossfade; runs early so bytes are ready by chain time. if ( - (shouldBytePreload || shouldBytePreloadForGaplessBackup || (shouldPreloadLocalFileAnalysis && nextIsLocalFile)) + (shouldBytePreloadForCrossfade || shouldBytePreloadForGaplessBackup) && nextTrack.id !== getBytePreloadingId() ) { setBytePreloadingId(nextTrack.id); @@ -296,10 +271,8 @@ export function handleAudioProgress( console.info('[psysonic][preload-request]', { nextTrackId: nextTrack.id, nextUrl, - shouldBytePreload, + shouldBytePreloadForCrossfade, shouldBytePreloadForGaplessBackup, - shouldPreloadLocalFileAnalysis, - nextIsLocalFile, remaining, gaplessEnabled, }); diff --git a/src/store/authPlumbingActions.ts b/src/store/authPlumbingActions.ts index b57fc6bb..28380c2d 100644 --- a/src/store/authPlumbingActions.ts +++ b/src/store/authPlumbingActions.ts @@ -6,23 +6,19 @@ type SetState = ( /** * Persistent plumbing settings that don't fit a more specific domain: - * runtime logging level, Navidrome `getNowPlaying` toggle, preload - * mode + custom seconds, audiobook exclusion, genre blacklist. + * runtime logging level, Navidrome `getNowPlaying` toggle, audiobook + * exclusion, genre blacklist. */ export function createPlumbingSettingsActions(set: SetState): Pick< AuthState, | 'setLoggingMode' | 'setNowPlayingEnabled' - | 'setPreloadMode' - | 'setPreloadCustomSeconds' | 'setExcludeAudiobooks' | 'setCustomGenreBlacklist' > { return { setLoggingMode: (v) => set({ loggingMode: v }), setNowPlayingEnabled: (v) => set({ nowPlayingEnabled: v }), - setPreloadMode: (v) => set({ preloadMode: v }), - setPreloadCustomSeconds: (v) => set({ preloadCustomSeconds: v }), setExcludeAudiobooks: (v) => set({ excludeAudiobooks: v }), setCustomGenreBlacklist: (v) => set({ customGenreBlacklist: v }), }; diff --git a/src/store/authStore.persistence.test.ts b/src/store/authStore.persistence.test.ts index 040fdc0c..9d26d1f5 100644 --- a/src/store/authStore.persistence.test.ts +++ b/src/store/authStore.persistence.test.ts @@ -4,8 +4,7 @@ * Covers Zustand persist's hydration path (via `useAuthStore.persist.rehydrate()`): * existing localStorage shapes load, missing fields default to the store's * initial values, corrupt JSON does not crash bootstrap, the - * hotCacheEnabled / preloadMode≠'off' conflicting-legacy migration clears - * both, and a few smaller field-level migrations. + * legacy field stripping, and a few smaller field-level migrations. * * Also pins the **synchronous storage** invariant called out in `CLAUDE.md` * ("never switch to async storage") — regression §2 of the pre-refactor @@ -53,7 +52,6 @@ describe('hydration — loads existing localStorage shape', () => { expect(s.gaplessEnabled).toBe(false); expect(s.replayGainEnabled).toBe(false); expect(s.normalizationEngine).toBe('off'); - expect(s.preloadMode).toBe('balanced'); }); it('preserves saved fields verbatim when present', async () => { @@ -94,34 +92,21 @@ describe('hydration — corrupt / unexpected input', () => { }); describe('onRehydrate migrations', () => { - it('clears the conflicting hotCacheEnabled + preloadMode≠"off" legacy combo', async () => { + it('keeps hotCacheEnabled when legacy preloadMode fields are present', async () => { writePersistedState({ servers: [], activeServerId: null, hotCacheEnabled: true, preloadMode: 'balanced', - }); - - await useAuthStore.persist.rehydrate(); - - const s = useAuthStore.getState(); - expect(s.hotCacheEnabled).toBe(false); - expect(s.preloadMode).toBe('off'); - }); - - it('keeps hotCacheEnabled when preloadMode was already "off"', async () => { - writePersistedState({ - servers: [], - activeServerId: null, - hotCacheEnabled: true, - preloadMode: 'off', + preloadCustomSeconds: 45, }); await useAuthStore.persist.rehydrate(); const s = useAuthStore.getState(); expect(s.hotCacheEnabled).toBe(true); - expect(s.preloadMode).toBe('off'); + expect((s as { preloadMode?: unknown }).preloadMode).toBeUndefined(); + expect((s as { preloadCustomSeconds?: unknown }).preloadCustomSeconds).toBeUndefined(); }); it('migrates a legacy `waveform` seekbarStyle to `truewave`', async () => { diff --git a/src/store/authStore.settings.test.ts b/src/store/authStore.settings.test.ts index 507289c6..6a117895 100644 --- a/src/store/authStore.settings.test.ts +++ b/src/store/authStore.settings.test.ts @@ -82,7 +82,6 @@ describe('trivial pass-through setters', () => { ['setMaxCacheMb', 'maxCacheMb', 2048], ['setHotCacheMaxMb', 'hotCacheMaxMb', 1024], ['setHotCacheDebounceSec', 'hotCacheDebounceSec', 60], - ['setPreloadCustomSeconds', 'preloadCustomSeconds', 45], ])('%s stores a numeric value', (setter, key, value) => { (useAuthStore.getState() as unknown as Record void>)[setter](value); expect((useAuthStore.getState() as unknown as Record)[key]).toBe(value); @@ -207,14 +206,7 @@ describe('replay-gain related setters (write through to player store)', () => { }); }); -describe('preload mode + discord cover source setters', () => { - it('setPreloadMode accepts off / balanced / early / custom', () => { - for (const mode of ['off', 'balanced', 'early', 'custom'] as const) { - useAuthStore.getState().setPreloadMode(mode); - expect(useAuthStore.getState().preloadMode).toBe(mode); - } - }); - +describe('discord cover source setters', () => { it('setDiscordCoverSource accepts none / apple / server', () => { for (const src of ['none', 'apple', 'server'] as const) { useAuthStore.getState().setDiscordCoverSource(src); diff --git a/src/store/authStore.ts b/src/store/authStore.ts index 43b58060..10d38414 100644 --- a/src/store/authStore.ts +++ b/src/store/authStore.ts @@ -59,8 +59,6 @@ export const useAuthStore = create()( trackPreviewLocations: { ...DEFAULT_TRACK_PREVIEW_LOCATIONS }, trackPreviewStartRatio: 0.33, trackPreviewDurationSec: 30, - preloadMode: 'balanced', - preloadCustomSeconds: 30, infiniteQueueEnabled: false, preservePlayNextOrder: false, showArtistImages: false, diff --git a/src/store/authStoreRehydrate.ts b/src/store/authStoreRehydrate.ts index b802e621..37dcd3aa 100644 --- a/src/store/authStoreRehydrate.ts +++ b/src/store/authStoreRehydrate.ts @@ -34,11 +34,9 @@ import type { * and writes the one-shot Linux smooth-scroll migration sentinel. */ export function computeAuthStoreRehydration(state: AuthState): Partial { - // If both hot cache and preload were enabled before mutual exclusion was enforced, reset both. - const conflictingLegacyState = - state.hotCacheEnabled && state.preloadMode !== 'off' - ? { hotCacheEnabled: false, preloadMode: 'off' as const } - : {}; + // Drop removed preload-next-track settings from legacy persist blobs. + delete (state as { preloadMode?: unknown }).preloadMode; + delete (state as { preloadCustomSeconds?: unknown }).preloadCustomSeconds; // Migrate lyricsServerFirst + enableNeteaselyrics → lyricsSources (one-time). // Only for an *existing* persisted state (upgrade from a build without @@ -166,7 +164,6 @@ export function computeAuthStoreRehydration(state: AuthState): Partial void; setTrackPreviewStartRatio: (v: number) => void; setTrackPreviewDurationSec: (v: number) => void; - setPreloadMode: (v: 'off' | 'balanced' | 'early' | 'custom') => void; - setPreloadCustomSeconds: (v: number) => void; setInfiniteQueueEnabled: (v: boolean) => void; setPreservePlayNextOrder: (v: boolean) => void; setShowArtistImages: (v: boolean) => void; diff --git a/src/store/loudnessBackfillWindow.ts b/src/store/loudnessBackfillWindow.ts index a72f77b2..e23a001f 100644 --- a/src/store/loudnessBackfillWindow.ts +++ b/src/store/loudnessBackfillWindow.ts @@ -46,12 +46,11 @@ export function collectLoudnessBackfillWindowTrackIds( return Array.from(ids); } -/** Next ~5 queue neighbours (+ immediate next when preload is on) for middle-tier analysis. */ +/** Next ~5 queue neighbours for middle-tier analysis priority hints. */ export function collectPlaybackMiddlePriorityTrackIds( queue: QueueItemRef[], queueIndex: number, currentTrack: Track | null, - preloadMode: 'off' | 'balanced' | 'early' | 'custom', ): string[] { const ids = new Set(); const start = Math.max(0, queueIndex + 1); @@ -60,13 +59,6 @@ export function collectPlaybackMiddlePriorityTrackIds( const tid = queue[i]?.trackId; if (tid && tid !== currentTrack?.id) ids.add(tid); } - if (preloadMode !== 'off') { - const nextIdx = queueIndex + 1; - if (nextIdx >= 0 && nextIdx < queue.length) { - const tid = queue[nextIdx]?.trackId; - if (tid && tid !== currentTrack?.id) ids.add(tid); - } - } return Array.from(ids); }