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§

§

### impl Freeze for PlaybackModeManager

§

### impl !RefUnwindSafe for PlaybackModeManager

§

### impl Send for PlaybackModeManager

§

### impl Sync for PlaybackModeManager

§

### impl Unpin for PlaybackModeManager

§

### impl UnsafeUnpin for PlaybackModeManager

§

### impl !UnwindSafe for PlaybackModeManager

## 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