feat: v1.34.1 — fullscreen lyrics overlay, FsArt crossfade, lyrics toggle, contributor updates

- FullscreenPlayer: synced lyrics overlay (5-line rail, mask fade, click-to-seek)
- FullscreenPlayer: FsArt crossfade component with onLoad pattern, 300px art, next-track pre-fetch
- FullscreenPlayer: MicVocal lyrics toggle button next to heart (persisted in authStore)
- FullscreenPlayer: idle system — only close button auto-hides, cluster+seekbar always visible
- LyricsPane: refactored to use shared useLyrics hook (no double-fetch with FS overlay)
- authStore: showFullscreenLyrics field (default true)
- i18n: fsLyricsToggle key added to all 7 locales; ru2.ts removed (merged into ru.ts)
- Settings: Contributors updated (nisrael, cucadmuh, kilyabin); logout moved to Server tab as btn-danger
- theme.css: btn-danger style added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-06 23:00:33 +02:00
parent 390e6e788d
commit d73f348339
17 changed files with 687 additions and 942 deletions
+37
View File
@@ -606,3 +606,40 @@ export async function getTopRadioStations(offset = 0): Promise<RadioBrowserStati
export async function fetchUrlBytes(url: string): Promise<[number[], string]> {
return invoke<[number[], string]>('fetch_url_bytes', { url });
}
// ─── Structured Lyrics (OpenSubsonic / getLyricsBySongId) ────────────────────
export interface SubsonicLyricLine {
start?: number; // milliseconds — absent when unsynced
value: string;
}
export interface SubsonicStructuredLyrics {
issynced: boolean;
lang?: string;
offset?: number;
displayArtist?: string;
displayTitle?: string;
line: SubsonicLyricLine[];
}
/**
* Fetches structured lyrics from the server's embedded tags via the
* OpenSubsonic `getLyricsBySongId` endpoint. Returns null when the
* server doesn't support the endpoint or the track has no embedded lyrics.
* Prefers synced lyrics over plain when both are present.
*/
export async function getLyricsBySongId(id: string): Promise<SubsonicStructuredLyrics | null> {
try {
const data = await api<{ lyricsList: { structuredLyrics?: SubsonicStructuredLyrics[] } }>(
'getLyricsBySongId.view',
{ id },
);
const list = data.lyricsList?.structuredLyrics;
if (!list || list.length === 0) return null;
return list.find(l => l.issynced) ?? list[0];
} catch {
// Server doesn't support the endpoint or track has no embedded lyrics
return null;
}
}