fix(android): keep lockscreen/media controls in sync with playback

The lockscreen controls drifted out of sync, especially while casting, and
couldn't control remote playback. Two media sessions were competing (a Media3
MediaSession driving transport vs a MediaSessionCompat driving the notification),
position was only pushed on play/pause so the scrubber froze mid-track, and
remote mode showed stale local metadata with dead buttons.

- Make MediaSessionCompat the single source of truth; route all transport
  commands (both the Compat callback and the Media3 wrappedPlayer) through Rust
  via nativeOnMediaCommand instead of touching ExoPlayer directly.
- Push position on every 250ms tick via a lightweight updatePlaybackPosition,
  and report 0.0 playback speed when paused so Android stops extrapolating.
- Mirror the remote session's now-playing onto the lockscreen from the native
  session poller (works while the screen is locked, unlike WebView timers) via
  a new player::update_lockscreen_metadata JNI bridge.
- Make MediaSessionHandler mode-aware: in remote mode forward play/pause/next/
  prev/seek to the remote Jellyfin session; Stop while casting emits
  RemoteDisconnectRequested, which the frontend handles by transferring to local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 23:55:26 +02:00
co-authored by Claude Opus 4.8
parent 345bd0730c
commit 385d2270c9
9 changed files with 432 additions and 86 deletions
+31
View File
@@ -46,6 +46,37 @@ pub use android::{
set_media_command_handler, set_remote_volume_handler, get_detected_codecs,
};
/// Metadata for the lockscreen / media notification.
///
/// Used to drive the Android MediaSession from Rust in remote (cast) mode, where
/// the local ExoPlayer is idle and so can't supply now-playing info. The session
/// poller fills this in from the remote Jellyfin session and pushes it to the
/// notification so the lockscreen stays in sync while casting.
#[derive(Debug, Clone)]
pub struct LockscreenMetadata {
pub title: String,
pub artist: String,
pub album: Option<String>,
/// Track duration in milliseconds.
pub duration_ms: i64,
/// Current playback position in milliseconds.
pub position_ms: i64,
pub is_playing: bool,
}
/// Push now-playing metadata to the Android lockscreen. No-op off Android, so the
/// session poller can call it unconditionally and stay platform-agnostic.
pub fn update_lockscreen_metadata(_meta: &LockscreenMetadata) -> Result<(), String> {
#[cfg(target_os = "android")]
{
return android::update_lockscreen_metadata(_meta);
}
#[cfg(not(target_os = "android"))]
{
Ok(())
}
}
use crate::utils::lock::MutexSafe;
use log::{debug, error, warn};
use std::sync::{Arc, Mutex};