diff --git a/packages/player-core/src/player.rs b/packages/player-core/src/player.rs index c0f36e6c..fbd6b0a7 100644 --- a/packages/player-core/src/player.rs +++ b/packages/player-core/src/player.rs @@ -246,6 +246,7 @@ impl AudioPlayer { pub async fn run(mut self) { let mut check_end_interval = tokio::time::interval(Duration::from_millis(50)); + let mut media_controls_events_open = true; loop { tokio::select! { @@ -258,11 +259,15 @@ impl AudioPlayer { } } else { break; } }, - msg = self.npc_event_rx.recv() => { + msg = self.npc_event_rx.recv(), if media_controls_events_open => { if let Some(event) = msg { self.media_manager .handle_event(event, &self.handler(), &self.evt_sender) .await; + } else { + // A failed media-controls initialization closes the channel. + // Stop polling it or this select loop remains continuously ready. + media_controls_events_open = false; } }, _ = check_end_interval.tick() => { diff --git a/packages/player/src-tauri/src/lib.rs b/packages/player/src-tauri/src/lib.rs index 847ac993..da1b47dc 100644 --- a/packages/player/src-tauri/src/lib.rs +++ b/packages/player/src-tauri/src/lib.rs @@ -17,6 +17,8 @@ use crate::server::AMLLWebSocketServerWrapper; mod db; mod db_events; +#[cfg(target_os = "linux")] +mod linux_graphics; mod logging; mod music_info; mod player; @@ -189,6 +191,9 @@ fn handle_window_event(_window: &tauri::Window, _event: &tauri::WindowEvent) { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + #[cfg(target_os = "linux")] + linux_graphics::configure_nvidia_wayland(); + // Install ring as the default crypto provider for rustls, because multiple providers // (aws-lc-rs and ring) might be enabled in our dependency tree and rustls demands one to be explicitly chosen. #[cfg(target_os = "android")] diff --git a/packages/player/src-tauri/src/linux_graphics.rs b/packages/player/src-tauri/src/linux_graphics.rs new file mode 100644 index 00000000..3f674f61 --- /dev/null +++ b/packages/player/src-tauri/src/linux_graphics.rs @@ -0,0 +1,16 @@ +use std::{env, path::Path}; + +/// NVIDIA's explicit-sync path can fail when WebKitGTK creates a native +/// Wayland surface. Disable it before GTK/WebKit initialization on affected +/// Linux sessions; an existing user value always wins. +pub(crate) fn configure_nvidia_wayland() { + if env::var_os("WAYLAND_DISPLAY").is_none() + || env::var_os("__NV_DISABLE_EXPLICIT_SYNC").is_some() + || !Path::new("/dev/nvidiactl").exists() + { + return; + } + + // SAFETY: called before Tauri initializes GTK or starts worker threads. + unsafe { env::set_var("__NV_DISABLE_EXPLICIT_SYNC", "1") }; +} diff --git a/packages/player/src/components/LocalMusicContext/index.tsx b/packages/player/src/components/LocalMusicContext/index.tsx index 5b48795c..1db1cf24 100644 --- a/packages/player/src/components/LocalMusicContext/index.tsx +++ b/packages/player/src/components/LocalMusicContext/index.tsx @@ -60,7 +60,7 @@ import { useDbQuery } from "../../utils/use-db-query.ts"; export const FFTToLowPassContext: FC = () => { const store = useStore(); const fftDataRange = useAtomValue(fftDataRangeAtom); - // const isLyricPageOpened = useAtomValue(isLyricPageOpenedAtom); + const isLyricPageOpened = useAtomValue(isLyricPageOpenedAtom); useEffect(() => { emitAudioThread("setFFTRange", { @@ -70,7 +70,9 @@ export const FFTToLowPassContext: FC = () => { }, [fftDataRange]); useEffect(() => { - // if (!isLyricPageOpened) return; + // The low-frequency value only drives the full-screen lyric background. + // Keep this loop stopped while that view is hidden. + if (!isLyricPageOpened) return; let rafId: number; let curValue = 1; let lt = 0; @@ -134,8 +136,7 @@ export const FFTToLowPassContext: FC = () => { return () => { cancelAnimationFrame(rafId); }; - }, [store]); - // }, [store, isLyricPageOpened]); + }, [store, isLyricPageOpened]); return null; }; @@ -362,7 +363,12 @@ export const LocalMusicContext: FC = () => { const duration = store.get(musicDurationAtom) / 1000; const clampedPos = Math.min(newPos, duration || 0); - store.set(musicPlayingPositionAtom, (clampedPos * 1000) | 0); + lastSyncRef.current.position = clampedPos; + lastSyncRef.current.timestamp = now; + const nextPosition = (clampedPos * 1000) | 0; + if (nextPosition !== store.get(musicPlayingPositionAtom)) { + store.set(musicPlayingPositionAtom, nextPosition); + } } else { const currentUIPosition = store.get(musicPlayingPositionAtom) / 1000; lastSyncRef.current.position = currentUIPosition;