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
+36 -29
View File
@@ -14,8 +14,8 @@ use std::sync::{Arc, Mutex};
use log::{debug, error, info};
use rusqlite::{Connection, Result as SqliteResult};
use schema::MIGRATIONS;
pub use db_service::{DatabaseService, RusqliteService};
use schema::MIGRATIONS;
/// Database connection wrapper with thread-safe access
pub struct Database {
@@ -113,10 +113,7 @@ impl Database {
match conn.execute_batch(sql) {
Ok(_) => {
info!("Successfully applied migration: {}", name);
match conn.execute(
"INSERT INTO _migrations (name) VALUES (?1)",
[name],
) {
match conn.execute("INSERT INTO _migrations (name) VALUES (?1)", [name]) {
Ok(_) => debug!("Recorded migration: {}", name),
Err(e) => {
error!("Failed to record migration {}: {}", name, e);
@@ -264,9 +261,11 @@ mod tests {
.unwrap();
let name: String = conn
.query_row("SELECT name FROM servers WHERE id = ?1", ["server1"], |row: &rusqlite::Row| {
row.get(0)
})
.query_row(
"SELECT name FROM servers WHERE id = ?1",
["server1"],
|row: &rusqlite::Row| row.get(0),
)
.unwrap();
assert_eq!(name, "Updated Server");
@@ -275,7 +274,9 @@ mod tests {
.unwrap();
let count: i32 = conn
.query_row("SELECT COUNT(*) FROM servers", [], |row: &rusqlite::Row| row.get(0))
.query_row("SELECT COUNT(*) FROM servers", [], |row: &rusqlite::Row| {
row.get(0)
})
.unwrap();
assert_eq!(count, 0);
}
@@ -313,16 +314,15 @@ mod tests {
assert_eq!(is_active, 1);
// Update is_active
conn.execute(
"UPDATE users SET is_active = 0 WHERE id = ?1",
["user1"],
)
.unwrap();
conn.execute("UPDATE users SET is_active = 0 WHERE id = ?1", ["user1"])
.unwrap();
let is_active: i32 = conn
.query_row("SELECT is_active FROM users WHERE id = ?1", ["user1"], |row: &rusqlite::Row| {
row.get(0)
})
.query_row(
"SELECT is_active FROM users WHERE id = ?1",
["user1"],
|row: &rusqlite::Row| row.get(0),
)
.unwrap();
assert_eq!(is_active, 0);
}
@@ -348,9 +348,11 @@ mod tests {
// Verify user exists
let count: i32 = conn
.query_row("SELECT COUNT(*) FROM users WHERE server_id = ?1", ["server1"], |row: &rusqlite::Row| {
row.get(0)
})
.query_row(
"SELECT COUNT(*) FROM users WHERE server_id = ?1",
["server1"],
|row: &rusqlite::Row| row.get(0),
)
.unwrap();
assert_eq!(count, 1);
@@ -360,7 +362,9 @@ mod tests {
// User should be deleted via CASCADE
let count: i32 = conn
.query_row("SELECT COUNT(*) FROM users", [], |row: &rusqlite::Row| row.get(0))
.query_row("SELECT COUNT(*) FROM users", [], |row: &rusqlite::Row| {
row.get(0)
})
.unwrap();
assert_eq!(count, 0);
}
@@ -677,15 +681,16 @@ mod tests {
// Initially both users are active (simulating the old bug)
let active_count: i32 = conn
.query_row("SELECT COUNT(*) FROM users WHERE is_active = 1", [], |row: &rusqlite::Row| {
row.get(0)
})
.query_row(
"SELECT COUNT(*) FROM users WHERE is_active = 1",
[],
|row: &rusqlite::Row| row.get(0),
)
.unwrap();
assert_eq!(active_count, 2);
// Now simulate setting user1 as active (global deactivation)
conn.execute("UPDATE users SET is_active = 0", [])
.unwrap();
conn.execute("UPDATE users SET is_active = 0", []).unwrap();
conn.execute(
"UPDATE users SET is_active = 1, last_login_at = CURRENT_TIMESTAMP WHERE id = ?1",
["user1"],
@@ -694,9 +699,11 @@ mod tests {
// Only one user should be active now
let active_count: i32 = conn
.query_row("SELECT COUNT(*) FROM users WHERE is_active = 1", [], |row: &rusqlite::Row| {
row.get(0)
})
.query_row(
"SELECT COUNT(*) FROM users WHERE is_active = 1",
[],
|row: &rusqlite::Row| row.get(0),
)
.unwrap();
assert_eq!(active_count, 1);