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:
2026-07-22 21:52:07 +02:00
parent 4e6ab017d4
commit 3fbf6afdbc
72 changed files with 6728 additions and 2338 deletions
+35 -10
View File
@@ -1,9 +1,9 @@
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use tauri::{AppHandle, Emitter};
use serde::{Serialize, Deserialize};
use tokio::sync::RwLock;
use crate::jellyfin::http_client::HttpClient;
@@ -170,9 +170,15 @@ impl ConnectivityReporter {
if let Some(app_handle) = &self.app_handle {
let event = ConnectivityChangeEvent { is_reachable };
if let Err(e) = app_handle.emit("connectivity:changed", event) {
log::error!("[ConnectivityMonitor] Failed to emit connectivity change event: {}", e);
log::error!(
"[ConnectivityMonitor] Failed to emit connectivity change event: {}",
e
);
} else {
log::info!("[ConnectivityMonitor] Emitted connectivity change: {}", is_reachable);
log::info!(
"[ConnectivityMonitor] Emitted connectivity change: {}",
is_reachable
);
}
}
}
@@ -181,7 +187,10 @@ impl ConnectivityReporter {
async fn emit_server_reconnected(&self) {
if let Some(app_handle) = &self.app_handle {
if let Err(e) = app_handle.emit("connectivity:reconnected", ()) {
log::error!("[ConnectivityMonitor] Failed to emit reconnection event: {}", e);
log::error!(
"[ConnectivityMonitor] Failed to emit reconnection event: {}",
e
);
} else {
log::info!("[ConnectivityMonitor] Emitted server reconnected event");
}
@@ -233,7 +242,14 @@ impl ConnectivityMonitor {
// Check new server immediately
log::info!("[ConnectivityMonitor] Checking reachability of new server...");
let is_reachable = self.check_reachability().await;
log::info!("[ConnectivityMonitor] New server is {}", if is_reachable { "REACHABLE" } else { "UNREACHABLE" });
log::info!(
"[ConnectivityMonitor] New server is {}",
if is_reachable {
"REACHABLE"
} else {
"UNREACHABLE"
}
);
}
/// Get current connectivity status
@@ -298,11 +314,16 @@ impl ConnectivityMonitor {
return;
}
log::info!("[ConnectivityMonitor] Starting connectivity monitoring (offline recovery probe)");
log::info!(
"[ConnectivityMonitor] Starting connectivity monitoring (offline recovery probe)"
);
// Perform an immediate check so startup reflects reality quickly.
let is_reachable = self.check_reachability().await;
log::info!("[ConnectivityMonitor] Initial connectivity check: {}", if is_reachable { "ONLINE" } else { "OFFLINE" });
log::info!(
"[ConnectivityMonitor] Initial connectivity check: {}",
if is_reachable { "ONLINE" } else { "OFFLINE" }
);
let is_monitoring = Arc::clone(&self.is_monitoring);
let server_url = Arc::clone(&self.server_url);
@@ -452,7 +473,9 @@ mod tests {
let reporter = test_reporter();
// Force offline.
reporter.apply_probe_result(false, Some("down".to_string())).await;
reporter
.apply_probe_result(false, Some("down".to_string()))
.await;
assert!(!is_reachable(&reporter).await);
// A single success brings us straight back online.
@@ -490,7 +513,9 @@ mod tests {
assert!(!is_reachable(&reporter).await);
// Should not panic or change state.
reporter.report_network_failure(Some("still down".to_string())).await;
reporter
.report_network_failure(Some("still down".to_string()))
.await;
assert!(!is_reachable(&reporter).await);
}
}