mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
fix: font switching, layout scaling, embedded lyrics, folder opening
- fix(fonts): update CSS var declarations to @fontsource-variable naming
('Inter Variable', 'Outfit Variable', etc.) so dynamic font switching works
- fix(layout): 100vh → 100% on .app-shell to fix Windows WebView2 playerbar drift;
1fr → minmax(0,1fr) in all grid-template-rows + remove min-height: 720px to fix
Linux playerbar disappearing at high zoom or small window sizes
- fix(updater): replace shell open() with Rust open_folder command to bypass
shell:allow-open capability scope blocking local paths on Windows
- fix(lyrics): add get_embedded_lyrics Tauri command (id3 crate for MP3 SYLT/USLT,
lofty for FLAC SYNCEDLYRICS/LYRICS); fix parseLrc regex for LRC without fractional
seconds; fix SubsonicStructuredLyrics to accept both synced and issynced fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+42
-2
@@ -1,10 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { fetchLyrics, parseLrc, LrcLine } from '../api/lrclib';
|
||||
import { getLyricsBySongId, SubsonicStructuredLyrics } from '../api/subsonic';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useOfflineStore } from '../store/offlineStore';
|
||||
import { useHotCacheStore } from '../store/hotCacheStore';
|
||||
import type { Track } from '../store/playerStore';
|
||||
|
||||
export type LyricsSource = 'server' | 'lrclib';
|
||||
export type LyricsSource = 'server' | 'lrclib' | 'embedded';
|
||||
|
||||
export interface CachedLyrics {
|
||||
syncedLines: LrcLine[] | null;
|
||||
@@ -20,7 +23,9 @@ export const lyricsCache = new Map<string, CachedLyrics>();
|
||||
export function parseStructuredLyrics(
|
||||
lyrics: SubsonicStructuredLyrics,
|
||||
): Pick<CachedLyrics, 'syncedLines' | 'plainLyrics'> {
|
||||
if (lyrics.issynced && lyrics.line.length > 0) {
|
||||
// Accept both `synced` (OpenSubsonic spec) and `issynced` (legacy servers).
|
||||
const isSynced = !!(lyrics.synced ?? lyrics.issynced);
|
||||
if (isSynced && lyrics.line.length > 0) {
|
||||
const lines: LrcLine[] = lyrics.line
|
||||
.filter(l => l.start !== undefined)
|
||||
.map(l => ({ time: l.start! / 1000, text: l.value.trim() }))
|
||||
@@ -79,6 +84,37 @@ export function useLyrics(currentTrack: Track | null): UseLyricsResult {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// For offline / hot-cached tracks we have the file locally — read SYLT /
|
||||
// SYNCEDLYRICS directly via Rust instead of relying on Navidrome's parsing.
|
||||
// Fast path: both store lookups are synchronous; returns false immediately
|
||||
// for streaming tracks so it has zero impact on the normal fetch sequence.
|
||||
const fetchEmbedded = async (): Promise<boolean> => {
|
||||
const serverId = useAuthStore.getState().activeServerId ?? '';
|
||||
const localUrl =
|
||||
useOfflineStore.getState().getLocalUrl(currentTrack.id, serverId) ??
|
||||
useHotCacheStore.getState().getLocalUrl(currentTrack.id, serverId);
|
||||
if (!localUrl) return false;
|
||||
|
||||
const prefix = 'psysonic-local://';
|
||||
const filePath = localUrl.startsWith(prefix) ? localUrl.slice(prefix.length) : null;
|
||||
if (!filePath) return false;
|
||||
|
||||
try {
|
||||
const lrcString = await invoke<string | null>('get_embedded_lyrics', { path: filePath });
|
||||
if (!lrcString) return false;
|
||||
|
||||
const lines = parseLrc(lrcString);
|
||||
const synced = lines.length > 0 ? lines : null;
|
||||
const plain = synced ? null : (lrcString.trim() || null);
|
||||
if (!synced && !plain) return false;
|
||||
|
||||
store({ syncedLines: synced, plainLyrics: plain, source: 'embedded', notFound: false });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchServer = async (): Promise<boolean> => {
|
||||
const structured = await getLyricsBySongId(currentTrack.id);
|
||||
if (!structured) return false;
|
||||
@@ -107,6 +143,10 @@ export function useLyrics(currentTrack: Track | null): UseLyricsResult {
|
||||
};
|
||||
|
||||
(async () => {
|
||||
// Embedded lyrics from local file always win (most accurate SYLT data).
|
||||
if (cancelled) return;
|
||||
if (await fetchEmbedded()) return;
|
||||
|
||||
const [first, second] = lyricsServerFirst
|
||||
? [fetchServer, fetchLrclibFn]
|
||||
: [fetchLrclibFn, fetchServer];
|
||||
|
||||
Reference in New Issue
Block a user