Background-audio handoff for video + repository/player refactor
Hand video playback off to a native audio-only stream when the app is backgrounded or locked, with no on-device video decode (UR-040). Adds player_enter/exit_background_audio commands, an audio-only stream URL for video items across the repository layer, and the frontend handoff state machine wired into VideoPlayer. Includes accompanying repository/offline/player refactoring and regenerates the traceability matrix.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use super::media::MediaItem;
|
||||
/**
|
||||
* Media Session Management
|
||||
*
|
||||
@@ -7,10 +8,8 @@
|
||||
*
|
||||
* See docs/architecture/01-rust-backend.md for the state machine diagram.
|
||||
*/
|
||||
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::media::MediaItem;
|
||||
|
||||
/// Media session type tracking the high-level playback context
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
@@ -98,7 +97,10 @@ impl MediaSessionManager {
|
||||
/// Start an audio session with a queue
|
||||
/// Transitions: Idle → Audio(active), Any → Audio(active)
|
||||
pub fn start_audio_session(&mut self, first_item: MediaItem) {
|
||||
info!("[MediaSession] Starting audio session: {}", first_item.title);
|
||||
info!(
|
||||
"[MediaSession] Starting audio session: {}",
|
||||
first_item.title
|
||||
);
|
||||
self.current = MediaSessionType::Audio {
|
||||
last_item: Some(first_item),
|
||||
is_active: true,
|
||||
@@ -107,7 +109,11 @@ impl MediaSessionManager {
|
||||
|
||||
/// Update audio session with new track (during playback)
|
||||
pub fn update_audio_track(&mut self, item: MediaItem) {
|
||||
if let MediaSessionType::Audio { last_item, is_active } = &mut self.current {
|
||||
if let MediaSessionType::Audio {
|
||||
last_item,
|
||||
is_active,
|
||||
} = &mut self.current
|
||||
{
|
||||
info!("[MediaSession] Updating audio track: {}", item.title);
|
||||
*last_item = Some(item);
|
||||
*is_active = true;
|
||||
@@ -171,8 +177,14 @@ impl MediaSessionManager {
|
||||
|
||||
/// Advance to next episode in TV session
|
||||
pub fn tv_session_next_episode(&mut self, next_item: MediaItem) {
|
||||
if let MediaSessionType::TvShow { item, is_active, .. } = &mut self.current {
|
||||
info!("[MediaSession] Advancing to next episode: {}", next_item.title);
|
||||
if let MediaSessionType::TvShow {
|
||||
item, is_active, ..
|
||||
} = &mut self.current
|
||||
{
|
||||
info!(
|
||||
"[MediaSession] Advancing to next episode: {}",
|
||||
next_item.title
|
||||
);
|
||||
*item = next_item;
|
||||
*is_active = true;
|
||||
}
|
||||
@@ -294,7 +306,10 @@ mod tests {
|
||||
|
||||
assert!(matches!(
|
||||
manager.current(),
|
||||
MediaSessionType::Audio { is_active: true, .. }
|
||||
MediaSessionType::Audio {
|
||||
is_active: true,
|
||||
..
|
||||
}
|
||||
));
|
||||
assert!(manager.should_show_miniplayer());
|
||||
|
||||
@@ -307,7 +322,10 @@ mod tests {
|
||||
manager.audio_session_inactive();
|
||||
assert!(matches!(
|
||||
manager.current(),
|
||||
MediaSessionType::Audio { is_active: false, .. }
|
||||
MediaSessionType::Audio {
|
||||
is_active: false,
|
||||
..
|
||||
}
|
||||
));
|
||||
assert!(manager.should_show_miniplayer()); // Still shows!
|
||||
|
||||
@@ -330,7 +348,10 @@ mod tests {
|
||||
|
||||
assert!(matches!(
|
||||
manager.current(),
|
||||
MediaSessionType::Movie { is_active: true, .. }
|
||||
MediaSessionType::Movie {
|
||||
is_active: true,
|
||||
..
|
||||
}
|
||||
));
|
||||
assert!(manager.should_show_video_player());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user