feat(lyrics): YouLyPlus karaoke + fix(macos): suppress mic prompt

Two independent user-facing fixes rolled together.

── Lyrics: YouLyPlus provider (issue #172) ──
Adds word-by-word synced (karaoke) lyrics as an alternative lyrics mode.
Backed by the public lyricsplus aggregator (Apple Music / Spotify /
Musixmatch / QQ Music) — no API keys on our side, subscription costs are
borne by the backend operator. Five mirrors are tried on network failure.

Settings → Lyrics now exposes a mode radio (Standard vs YouLyPlus) plus
a static-only toggle. YouLyPlus misses silently fall back to the existing
server + LRCLIB + Netease pipeline so obscure tracks still resolve. The
drag-to-reorder source list is hidden in YouLyPlus mode since the
external aggregator manages its own source priority.

Rendering: per-word spans on each line, active word highlighted with
accent-tinted glow. Subscribed imperatively via usePlayerStore.subscribe
so 500 ms progress ticks do not re-render the whole lyrics block. The
Fullscreen Player reuses the same pattern; the 5-line rail layout is
unchanged. white-space: pre on .fs-lyric-word preserves the trailing
spaces the API embeds in each syllabus token (flex context would
otherwise collapse them).

i18n: new keys in all 8 locales (de, en, fr, nl, nb, ru, es, zh).

── macOS mic-prompt suppression ──
Ships a vendored cpal 0.15.3 at patches/cpal-0.15.3/ wired via
[patch.crates-io]. The only change is in src/host/coreaudio/macos/mod.rs:
audio_unit_from_device now unconditionally uses IOType::DefaultOutput for
output streams instead of conditionally choosing HalOutput.

AUHAL (HalOutput) is classified as input-capable by macOS TCC and
triggers the microphone-permission dialog at first launch / after each
update even for playback-only apps. Previous attempts (removing
NSMicrophoneUsageDescription, setting com.apple.security.device.audio-input
false in Entitlements.plist) did not suppress the prompt because TCC
fires at AudioUnit instantiation, not at plist level. The upstream fix
in cpal PR #1070 / cpal 0.17 only helps when the pinned device equals
the system default; always-DefaultOutput covers all cases.

Tradeoff: per-device output selection is a no-op on macOS — the stream
always follows the system default. Settings.tsx surfaces this via
audioOutputDeviceMacNotice (hides the CustomSelect on macOS) and skips
audio_list_devices + device-watcher effects there. Matches the behaviour
of Apple Music / Spotify on macOS.

Also removes the now-obsolete com.apple.security.device.audio-input
entitlement (sandbox is disabled anyway, so it was ignored).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-17 21:30:31 +02:00
parent e1b807d0ef
commit 34b4445c13
67 changed files with 13697 additions and 129 deletions
+49
View File
@@ -2694,6 +2694,33 @@
color: var(--text-secondary);
}
/* Word-sync (karaoke) mode — per-word highlight inside the active line.
The parent `.lyrics-line.active` colour is overridden: only `.active` / `.played`
words carry accent tint, the remaining words stay at the line's base colour. */
.lyrics-word-synced .lyrics-line {
color: var(--text-secondary);
}
.lyrics-word-synced .lyrics-line.active {
color: var(--text-secondary); /* line colour neutralised; words decide */
}
.lyrics-word-synced .lyrics-line.completed {
color: var(--text-muted);
}
.lyrics-word {
display: inline;
transition: color 0.18s ease;
color: inherit;
}
.lyrics-word.played {
color: var(--accent);
opacity: 0.65;
}
.lyrics-word.active {
color: var(--accent);
opacity: 1;
text-shadow: 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent);
}
.lyrics-plain {
padding: 4px 0 16px;
}
@@ -3555,6 +3582,28 @@
transform: scaleX(1.015); /* subtle compositor-only emphasis, no layout cost */
}
/* Fullscreen word-sync (karaoke). Parent .fsl-active base colour stays dim so
only the .active/.played words pop. */
.fs-lyric-line.fsl-active .fs-lyric-word {
color: rgba(255, 255, 255, 0.35);
text-shadow: none;
}
.fs-lyric-word {
display: inline;
/* Preserve the trailing space the lyricsplus API embeds in each syllabus
token (`"Gina "`, `"dreams "`, …). Without this the flex formatting
context collapses it and words run together. */
white-space: pre;
transition: color 0.18s ease, text-shadow 0.18s ease;
}
.fs-lyric-line.fsl-active .fs-lyric-word.played {
color: rgba(255, 255, 255, 0.75);
}
.fs-lyric-line.fsl-active .fs-lyric-word.active {
color: #ffffff;
text-shadow: 0 0 28px var(--accent-glow, var(--accent)), 0 1px 8px rgba(0, 0, 0, 0.6);
}
/* ── no-compositing overrides ─────────────────────────────────────────────────
Applied only when WEBKIT_DISABLE_COMPOSITING_MODE=1 is set (Arch Linux etc).