diff --git a/docs/traceability.md b/docs/traceability.md index 99b31cd2..e8879b48 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -1,11 +1,11 @@ # Code Traceability Matrix -**Generated:** 8/1/2026, 10:40:16 AM +**Generated:** 8/2/2026, 6:10:09 PM ## Summary - **Total Files Scanned:** 305 -- **Total TRACES Found:** 337 +- **Total TRACES Found:** 338 - **Requirements Covered:** - User Requirements (UR): 58 - Integration Requirements (IR): 15 @@ -229,11 +229,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 17 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2256) - - **Line:** 2256 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2258) + - **Line:** 2258 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313) - - **Line:** 2313 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315) + - **Line:** 2315 - **Context:** `Unknown` - **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8) - **Line:** 8 @@ -285,8 +285,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 2 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2134) - - **Line:** 2134 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2136) + - **Line:** 2136 - **Context:** `export type PlaybackProgress = { itemId: string;` - **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L42) - **Line:** 42 @@ -296,8 +296,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 2 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238) - - **Line:** 2238 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240) + - **Line:** 2240 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L124) - **Line:** 124 @@ -339,8 +339,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 13 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2510) - - **Line:** 2510 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2512) + - **Line:** 2512 - **Context:** `export type RemoteSessionStatus = { position: number; duration: number...` - **File:** [`src/lib/stores/queue.test.ts`](src/lib/stores/queue.test.ts#L4) - **Line:** 4 @@ -475,11 +475,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 9 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L531) - - **Line:** 531 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L533) + - **Line:** 533 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L547) - - **Line:** 547 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L549) + - **Line:** 549 - **Context:** `Unknown` - **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L2) - **Line:** 2 @@ -738,8 +738,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 18 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313) - - **Line:** 2313 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315) + - **Line:** 2315 - **Context:** `Unknown` - **File:** [`src/lib/utils/duration.test.ts`](src/lib/utils/duration.test.ts#L4) - **Line:** 4 @@ -797,8 +797,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 10 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2570) - - **Line:** 2570 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2572) + - **Line:** 2572 - **Context:** `export type SessionInfo = { id?: string | null; userId?: string | null...` - **File:** [`src/lib/components/player/SleepTimerModal.svelte`](src/lib/components/player/SleepTimerModal.svelte#L1) - **Line:** 1 @@ -838,8 +838,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L181) - **Line:** 181 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1778) - - **Line:** 1778 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1780) + - **Line:** 1780 - **Context:** `export type DownloadsResponse = { downloads: DownloadInfo[]; stats: Do...` - **File:** [`src-tauri/src/player/android/mod.rs`](src-tauri/src/player/android/mod.rs#L545) - **Line:** 545 @@ -1137,10 +1137,13 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, ### DR-047 -**Locations:** 13 file(s) +**Locations:** 15 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313) - - **Line:** 2313 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241) + - **Line:** 241 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315) + - **Line:** 2315 - **Context:** `Unknown` - **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9) - **Line:** 9 @@ -1178,6 +1181,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) - **Line:** 3 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145) + - **Line:** 145 + - **Context:** `Unknown` ### DR-048 @@ -1234,7 +1240,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, ### DR-052 -**Locations:** 14 file(s) +**Locations:** 15 file(s) - **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L38) - **Line:** 38 @@ -1242,6 +1248,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51) - **Line:** 51 - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241) + - **Line:** 241 + - **Context:** `Unknown` - **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10) - **Line:** 10 - **Context:** `Unknown` @@ -1269,14 +1278,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L1264) - **Line:** 1264 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L68) - - **Line:** 68 - - **Context:** `pub struct VideoSettingsWrapper(pub Mutex);` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583) - - **Line:** 583 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571) + - **Line:** 571 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675) - - **Line:** 675 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663) + - **Line:** 663 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145) + - **Line:** 145 - **Context:** `Unknown` ### DR-053 @@ -1381,11 +1390,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/routes/search/+page.svelte`](src/routes/search/+page.svelte#L14) - **Line:** 14 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1298) - - **Line:** 1298 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1300) + - **Line:** 1300 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2545) - - **Line:** 2545 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2547) + - **Line:** 2547 - **Context:** `export type SearchResult = { items: MediaItem[]; totalRecordCount: num...` - **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L236) - **Line:** 236 @@ -1502,17 +1511,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 17 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L948) - - **Line:** 948 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L950) + - **Line:** 950 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L959) - - **Line:** 959 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L961) + - **Line:** 961 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1970) - - **Line:** 1970 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1972) + - **Line:** 1972 - **Context:** `export type MergedMediaItem = { id: string; title: string; artist: str...` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1979) - - **Line:** 1979 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1981) + - **Line:** 1981 - **Context:** `export type NetworkStateWrapperArg = { networkType: NetworkType; unmet...` - **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L81) - **Line:** 81 @@ -1653,11 +1662,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105) - **Line:** 105 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1221) - - **Line:** 1221 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1223) + - **Line:** 1223 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1229) - - **Line:** 1229 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1231) + - **Line:** 1231 - **Context:** `Unknown` - **File:** [`src/lib/components/downloads/DownloadedBrowse.svelte`](src/lib/components/downloads/DownloadedBrowse.svelte#L10) - **Line:** 10 @@ -1715,11 +1724,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105) - **Line:** 105 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L805) - - **Line:** 805 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L807) + - **Line:** 807 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1229) - - **Line:** 1229 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1231) + - **Line:** 1231 - **Context:** `Unknown` - **File:** [`src/lib/components/library/MediaCard.svelte`](src/lib/components/library/MediaCard.svelte#L24) - **Line:** 24 @@ -1788,11 +1797,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L113) - **Line:** 113 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1237) - - **Line:** 1237 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1239) + - **Line:** 1239 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1723) - - **Line:** 1723 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1725) + - **Line:** 1725 - **Context:** `Unknown` - **File:** [`src/lib/components/library/LibraryGrid.svelte`](src/lib/components/library/LibraryGrid.svelte#L17) - **Line:** 17 @@ -2267,8 +2276,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L203) - **Line:** 203 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1324) - - **Line:** 1324 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1326) + - **Line:** 1326 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520) - **Line:** 520 @@ -2293,8 +2302,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 16 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238) - - **Line:** 2238 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240) + - **Line:** 2240 - **Context:** `Unknown` - **File:** [`src/lib/stores/connectivity.ts`](src/lib/stores/connectivity.ts#L10) - **Line:** 10 @@ -2346,11 +2355,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 20 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2134) - - **Line:** 2134 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2136) + - **Line:** 2136 - **Context:** `export type PlaybackProgress = { itemId: string;` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238) - - **Line:** 2238 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240) + - **Line:** 2240 - **Context:** `Unknown` - **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) - **Line:** 1 @@ -2411,11 +2420,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 15 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2134) - - **Line:** 2134 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2136) + - **Line:** 2136 - **Context:** `export type PlaybackProgress = { itemId: string;` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238) - - **Line:** 2238 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240) + - **Line:** 2240 - **Context:** `Unknown` - **File:** [`src/lib/components/player/AudioPlayer.svelte`](src/lib/components/player/AudioPlayer.svelte#L1) - **Line:** 1 @@ -2461,14 +2470,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 66 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2256) - - **Line:** 2256 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2258) + - **Line:** 2258 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313) - - **Line:** 2313 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315) + - **Line:** 2315 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2510) - - **Line:** 2510 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2512) + - **Line:** 2512 - **Context:** `export type RemoteSessionStatus = { position: number; duration: number...` - **File:** [`src/lib/components/player/Controls.svelte`](src/lib/components/player/Controls.svelte#L1) - **Line:** 1 @@ -2714,11 +2723,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 15 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L531) - - **Line:** 531 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L533) + - **Line:** 533 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L547) - - **Line:** 547 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L549) + - **Line:** 549 - **Context:** `Unknown` - **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) - **Line:** 4 @@ -2764,8 +2773,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 11 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238) - - **Line:** 2238 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240) + - **Line:** 2240 - **Context:** `Unknown` - **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L2) - **Line:** 2 @@ -2842,8 +2851,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 12 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313) - - **Line:** 2313 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315) + - **Line:** 2315 - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) - **Line:** 4 @@ -2881,7 +2890,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, ### UR-023 -**Locations:** 22 file(s) +**Locations:** 24 file(s) - **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1) - **Line:** 1 @@ -2892,8 +2901,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/routes/settings/+page.svelte`](src/routes/settings/+page.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313) - - **Line:** 2313 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241) + - **Line:** 241 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315) + - **Line:** 2315 - **Context:** `Unknown` - **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9) - **Line:** 9 @@ -2946,19 +2958,25 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) - **Line:** 3 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145) + - **Line:** 145 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### UR-026 -**Locations:** 17 file(s) +**Locations:** 19 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313) - - **Line:** 2313 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241) + - **Line:** 241 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2570) - - **Line:** 2570 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315) + - **Line:** 2315 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2572) + - **Line:** 2572 - **Context:** `export type SessionInfo = { id?: string | null; userId?: string | null...` - **File:** [`src/lib/components/player/SleepTimerModal.svelte`](src/lib/components/player/SleepTimerModal.svelte#L1) - **Line:** 1 @@ -3005,6 +3023,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) - **Line:** 3 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145) + - **Line:** 145 + - **Context:** `Unknown` ### UR-027 @@ -3016,8 +3037,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L181) - **Line:** 181 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1778) - - **Line:** 1778 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1780) + - **Line:** 1780 - **Context:** `export type DownloadsResponse = { downloads: DownloadInfo[]; stats: Do...` - **File:** [`src-tauri/src/player/android/mod.rs`](src-tauri/src/player/android/mod.rs#L545) - **Line:** 545 @@ -3176,7 +3197,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, ### UR-040 -**Locations:** 26 file(s) +**Locations:** 27 file(s) - **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L434) - **Line:** 434 @@ -3193,8 +3214,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51) - **Line:** 51 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1324) - - **Line:** 1324 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241) + - **Line:** 241 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1326) + - **Line:** 1326 - **Context:** `Unknown` - **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10) - **Line:** 10 @@ -3229,14 +3253,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L1264) - **Line:** 1264 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L68) - - **Line:** 68 - - **Context:** `pub struct VideoSettingsWrapper(pub Mutex);` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583) - - **Line:** 583 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571) + - **Line:** 571 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675) - - **Line:** 675 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663) + - **Line:** 663 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145) + - **Line:** 145 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520) - **Line:** 520 @@ -3298,11 +3322,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/routes/search/+page.svelte`](src/routes/search/+page.svelte#L22) - **Line:** 22 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1298) - - **Line:** 1298 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1300) + - **Line:** 1300 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2545) - - **Line:** 2545 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2547) + - **Line:** 2547 - **Context:** `export type SearchResult = { items: MediaItem[]; totalRecordCount: num...` - **File:** [`src/lib/components/search/SearchScopeChips.svelte`](src/lib/components/search/SearchScopeChips.svelte#L7) - **Line:** 7 @@ -3342,8 +3366,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 9 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1298) - - **Line:** 1298 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1300) + - **Line:** 1300 - **Context:** `Unknown` - **File:** [`src/lib/components/search/SearchResults.svelte`](src/lib/components/search/SearchResults.svelte#L9) - **Line:** 9 @@ -3374,17 +3398,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, **Locations:** 17 file(s) -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L948) - - **Line:** 948 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L950) + - **Line:** 950 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L959) - - **Line:** 959 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L961) + - **Line:** 961 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1970) - - **Line:** 1970 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1972) + - **Line:** 1972 - **Context:** `export type MergedMediaItem = { id: string; title: string; artist: str...` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1979) - - **Line:** 1979 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1981) + - **Line:** 1981 - **Context:** `export type NetworkStateWrapperArg = { networkType: NetworkType; unmet...` - **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L81) - **Line:** 81 @@ -3439,14 +3463,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105) - **Line:** 105 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L805) - - **Line:** 805 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L807) + - **Line:** 807 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1221) - - **Line:** 1221 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1223) + - **Line:** 1223 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1229) - - **Line:** 1229 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1231) + - **Line:** 1231 - **Context:** `Unknown` - **File:** [`src/lib/components/library/MediaCard.svelte`](src/lib/components/library/MediaCard.svelte#L24) - **Line:** 24 @@ -3522,11 +3546,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L113) - **Line:** 113 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1237) - - **Line:** 1237 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1239) + - **Line:** 1239 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1723) - - **Line:** 1723 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1725) + - **Line:** 1725 - **Context:** `Unknown` - **File:** [`src/lib/components/library/LibraryGrid.svelte`](src/lib/components/library/LibraryGrid.svelte#L17) - **Line:** 17 @@ -3611,11 +3635,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51) - **Line:** 51 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583) - - **Line:** 583 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571) + - **Line:** 571 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675) - - **Line:** 675 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663) + - **Line:** 663 - **Context:** `Unknown` ### IT-016 @@ -3903,17 +3927,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, - **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51) - **Line:** 51 - **Context:** `Unknown` -- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1324) - - **Line:** 1324 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1326) + - **Line:** 1326 - **Context:** `Unknown` - **File:** [`src/lib/player/backgroundAudio.commands.test.ts`](src/lib/player/backgroundAudio.commands.test.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583) - - **Line:** 583 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571) + - **Line:** 571 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675) - - **Line:** 675 +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663) + - **Line:** 663 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520) - **Line:** 520 diff --git a/package.json b/package.json index 0fe3614e..1fcbfbfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jellytau", - "version": "0.2.8", + "version": "0.2.9", "description": "", "type": "module", "packageManager": "bun@1.3.5", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f22ef0e1..513ee164 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1994,7 +1994,7 @@ dependencies = [ [[package]] name = "jellytau" -version = "0.2.8" +version = "0.2.9" dependencies = [ "aes-gcm", "async-trait", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2ffa429c..d330bd0c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jellytau" -version = "0.2.8" +version = "0.2.9" description = "A Tauri App" authors = ["you"] edition = "2021" diff --git a/src-tauri/src/commands/player/mod.rs b/src-tauri/src/commands/player/mod.rs index 24c79093..efc40db2 100644 --- a/src-tauri/src/commands/player/mod.rs +++ b/src-tauri/src/commands/player/mod.rs @@ -57,18 +57,6 @@ pub struct MediaSessionManagerWrapper(pub Mutex); /// @req: DR-048 - Video settings (auto-play toggle, countdown duration) pub struct VideoSettingsWrapper(pub Mutex); -/// Base offset (seconds) for the active background-audio handoff. -/// -/// The audio-only stream is requested with `StartTimeTicks` = the handoff -/// position, so the server makes that point the stream's zero. ExoPlayer then -/// reports position RELATIVE to that zero. To convert back to an absolute -/// position on exit (so the video resumes where the audio actually reached), we -/// add this stored base to the native player's reported position. -/// -/// TRACES: UR-040 | DR-052 -#[derive(Default)] -pub struct BackgroundAudioOffset(pub Mutex); - /// Response for player state queries #[derive(specta::Type, Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -586,7 +574,6 @@ pub async fn player_play_item( pub async fn player_enter_background_audio( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, - bg_offset: State<'_, BackgroundAudioOffset>, item: PlayItemRequest, position_seconds: f64, ) -> Result { @@ -636,17 +623,18 @@ pub async fn player_enter_background_audio( session_mgr.start_audio_session(media_item.clone()); } - // Remember where the video was: the audio stream's zero == this position - // (the URL was built with StartTimeTicks=position_seconds), so on exit we add - // this base to the native player's relative position to get the absolute one. - *bg_offset.0.lock().map_err(|e| e.to_string())? = position_seconds.max(0.0); - // Same base offset drives the lockscreen scrubber: ExoPlayer reports position // relative to the stream's StartTimeTicks zero, but the metadata duration is // absolute, so shift the reported position back to absolute for the scrubber. let _ = crate::player::set_lockscreen_position_offset(position_seconds.max(0.0)); let controller = player.0.lock().await; + // Remember where the video was: the audio stream's zero == this position + // (the URL was built with StartTimeTicks=position_seconds), so on exit we add + // this base to the native player's relative position to get the absolute one. + // The controller owns it so a backend-driven advance to the next episode + // clears it along with the stream it described. + controller.set_background_audio_base(position_seconds); controller .play_item(media_item) .map_err(|e| e.to_string())?; @@ -677,21 +665,15 @@ pub async fn player_enter_background_audio( #[specta::specta] pub async fn player_exit_background_audio( player: State<'_, PlayerStateWrapper>, - bg_offset: State<'_, BackgroundAudioOffset>, ) -> Result { - // The base offset (handoff position) + native player's relative position = - // the absolute position to resume the video at. Read/reset the base first. - let base = { - let mut off = bg_offset.0.lock().map_err(|e| e.to_string())?; - let b = *off; - *off = 0.0; - b - }; - // Back to foreground playback: the lockscreen scrubber is absolute again. let _ = crate::player::set_lockscreen_position_offset(0.0); let controller = player.0.lock().await; + // The base offset (handoff position) + native player's relative position = + // the absolute position to resume the video at. Zero after a backend-driven + // episode advance, whose stream already starts at its own zero. + let base = controller.take_background_audio_base(); // Capture position into a `let` BEFORE stop() — never hold work across a lock // re-entrant call (deadlock discipline, CLAUDE.md). let relative = controller.position(); diff --git a/src-tauri/src/commands/player/timers.rs b/src-tauri/src/commands/player/timers.rs index bd6be656..ca56ac64 100644 --- a/src-tauri/src/commands/player/timers.rs +++ b/src-tauri/src/commands/player/timers.rs @@ -141,6 +141,8 @@ pub async fn player_play_next_episode( /// - Frontend when HTML5 video ends (Linux/desktop) - passes itemId + repositoryHandle for the video /// - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed /// - Android JNI callback also triggers this logic directly +/// +/// TRACES: UR-023, UR-026, UR-040 | DR-047, DR-052 #[tauri::command] #[specta::specta] pub async fn player_on_playback_ended( @@ -242,12 +244,17 @@ pub async fn player_on_playback_ended( }); } - // Start countdown if auto_advance enabled + // Advance if auto_advance is enabled. This is the path that actually + // runs on Android: the JNI callback's own decision is swallowed by the + // NewTrackLoaded end reason set at load, so it returns Stop, emits + // PlaybackEnded, and the frontend echoes it back into this command — + // which is where the real decision lands. if auto_advance { controller_arc .lock() .await - .start_autoplay_countdown(next_episode, countdown_seconds); + .auto_advance_to_next_episode(next_episode, countdown_seconds) + .await; } } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index abb94839..3f676199 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1196,9 +1196,6 @@ pub fn run() { let video_settings = VideoSettingsWrapper(Mutex::new(VideoSettings::default())); app.manage(video_settings); - // Background-audio handoff base offset (UR-040). - app.manage(commands::player::BackgroundAudioOffset::default()); - // Initialize thumbnail cache info!("[INIT] Initializing thumbnail cache..."); let app_data_dir = if let Ok(test_data_dir) = std::env::var("JELLYTAU_DATA_DIR") { diff --git a/src-tauri/src/player/android/mod.rs b/src-tauri/src/player/android/mod.rs index a081f367..3a637381 100644 --- a/src-tauri/src/player/android/mod.rs +++ b/src-tauri/src/player/android/mod.rs @@ -915,39 +915,19 @@ pub extern "system" fn Java_com_dtourolle_jellytau_player_JellyTauPlayer_nativeO } if auto_advance { - // Background audio-only episode: the frontend that normally - // performs the advance (goto /player/) is suspended, so - // the backend must load the next episode's audio-only stream - // itself — otherwise playback just stops at the boundary. - let is_bg_audio_episode = - controller.lock().await.current_is_audio_episode(); - if is_bg_audio_episode { - log::info!( - "[Autoplay] Background audio episode — advancing to {} in backend", - next_episode.id - ); - let ctrl = controller.lock().await; - if let Err(e) = ctrl - .advance_to_next_episode_audio_only(&next_episode.id) - .await - { - log::error!( - "[Autoplay] Background audio advance failed: {} — stopping", - e - ); - if let Some(emitter) = EVENT_EMITTER.get() { - emitter.emit(PlayerStatusEvent::PlaybackEnded); - } - } else { - ctrl.emit_queue_changed(); - } - } else { - // Foreground: frontend drives the advance off the countdown. - controller - .lock() - .await - .start_autoplay_countdown(next_episode, countdown_seconds); - } + // Shared with the frontend-invoked command path + // (player_on_playback_ended) so the two dispatchers cannot + // disagree about how a background audio-only episode + // advances — they did, and the command's copy was missing + // the case entirely. That copy is the one that actually + // decides here: the end reason set at load makes this + // callback's own decision Stop, and the frontend echoes the + // resulting PlaybackEnded back into the command. + controller + .lock() + .await + .auto_advance_to_next_episode(next_episode, countdown_seconds) + .await; } } Err(e) => { diff --git a/src-tauri/src/player/mod.rs b/src-tauri/src/player/mod.rs index c2b9775d..431d286f 100644 --- a/src-tauri/src/player/mod.rs +++ b/src-tauri/src/player/mod.rs @@ -105,7 +105,7 @@ pub fn set_lockscreen_position_offset(_offset_seconds: f64) -> Result<(), String } use crate::utils::lock::MutexSafe; -use log::{debug, error, warn}; +use log::{debug, error, info, warn}; use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::sync::Mutex as TokioMutex; @@ -153,6 +153,21 @@ pub struct PlayerController { // Auto-play episode counter (session-based, resets on manual play) autoplay_episode_count: Arc>, + // Base offset (seconds) of the active background-audio handoff. + // + // The audio-only stream is requested with `StartTimeTicks` = the position the + // video was handed off at, so the server makes that point the stream's zero + // and the native player reports position RELATIVE to it. Adding this base back + // yields the absolute position to resume the video at on the way out. + // + // Lives on the controller (not beside the command) because the queue and this + // offset describe the same stream: whenever the controller loads a different + // one — notably the backend-driven advance to the next episode — the base has + // to move with it. + // + // TRACES: UR-040 | DR-052 + background_audio_base: Arc>, + // Last state reported by a webview-rendered HTML5