From 02b2df1589c2d0e72725d1d77c6a7162c6c5ea2d Mon Sep 17 00:00:00 2001
From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com>
Date: Fri, 22 May 2026 21:06:04 +0200
Subject: [PATCH] feat(lyrics): make lyrics fully disablable (independent
YouLyPlus toggle) (#855)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(lyrics): independent YouLyPlus toggle + all-sources-off state
Replace the binary lyricsMode ('standard' | 'lyricsplus') with an
independent youLyPlusEnabled flag so YouLyPlus and the standard sources
are no longer mutually exclusive — turning one off no longer forces the
other on. YouLyPlus (when on) is tried first with the enabled sources as
fallback; off uses only the enabled sources. When YouLyPlus is off and no
source is enabled, useLyrics fetches nothing (issue #810).
Fresh installs ship with every source off; the rehydrate migration only
restores the old on-by-default set for genuine upgrades, not new installs.
* feat(lyrics): YouLyPlus toggle UI + queue 'no sources' hint
Settings: single YouLyPlus toggle replacing the two mutually exclusive
mode switches; the source list is always visible with a context hint
(fallback vs primary). Queue lyric tab shows a hint when no source is
active. en + de strings; other locales fall back to en.
* docs(changelog): lyrics fully disablable (#855)
---
CHANGELOG.md | 8 +++
src/components/LyricsPane.tsx | 14 ++++-
.../settings/LyricsSourcesCustomizer.tsx | 56 +++++++------------
src/hooks/useLyrics.ts | 31 +++++++---
src/locales/de/player.ts | 1 +
src/locales/de/settings.ts | 8 +--
src/locales/en/player.ts | 1 +
src/locales/en/settings.ts | 8 +--
src/store/authLyricsSettingsActions.ts | 4 +-
src/store/authStore.settings.test.ts | 8 ++-
src/store/authStore.ts | 2 +-
src/store/authStoreDefaults.ts | 7 ++-
src/store/authStoreRehydrate.test.ts | 36 ++++++++++++
src/store/authStoreRehydrate.ts | 16 +++++-
src/store/authStoreTypes.ts | 11 ++--
15 files changed, 145 insertions(+), 66 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 646da186..79cf5b3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -117,6 +117,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+### Lyrics — sources can be turned off entirely
+
+**By [@Psychotoxical](https://github.com/Psychotoxical), suggested by sddania, PR [#855](https://github.com/Psychotoxical/psysonic/pull/855)**
+
+* YouLyPlus is now an independent toggle instead of an either/or with the standard sources, so lyrics can be switched off completely — turn off YouLyPlus and every source under **Settings → Lyrics**. With nothing selected, no lyrics are fetched or shown and the queue lyric tab says so. Fresh installs start with all sources off.
+
+
+
## Fixed
### In-page browse — virtual scroll and cover-art priority
diff --git a/src/components/LyricsPane.tsx b/src/components/LyricsPane.tsx
index e93136fa..0ccc1185 100644
--- a/src/components/LyricsPane.tsx
+++ b/src/components/LyricsPane.tsx
@@ -23,10 +23,14 @@ export default function LyricsPane({ currentTrack }: Props) {
const { t } = useTranslation();
const { syncedLines, wordLines, plainLyrics, source, loading, notFound } = useLyrics(currentTrack);
- const { staticOnly, sidebarLyricsStyle } = useAuthStore(useShallow(s => ({
+ const { staticOnly, sidebarLyricsStyle, youLyPlusEnabled, lyricsSources } = useAuthStore(useShallow(s => ({
staticOnly: s.lyricsStaticOnly,
sidebarLyricsStyle: s.sidebarLyricsStyle,
+ youLyPlusEnabled: s.youLyPlusEnabled,
+ lyricsSources: s.lyricsSources,
})));
+ // Lyrics fully off: YouLyPlus off and no source enabled (issue #810).
+ const lyricsDisabled = !youLyPlusEnabled && !lyricsSources.some(s => s.enabled);
const useWords = !staticOnly && wordLines !== null && wordLines.length > 0;
const hasSynced = !staticOnly && !useWords && syncedLines !== null && syncedLines.length > 0;
@@ -146,6 +150,14 @@ export default function LyricsPane({ currentTrack }: Props) {
);
}
+ if (lyricsDisabled) {
+ return (
+
+
{t('player.lyricsNoSources')}
+
+ );
+ }
+
const sourceLabel = source === 'server'
? t('player.lyricsSourceServer')
: source === 'lrclib'
diff --git a/src/components/settings/LyricsSourcesCustomizer.tsx b/src/components/settings/LyricsSourcesCustomizer.tsx
index 1d69d432..a4dc21a5 100644
--- a/src/components/settings/LyricsSourcesCustomizer.tsx
+++ b/src/components/settings/LyricsSourcesCustomizer.tsx
@@ -36,13 +36,13 @@ export function LyricsSourcesCustomizer() {
const { t } = useTranslation();
const lyricsSources = useAuthStore(useShallow(s => s.lyricsSources));
const setLyricsSources = useAuthStore(s => s.setLyricsSources);
- const lyricsMode = useAuthStore(s => s.lyricsMode);
- const setLyricsMode = useAuthStore(s => s.setLyricsMode);
+ const youLyPlusEnabled = useAuthStore(s => s.youLyPlusEnabled);
+ const setYouLyPlusEnabled = useAuthStore(s => s.setYouLyPlusEnabled);
const lyricsStaticOnly = useAuthStore(s => s.lyricsStaticOnly);
const setLyricsStaticOnly = useAuthStore(s => s.setLyricsStaticOnly);
const { isDragging: isPsyDragging } = useDragDrop();
- // useState (not useRef) so the listener-effect re-runs when the container
- // gets unmounted/remounted by the {lyricsMode === 'standard'} wrapper.
+ // useState (not useRef) so the listener-effect re-binds if the container
+ // element is ever remounted.
const [containerEl, setContainerEl] = useState(null);
const [dropTarget, setDropTarget] = useState(null);
const dropTargetRef = useRef(null);
@@ -107,48 +107,35 @@ export function LyricsSourcesCustomizer() {
{t('settings.lyricsSourcesDesc')}
- {/* Mode switch — standard three-provider pipeline vs. YouLyPlus karaoke.
- YouLyPlus misses silently fall back to the standard pipeline. */}
+ {/* YouLyPlus (karaoke) — independent toggle. When on it is tried first and
+ the enabled sources below act as fallback; when off only those sources
+ are used. YouLyPlus off + every source off = lyrics fully disabled. */}