Skip to main content

PlaybackModeManager

Struct PlaybackModeManager 

Source
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

Source

pub fn new( jellyfin_client: Arc<Mutex<Option<JellyfinClient>>>, player_controller: Arc<TokioMutex<PlayerController>>, ) -> Self

Create a new playback mode manager

Source

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.

Source

pub fn get_mode(&self) -> PlaybackMode

Get current playback mode

Source

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

Source

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.

Source

pub fn is_transferring(&self) -> bool

Check if currently transferring

Source

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.

Source

pub async fn send_remote_volume_command( &self, command: &str, volume: i32, ) -> Result<(), String>

Send volume command to remote session Commands: “SetVolume”, “VolumeUp”, “VolumeDown”

Source

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)

Source

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

Source

async fn transfer_to_remote_inner( &self, session_id: &str, position_override: Option<f64>, ) -> Result<(), String>

Source

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

Source

async fn transfer_to_local_inner( &self, current_item_id: &str, position_ticks: i64, ) -> Result<(), String>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more