pub struct PlaybackModeManager {
jellyfin_client: Arc<Mutex<Option<JellyfinClient>>>,
player_controller: Arc<Mutex<PlayerController>>,
current_mode: Arc<RwLock<PlaybackMode>>,
is_transferring: Arc<AtomicBool>,
event_emitter: Arc<Mutex<Option<Arc<dyn PlayerEventEmitter>>>>,
remote_volume: Arc<dyn RemoteVolumeControl>,
}Expand description
Manages playback mode transfers between local and remote sessions
Fields§
§jellyfin_client: Arc<Mutex<Option<JellyfinClient>>>§player_controller: Arc<Mutex<PlayerController>>§current_mode: Arc<RwLock<PlaybackMode>>§is_transferring: Arc<AtomicBool>§event_emitter: Arc<Mutex<Option<Arc<dyn PlayerEventEmitter>>>>Optional emitter used to notify the frontend when the mode changes, so its
mirror store stays in sync with this authoritative one. None in tests.
remote_volume: Arc<dyn RemoteVolumeControl>Platform hook for OS-level remote volume routing (swapped in tests).
Implementations§
Source§impl PlaybackModeManager
impl PlaybackModeManager
Sourcepub fn new(
jellyfin_client: Arc<Mutex<Option<JellyfinClient>>>,
player_controller: Arc<TokioMutex<PlayerController>>,
) -> Self
pub fn new( jellyfin_client: Arc<Mutex<Option<JellyfinClient>>>, player_controller: Arc<TokioMutex<PlayerController>>, ) -> Self
Create a new playback mode manager
Sourcepub fn set_event_emitter(&self, emitter: Arc<dyn PlayerEventEmitter>)
pub fn set_event_emitter(&self, emitter: Arc<dyn PlayerEventEmitter>)
Wire the event emitter so set_mode notifies the frontend. Called once
during setup; safe to leave unset (tests do), in which case mode changes
simply aren’t broadcast.
Sourcepub fn get_mode(&self) -> PlaybackMode
pub fn get_mode(&self) -> PlaybackMode
Get current playback mode
Sourcepub fn set_mode(&self, mode: PlaybackMode)
pub fn set_mode(&self, mode: PlaybackMode)
Set playback mode (internal use).
Broadcasts a PlaybackModeChanged event when the mode actually changes so
the frontend’s mirror store reconciles to this authoritative value. The
write lock is released before emitting to avoid holding it across the
emitter call.
Also owns OS volume routing, which is derived from the transition
rather than from each call site: entering remote mode attaches the remote
volume control, and any exit from remote mode hands it back to the local
speaker. Doing this per-call-site is what caused the bug where stopping a
remote session (player_stop → Idle) left Android stuck on the remote
volume slider — only the transfer-to-local path tore it down.
TRACES: UR-010 | DR-059, IR-021
Sourcefn enable_remote_control(&self)
fn enable_remote_control(&self)
Start the Android playback service and hand it remote-volume control.
Must run on EVERY transition into remote mode, because it is what starts
the foreground service. Without a running service there is no media
notification (the lockscreen card is missing) AND system volume buttons
aren’t intercepted for the remote session (remote volume control dead).
Both symptoms share this one cause, so this must not be skipped on any
remote-entry path (notably the empty-queue early return in
transfer_to_remote_inner). No-op / non-Android builds do nothing.
set_mode already arms this on entry into remote mode;
calling it again is harmless (the service start is idempotent) and keeps
the guarantee when the mode was already remote, which set_mode skips as
a no-op transition.
Sourcepub fn is_transferring(&self) -> bool
pub fn is_transferring(&self) -> bool
Check if currently transferring
Sourcepub fn set_transferring(&self, transferring: bool)
pub fn set_transferring(&self, transferring: bool)
Set the transferring flag directly.
The remote->local transfer is driven from the frontend in two steps
(player_play_tracks to start local playback, then
playback_mode_transfer_to_local to stop the remote). The first step’s
routing depends on this flag: while it’s set, player_play_tracks plays
locally instead of casting back to the remote session. The frontend must
raise the flag before that first call and lower it when the sequence is
done (or aborts), so it can’t be left stuck on.
Sourcepub async fn send_remote_volume_command(
&self,
command: &str,
volume: i32,
) -> Result<(), String>
pub async fn send_remote_volume_command( &self, command: &str, volume: i32, ) -> Result<(), String>
Send volume command to remote session Commands: “SetVolume”, “VolumeUp”, “VolumeDown”
Sourcefn extract_jellyfin_ids(
&self,
items: &[MediaItem],
original_index: usize,
) -> Result<(Vec<String>, usize), String>
fn extract_jellyfin_ids( &self, items: &[MediaItem], original_index: usize, ) -> Result<(Vec<String>, usize), String>
Extract Jellyfin item IDs from queue items Returns (item_ids, adjusted_current_index)
Sourcepub async fn transfer_to_remote(
&self,
session_id: String,
position_override: Option<f64>,
) -> Result<(), String>
pub async fn transfer_to_remote( &self, session_id: String, position_override: Option<f64>, ) -> Result<(), String>
Transfer playback from local device to remote Jellyfin session
async fn transfer_to_remote_inner( &self, session_id: &str, position_override: Option<f64>, ) -> Result<(), String>
Sourcepub async fn transfer_to_local(
&self,
current_item_id: String,
position_ticks: i64,
) -> Result<(), String>
pub async fn transfer_to_local( &self, current_item_id: String, position_ticks: i64, ) -> Result<(), String>
Transfer playback from remote session back to local device