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
+26 -16
View File
@@ -128,7 +128,9 @@ impl DatabaseService for RusqliteService {
async fn execute(&self, query: Query) -> DbResult<usize> {
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || {
let conn = conn.lock().map_err(|e| format!("Failed to lock connection: {}", e))?;
let conn = conn
.lock()
.map_err(|e| format!("Failed to lock connection: {}", e))?;
execute_query(&conn, query)
})
.await
@@ -139,7 +141,9 @@ impl DatabaseService for RusqliteService {
let conn = Arc::clone(&self.conn);
let sql = sql.to_string();
tokio::task::spawn_blocking(move || {
let conn = conn.lock().map_err(|e| format!("Failed to lock connection: {}", e))?;
let conn = conn
.lock()
.map_err(|e| format!("Failed to lock connection: {}", e))?;
conn.execute_batch(&sql)
.map_err(|e| format!("Execute batch failed: {}", e))
})
@@ -154,7 +158,9 @@ impl DatabaseService for RusqliteService {
{
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || {
let conn = conn.lock().map_err(|e| format!("Failed to lock connection: {}", e))?;
let conn = conn
.lock()
.map_err(|e| format!("Failed to lock connection: {}", e))?;
query_one(&conn, query, mapper)
})
.await
@@ -168,7 +174,9 @@ impl DatabaseService for RusqliteService {
{
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || {
let conn = conn.lock().map_err(|e| format!("Failed to lock connection: {}", e))?;
let conn = conn
.lock()
.map_err(|e| format!("Failed to lock connection: {}", e))?;
query_optional(&conn, query, mapper)
})
.await
@@ -182,7 +190,9 @@ impl DatabaseService for RusqliteService {
{
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || {
let conn = conn.lock().map_err(|e| format!("Failed to lock connection: {}", e))?;
let conn = conn
.lock()
.map_err(|e| format!("Failed to lock connection: {}", e))?;
query_many(&conn, query, mapper)
})
.await
@@ -196,7 +206,9 @@ impl DatabaseService for RusqliteService {
{
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || {
let conn = conn.lock().map_err(|e| format!("Failed to lock connection: {}", e))?;
let conn = conn
.lock()
.map_err(|e| format!("Failed to lock connection: {}", e))?;
conn.execute("BEGIN TRANSACTION", [])
.map_err(|e| format!("Failed to begin transaction: {}", e))?;
@@ -224,7 +236,9 @@ impl DatabaseService for RusqliteService {
async fn last_insert_rowid(&self) -> DbResult<i64> {
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || {
let conn = conn.lock().map_err(|e| format!("Failed to lock connection: {}", e))?;
let conn = conn
.lock()
.map_err(|e| format!("Failed to lock connection: {}", e))?;
Ok(conn.last_insert_rowid())
})
.await
@@ -255,7 +269,9 @@ where
{
match query_one(conn, query, mapper) {
Ok(value) => Ok(Some(value)),
Err(e) if e.contains("Query returned no rows") || e.contains("QueryReturnedNoRows") => Ok(None),
Err(e) if e.contains("Query returned no rows") || e.contains("QueryReturnedNoRows") => {
Ok(None)
}
Err(e) => Err(e),
}
}
@@ -325,10 +341,7 @@ mod tests {
let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
let query = Query::new("SELECT name FROM test WHERE id = 1");
let name: String = service
.query_one(query, |row| row.get(0))
.await
.unwrap();
let name: String = service.query_one(query, |row| row.get(0)).await.unwrap();
assert_eq!(name, "Bob");
}
@@ -346,10 +359,7 @@ mod tests {
let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
let query = Query::new("SELECT name FROM test ORDER BY id");
let names: Vec<String> = service
.query_many(query, |row| row.get(0))
.await
.unwrap();
let names: Vec<String> = service.query_many(query, |row| row.get(0)).await.unwrap();
assert_eq!(names, vec!["Alice", "Bob"]);
}