diff --git a/docs/traceability.md b/docs/traceability.md index 1d4534c8..89042d1c 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -1,37 +1,37 @@ # Code Traceability Matrix -**Generated:** 2/28/2026, 8:39:19 PM +**Generated:** 7/22/2026, 9:49:16 PM ## Summary -- **Total Files Scanned:** 208 -- **Total TRACES Found:** 117 +- **Total Files Scanned:** 264 +- **Total TRACES Found:** 182 - **Requirements Covered:** - - User Requirements (UR): 32 - - Integration Requirements (IR): 8 - - Development Requirements (DR): 40 - - Jellyfin API Requirements (JA): 19 + - User Requirements (UR): 42 + - Integration Requirements (IR): 14 + - Development Requirements (DR): 53 + - Jellyfin API Requirements (JA): 24 ## Requirements by Type ### User Requirements (UR) ``` -UR-002, UR-003, UR-004, UR-005, UR-007, UR-008, UR-009, UR-010, UR-011, UR-012, UR-013, UR-015, UR-016, UR-017, UR-018, UR-019, UR-020, UR-021, UR-023, UR-024, UR-025, UR-026, UR-028, UR-029, UR-030, UR-031, UR-032, UR-033, UR-034, UR-035, UR-036, UR-039 +UR-002, UR-003, UR-004, UR-005, UR-007, UR-008, UR-009, UR-010, UR-011, UR-012, UR-013, UR-014, UR-015, UR-016, UR-017, UR-018, UR-019, UR-020, UR-021, UR-022, UR-023, UR-024, UR-025, UR-026, UR-028, UR-029, UR-030, UR-031, UR-032, UR-033, UR-034, UR-035, UR-036, UR-039, UR-040, UR-041, UR-042, UR-043, UR-044, UR-045, UR-046, UR-047 ``` ### Integration Requirements (IR) ``` -IR-003, IR-004, IR-009, IR-010, IR-011, IR-012, IR-013, IR-014 +IR-003, IR-004, IR-009, IR-010, IR-011, IR-012, IR-013, IR-014, IR-015, IR-023, IR-025, IR-026, IR-027, IR-028 ``` ### Development Requirements (DR) ``` -DR-001, DR-002, DR-003, DR-004, DR-005, DR-006, DR-007, DR-009, DR-010, DR-011, DR-012, DR-013, DR-014, DR-015, DR-016, DR-017, DR-018, DR-020, DR-021, DR-023, DR-024, DR-026, DR-027, DR-028, DR-029, DR-032, DR-033, DR-034, DR-035, DR-036, DR-037, DR-038, DR-039, DR-040, DR-041, DR-044, DR-045, DR-047, DR-048, DR-050 +DR-001, DR-002, DR-003, DR-004, DR-005, DR-006, DR-007, DR-009, DR-010, DR-011, DR-012, DR-013, DR-014, DR-015, DR-016, DR-017, DR-018, DR-020, DR-021, DR-022, DR-023, DR-024, DR-025, DR-026, DR-027, DR-028, DR-029, DR-032, DR-033, DR-034, DR-035, DR-036, DR-037, DR-038, DR-039, DR-040, DR-041, DR-044, DR-045, DR-047, DR-048, DR-049, DR-050, DR-051, DR-052, DR-053, DR-054, DR-055, DR-056, DR-057, DR-058, DR-059, DR-060 ``` ### Jellyfin API Requirements (JA) ``` -JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, JA-021, JA-022, JA-023, JA-024, JA-025, JA-026, JA-029, JA-030, JA-031 +JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016, JA-017, JA-019, JA-020, JA-021, JA-022, JA-023, JA-024, JA-025, JA-026, JA-028, JA-029, JA-030, JA-031, JA-032 ``` ## Detailed Mapping @@ -57,19 +57,22 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### IR-009 -**Locations:** 5 file(s) +**Locations:** 6 file(s) +- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` - **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L6) - **Line:** 6 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L269) - - **Line:** 269 +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L266) + - **Line:** 266 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L296) - - **Line:** 296 +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L293) + - **Line:** 293 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) - - **Line:** 4 +- **File:** [`src-tauri/src/commands/auth.rs`](src-tauri/src/commands/auth.rs#L3) + - **Line:** 3 - **Context:** `Unknown` - **File:** [`src-tauri/src/jellyfin/client.rs`](src-tauri/src/jellyfin/client.rs#L1) - **Line:** 1 @@ -96,67 +99,136 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### IR-012 -**Locations:** 1 file(s) +**Locations:** 2 file(s) - **File:** [`src/lib/stores/playbackMode.ts`](src/lib/stores/playbackMode.ts#L9) - **Line:** 9 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### IR-013 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/storage/schema.rs`](src-tauri/src/storage/schema.rs#L3) - **Line:** 3 - **Context:** `Unknown` ### IR-014 -**Locations:** 5 file(s) +**Locations:** 6 file(s) +- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` - **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L6) - **Line:** 6 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L296) - - **Line:** 296 +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L293) + - **Line:** 293 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L466) - - **Line:** 466 +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L445) + - **Line:** 445 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) - - **Line:** 4 +- **File:** [`src-tauri/src/commands/auth.rs`](src-tauri/src/commands/auth.rs#L3) + - **Line:** 3 - **Context:** `Unknown` - **File:** [`src-tauri/src/credentials.rs`](src-tauri/src/credentials.rs#L10) - **Line:** 10 - **Context:** `Unknown` +### IR-015 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/playback_reporting.rs`](src-tauri/src/commands/playback_reporting.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### IR-023 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/storage/people.rs`](src-tauri/src/commands/storage/people.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### IR-025 + +**Locations:** 1 file(s) + +- **File:** [`src/lib/utils/backgroundAudio.ts`](src/lib/utils/backgroundAudio.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` + +### IR-026 + +**Locations:** 1 file(s) + +- **File:** [`src/lib/utils/pictureInPicture.ts`](src/lib/utils/pictureInPicture.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` + +### IR-027 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/connectivity.rs`](src-tauri/src/commands/connectivity.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### IR-028 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + ### DR-001 -**Locations:** 11 file(s) +**Locations:** 17 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1974) + - **Line:** 1974 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2031) + - **Line:** 2031 + - **Context:** `Unknown` - **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L29) - - **Line:** 29 +- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L31) + - **Line:** 31 - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/playerEvents.regression.test.ts`](src/lib/services/playerEvents.regression.test.ts#L9) + - **Line:** 9 + - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L26) - - **Line:** 26 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L186) + - **Line:** 186 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L170) - - **Line:** 170 - - **Context:** `function handlePositionUpdate(position: number, duration: number): voi...` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) - - **Line:** 6 +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L20) - - **Line:** 20 +- **File:** [`src/lib/player/adapters/rustReportHost.ts`](src/lib/player/adapters/rustReportHost.ts#L13) + - **Line:** 13 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` +- **File:** [`src/lib/player/index.ts`](src/lib/player/index.ts#L15) + - **Line:** 15 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/state.rs`](src-tauri/src/player/state.rs#L7) - **Line:** 7 @@ -164,118 +236,184 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src-tauri/src/player/state.rs`](src-tauri/src/player/state.rs#L25) - **Line:** 25 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) + - **Line:** 6 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L23) + - **Line:** 23 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### DR-002 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1860) + - **Line:** 1860 + - **Context:** `export type PlaybackProgress = { itemId: string; positionTicks: number...` - **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L42) - **Line:** 42 - **Context:** `Unknown` ### DR-003 -**Locations:** 1 file(s) +**Locations:** 2 file(s) -- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L116) - - **Line:** 116 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1956) + - **Line:** 1956 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L117) + - **Line:** 117 - **Context:** `pub enum MediaType {` ### DR-004 -**Locations:** 3 file(s) +**Locations:** 5 file(s) +- **File:** [`src/lib/player/adapters/index.ts`](src/lib/player/adapters/index.ts#L11) + - **Line:** 11 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/nativeAdapter.ts`](src/lib/player/adapters/nativeAdapter.ts#L16) + - **Line:** 16 + - **Context:** `Unknown` - **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L39) - **Line:** 39 - **Context:** `pub fn playback_failed>(message: S) -> Self {` -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L241) - - **Line:** 241 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L251) + - **Line:** 251 - **Context:** `Unknown` ### DR-005 -**Locations:** 11 file(s) +**Locations:** 13 file(s) -- **File:** [`src/lib/stores/appState.ts`](src/lib/stores/appState.ts#L2) - - **Line:** 2 - - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2179) + - **Line:** 2179 + - **Context:** `export type RemoteSessionStatus = { position: number; duration: number...` - **File:** [`src/lib/stores/queue.test.ts`](src/lib/stores/queue.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` - **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L106) - - **Line:** 106 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L105) + - **Line:** 105 - **Context:** `Unknown` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L111) - - **Line:** 111 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L110) + - **Line:** 110 - **Context:** `async function next() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L116) - - **Line:** 116 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L115) + - **Line:** 115 - **Context:** `async function previous() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L121) - - **Line:** 121 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L120) + - **Line:** 120 - **Context:** `async function skipTo(index: number) {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L126) - - **Line:** 126 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L125) + - **Line:** 125 - **Context:** `async function toggleShuffle() {` +- **File:** [`src/lib/stores/appState.ts`](src/lib/stores/appState.ts#L2) + - **Line:** 2 + - **Context:** `Unknown` - **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L8) - **Line:** 8 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L20) - **Line:** 20 - **Context:** `pub enum RepeatMode {` -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L508) - - **Line:** 508 +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535) + - **Line:** 535 - **Context:** `pub enum AddPosition {` +- **File:** [`src-tauri/src/commands/player/queue.rs`](src-tauri/src/commands/player/queue.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-006 **Locations:** 2 file(s) -- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) - - **Line:** 5 - - **Context:** `Unknown` - **File:** [`src/lib/services/preload.test.ts`](src/lib/services/preload.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) + - **Line:** 5 + - **Context:** `Unknown` ### DR-007 -**Locations:** 2 file(s) +**Locations:** 9 file(s) +- **File:** [`src/routes/library/music/+page.svelte`](src/routes/library/music/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/tv/+page.svelte`](src/routes/library/tv/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/lib/components/library/AlphabetScrollBar.svelte`](src/lib/components/library/AlphabetScrollBar.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/library/GenericMediaListPage.svelte`](src/lib/components/library/GenericMediaListPage.svelte#L1) - **Line:** 1 - **Context:** `Unknown` - **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src/lib/stores/movies.ts`](src/lib/stores/movies.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/tv.ts`](src/lib/stores/tv.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/music.ts`](src/lib/stores/music.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-009 -**Locations:** 5 file(s) +**Locations:** 12 file(s) +- **File:** [`src/lib/components/player/Controls.svelte`](src/lib/components/player/Controls.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/player/AudioPlayer.svelte`](src/lib/components/player/AudioPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` - **File:** [`src/lib/components/player/MiniPlayer.svelte`](src/lib/components/player/MiniPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/components/player/Controls.svelte`](src/lib/components/player/Controls.svelte#L1) - - **Line:** 1 +- **File:** [`src/lib/components/BottomUi.svelte`](src/lib/components/BottomUi.svelte#L16) + - **Line:** 16 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8) + - **Line:** 8 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/playerVisibility.test.ts`](src/lib/stores/playerVisibility.test.ts#L9) + - **Line:** 9 - **Context:** `Unknown` - **File:** [`src/lib/stores/appState.ts`](src/lib/stores/appState.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8) - - **Line:** 8 +- **File:** [`src/lib/utils/layoutShell.test.ts`](src/lib/utils/layoutShell.test.ts#L14) + - **Line:** 14 + - **Context:** `Unknown` +- **File:** [`src/lib/utils/layoutShell.ts`](src/lib/utils/layoutShell.ts#L17) + - **Line:** 17 + - **Context:** `Unknown` +- **File:** [`src/lib/player/index.ts`](src/lib/player/index.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/session.rs`](src-tauri/src/commands/player/session.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/conversions.rs`](src-tauri/src/commands/conversions.rs#L3) + - **Line:** 3 - **Context:** `Unknown` ### DR-010 @@ -288,39 +426,51 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### DR-011 -**Locations:** 7 file(s) +**Locations:** 9 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L521) + - **Line:** 521 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L537) + - **Line:** 537 + - **Context:** `Unknown` - **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src/lib/services/deviceId.test.ts`](src/lib/services/deviceId.test.ts#L7) + - **Line:** 7 + - **Context:** `Unknown` - **File:** [`src/lib/services/deviceId.ts`](src/lib/services/deviceId.ts#L8) - **Line:** 8 - **Context:** `Unknown` - **File:** [`src/lib/services/deviceId.ts`](src/lib/services/deviceId.ts#L23) - **Line:** 23 - **Context:** `Unknown` -- **File:** [`src/lib/services/deviceId.test.ts`](src/lib/services/deviceId.test.ts#L7) - - **Line:** 7 - - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L4) - **Line:** 4 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L24) - **Line:** 24 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L80) - - **Line:** 80 +- **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L81) + - **Line:** 81 - **Context:** `Unknown` ### DR-012 -**Locations:** 3 file(s) +**Locations:** 5 file(s) -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 +- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 - **Context:** `pub fn file_size(&self) -> Option {` - **File:** [`src-tauri/src/storage/schema.rs`](src-tauri/src/storage/schema.rs#L3) - **Line:** 3 @@ -328,10 +478,13 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### DR-013 -**Locations:** 2 file(s) +**Locations:** 3 file(s) -- **File:** [`src/lib/stores/connectivity.ts`](src/lib/stores/connectivity.ts#L5) - - **Line:** 5 +- **File:** [`src/lib/stores/connectivity.ts`](src/lib/stores/connectivity.ts#L10) + - **Line:** 10 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 - **Context:** `Unknown` - **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L1) - **Line:** 1 @@ -344,11 +497,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L58) - - **Line:** 58 +- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L64) + - **Line:** 64 - **Context:** `Unknown` -- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L170) - - **Line:** 170 +- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L188) + - **Line:** 188 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/sync.rs`](src-tauri/src/commands/sync.rs#L5) - **Line:** 5 @@ -367,25 +520,28 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/utils/validation.test.ts`](src/lib/utils/validation.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` -- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) - - **Line:** 5 - - **Context:** `Unknown` - **File:** [`src/lib/services/preload.test.ts`](src/lib/services/preload.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/download.rs`](src-tauri/src/commands/download.rs#L1552) - - **Line:** 1552 +- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) + - **Line:** 5 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L1841) + - **Line:** 1841 - **Context:** `Unknown` ### DR-016 -**Locations:** 2 file(s) +**Locations:** 3 file(s) +- **File:** [`src/lib/services/imageCache.test.ts`](src/lib/services/imageCache.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` - **File:** [`src/lib/services/imageCache.ts`](src/lib/services/imageCache.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/services/imageCache.test.ts`](src/lib/services/imageCache.test.ts#L4) - - **Line:** 4 +- **File:** [`src-tauri/src/commands/storage/thumbnails.rs`](src-tauri/src/commands/storage/thumbnails.rs#L3) + - **Line:** 3 - **Context:** `Unknown` ### DR-017 @@ -398,21 +554,21 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L133) - - **Line:** 133 +- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L136) + - **Line:** 136 - **Context:** `Unknown` ### DR-018 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/commands/download.rs`](src-tauri/src/commands/download.rs#L1552) - - **Line:** 1552 +- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L1841) + - **Line:** 1841 - **Context:** `Unknown` ### DR-020 -**Locations:** 7 file(s) +**Locations:** 8 file(s) - **File:** [`src/lib/stores/queue.test.ts`](src/lib/stores/queue.test.ts#L4) - **Line:** 4 @@ -420,48 +576,82 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L116) - - **Line:** 116 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L115) + - **Line:** 115 - **Context:** `async function previous() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L131) - - **Line:** 131 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L130) + - **Line:** 130 - **Context:** `async function cycleRepeat() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L136) - - **Line:** 136 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L135) + - **Line:** 135 - **Context:** `async function removeFromQueue(index: number) {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L141) - - **Line:** 141 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L140) + - **Line:** 140 - **Context:** `async function moveInQueue(fromIndex: number, toIndex: number) {` - **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L20) - **Line:** 20 - **Context:** `pub enum RepeatMode {` +- **File:** [`src-tauri/src/commands/player/queue.rs`](src-tauri/src/commands/player/queue.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-021 **Locations:** 2 file(s) +- **File:** [`src/lib/services/favorites.test.ts`](src/lib/services/favorites.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` - **File:** [`src/lib/services/favorites.ts`](src/lib/services/favorites.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/services/favorites.test.ts`](src/lib/services/favorites.test.ts#L4) - - **Line:** 4 + +### DR-022 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 - **Context:** `Unknown` ### DR-023 -**Locations:** 1 file(s) +**Locations:** 3 file(s) - **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` ### DR-024 -**Locations:** 1 file(s) +**Locations:** 4 file(s) - **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/series_prefs.rs`](src-tauri/src/commands/storage/series_prefs.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### DR-025 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-026 @@ -476,71 +666,95 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### DR-027 -**Locations:** 1 file(s) +**Locations:** 2 file(s) - **File:** [`src/lib/stores/home.ts`](src/lib/stores/home.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-028 -**Locations:** 13 file(s) +**Locations:** 18 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2031) + - **Line:** 2031 + - **Context:** `Unknown` - **File:** [`src/lib/utils/duration.test.ts`](src/lib/utils/duration.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` - **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) - **Line:** 10 - **Context:** `Unknown` - **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L21) - **Line:** 21 - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L64) - - **Line:** 64 +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L58) + - **Line:** 58 - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L99) - - **Line:** 99 +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L89) + - **Line:** 89 - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L136) - - **Line:** 136 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) - - **Line:** 4 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) - - **Line:** 4 +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L122) + - **Line:** 122 - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L26) - - **Line:** 26 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L156) + - **Line:** 156 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L160) - - **Line:** 160 +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/nativeAdapter.ts`](src/lib/player/adapters/nativeAdapter.ts#L16) + - **Line:** 16 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/rustReportHost.ts`](src/lib/player/adapters/rustReportHost.ts#L13) + - **Line:** 13 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) - **Line:** 6 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L20) - - **Line:** 20 +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L23) + - **Line:** 23 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/playback_reporting.rs`](src-tauri/src/commands/playback_reporting.rs#L3) + - **Line:** 3 - **Context:** `Unknown` ### DR-029 -**Locations:** 7 file(s) +**Locations:** 9 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2221) + - **Line:** 2221 + - **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 - **Context:** `Unknown` - **File:** [`src/lib/stores/sleepTimer.ts`](src/lib/stores/sleepTimer.ts#L10) - **Line:** 10 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L263) - - **Line:** 263 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L264) + - **Line:** 264 - **Context:** `function handleMediaLoaded(duration: number): void {` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L300) - - **Line:** 300 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L301) + - **Line:** 301 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/autoplay.rs`](src-tauri/src/player/autoplay.rs#L2) + - **Line:** 2 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/sleep_timer.rs`](src-tauri/src/player/sleep_timer.rs#L4) - **Line:** 4 @@ -548,8 +762,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src-tauri/src/player/sleep_timer.rs`](src-tauri/src/player/sleep_timer.rs#L81) - **Line:** 81 - **Context:** `pub fn cancel(&mut self) {` -- **File:** [`src-tauri/src/player/autoplay.rs`](src-tauri/src/player/autoplay.rs#L2) - - **Line:** 2 +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) + - **Line:** 3 - **Context:** `Unknown` ### DR-032 @@ -573,32 +787,44 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### DR-034 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### DR-035 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### DR-036 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### DR-037 -**Locations:** 10 file(s) +**Locations:** 12 file(s) +- **File:** [`src/lib/components/sessions/SessionCard.svelte`](src/lib/components/sessions/SessionCard.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/sessions/SessionsList.svelte`](src/lib/components/sessions/SessionsList.svelte#L1) - **Line:** 1 - **Context:** `Unknown` @@ -611,50 +837,92 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/components/sessions/SessionPickerModal.svelte`](src/lib/components/sessions/SessionPickerModal.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/components/sessions/SessionCard.svelte`](src/lib/components/sessions/SessionCard.svelte#L1) - - **Line:** 1 - - **Context:** `Unknown` - **File:** [`src/lib/stores/sessions.test.ts`](src/lib/stores/sessions.test.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/stores/playbackMode.test.ts`](src/lib/stores/playbackMode.test.ts#L2) +- **File:** [`src/lib/stores/sessions.ts`](src/lib/stores/sessions.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src/lib/stores/lmsSync.ts`](src/lib/stores/lmsSync.ts#L12) + - **Line:** 12 + - **Context:** `Unknown` - **File:** [`src/lib/stores/playbackMode.ts`](src/lib/stores/playbackMode.ts#L9) - **Line:** 9 - **Context:** `Unknown` -- **File:** [`src/lib/stores/sessions.ts`](src/lib/stores/sessions.ts#L2) +- **File:** [`src/lib/stores/playbackMode.test.ts`](src/lib/stores/playbackMode.test.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/sessions.rs`](src-tauri/src/commands/sessions.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### DR-038 -**Locations:** 2 file(s) +**Locations:** 9 file(s) +- **File:** [`src/routes/library/music/+page.svelte`](src/routes/library/music/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/tv/+page.svelte`](src/routes/library/tv/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/home/HeroBanner.svelte`](src/lib/components/home/HeroBanner.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src/lib/stores/movies.ts`](src/lib/stores/movies.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/tv.ts`](src/lib/stores/tv.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/music.ts`](src/lib/stores/music.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src/lib/stores/home.ts`](src/lib/stores/home.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src/lib/utils/heroMix.ts`](src/lib/utils/heroMix.ts#L8) + - **Line:** 8 + - **Context:** `Unknown` ### DR-039 -**Locations:** 2 file(s) +**Locations:** 8 file(s) +- **File:** [`src/routes/library/music/+page.svelte`](src/routes/library/music/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/tv/+page.svelte`](src/routes/library/tv/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/home/Carousel.svelte`](src/lib/components/home/Carousel.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src/lib/stores/movies.ts`](src/lib/stores/movies.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/tv.ts`](src/lib/stores/tv.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/music.ts`](src/lib/stores/music.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src/lib/stores/home.ts`](src/lib/stores/home.ts#L2) - **Line:** 2 - **Context:** `Unknown` ### DR-040 -**Locations:** 2 file(s) +**Locations:** 3 file(s) - **File:** [`src/lib/components/library/CastSection.svelte`](src/lib/components/library/CastSection.svelte#L1) - **Line:** 1 @@ -662,14 +930,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/components/library/CrewLinks.svelte`](src/lib/components/library/CrewLinks.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/people.rs`](src-tauri/src/commands/storage/people.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-041 -**Locations:** 1 file(s) +**Locations:** 2 file(s) - **File:** [`src/lib/components/library/PersonDetailView.svelte`](src/lib/components/library/PersonDetailView.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/people.rs`](src-tauri/src/commands/storage/people.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-044 @@ -689,44 +963,47 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### DR-047 -**Locations:** 12 file(s) +**Locations:** 13 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2031) + - **Line:** 2031 + - **Context:** `Unknown` - **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9) - **Line:** 9 - **Context:** `Unknown` -- **File:** [`src/lib/services/nextEpisodeService.ts`](src/lib/services/nextEpisodeService.ts#L7) - - **Line:** 7 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) - - **Line:** 10 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) - - **Line:** 4 - - **Context:** `Unknown` - **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) + - **Line:** 10 + - **Context:** `Unknown` +- **File:** [`src/lib/services/nextEpisodeService.ts`](src/lib/services/nextEpisodeService.ts#L11) + - **Line:** 11 + - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L26) - - **Line:** 26 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L263) - - **Line:** 263 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L264) + - **Line:** 264 - **Context:** `function handleMediaLoaded(duration: number): void {` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L309) - - **Line:** 309 - - **Context:** `function handleSleepTimerChanged(mode: SleepTimerMode, remainingSecond...` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) - - **Line:** 6 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L20) - - **Line:** 20 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L340) + - **Line:** 340 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/autoplay.rs`](src-tauri/src/player/autoplay.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) + - **Line:** 6 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L23) + - **Line:** 23 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### DR-048 @@ -738,12 +1015,12 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9) - **Line:** 9 - **Context:** `Unknown` -- **File:** [`src/lib/services/nextEpisodeService.ts`](src/lib/services/nextEpisodeService.ts#L7) - - **Line:** 7 +- **File:** [`src/lib/services/nextEpisodeService.ts`](src/lib/services/nextEpisodeService.ts#L11) + - **Line:** 11 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L340) + - **Line:** 340 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L309) - - **Line:** 309 - - **Context:** `function handleSleepTimerChanged(mode: SleepTimerMode, remainingSecond...` - **File:** [`src-tauri/src/player/autoplay.rs`](src-tauri/src/player/autoplay.rs#L2) - **Line:** 2 - **Context:** `Unknown` @@ -751,6 +1028,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **Line:** 1 - **Context:** `Unknown` +### DR-049 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + ### DR-050 **Locations:** 1 file(s) @@ -759,6 +1044,110 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **Line:** 1 - **Context:** `Unknown` +### DR-051 + +**Locations:** 2 file(s) + +- **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/lib/utils/backgroundAudio.ts`](src/lib/utils/backgroundAudio.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` + +### DR-052 + +**Locations:** 8 file(s) + +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L38) + - **Line:** 38 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51) + - **Line:** 51 + - **Context:** `Unknown` +- **File:** [`src/lib/components/player/backgroundAudioHandoff.ts`](src/lib/components/player/backgroundAudioHandoff.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10) + - **Line:** 10 + - **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#L543) + - **Line:** 543 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L623) + - **Line:** 623 + - **Context:** `Unknown` + +### DR-053 + +**Locations:** 1 file(s) + +- **File:** [`src/lib/utils/pictureInPicture.ts`](src/lib/utils/pictureInPicture.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` + +### DR-054 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/auth.rs`](src-tauri/src/commands/auth.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### DR-055 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/connectivity.rs`](src-tauri/src/commands/connectivity.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### DR-056 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/download/pinning.rs`](src-tauri/src/commands/download/pinning.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### DR-057 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/download/smart_cache.rs`](src-tauri/src/commands/download/smart_cache.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### DR-058 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### DR-059 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/playback_mode.rs`](src-tauri/src/commands/playback_mode.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### DR-060 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + ### JA-001 **Locations:** 1 file(s) @@ -769,8 +1158,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### JA-002 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/auth.rs`](src-tauri/src/commands/auth.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/jellyfin/client.rs`](src-tauri/src/jellyfin/client.rs#L1) - **Line:** 1 - **Context:** `Unknown` @@ -785,8 +1177,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### JA-004 -**Locations:** 2 file(s) +**Locations:** 3 file(s) +- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L4) - **Line:** 4 - **Context:** `Unknown` @@ -812,28 +1207,45 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### JA-010 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/playback_reporting.rs`](src-tauri/src/commands/playback_reporting.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/jellyfin/client.rs`](src-tauri/src/jellyfin/client.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### JA-011 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/playback_reporting.rs`](src-tauri/src/commands/playback_reporting.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/jellyfin/client.rs`](src-tauri/src/jellyfin/client.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### JA-012 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/playback_reporting.rs`](src-tauri/src/commands/playback_reporting.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/jellyfin/client.rs`](src-tauri/src/jellyfin/client.rs#L1) - **Line:** 1 - **Context:** `Unknown` +### JA-016 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + ### JA-017 **Locations:** 1 file(s) @@ -842,9 +1254,25 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **Line:** 1 - **Context:** `Unknown` +### JA-019 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/playlist.rs`](src-tauri/src/commands/playlist.rs#L4) + - **Line:** 4 + - **Context:** `Unknown` + +### JA-020 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/playlist.rs`](src-tauri/src/commands/playlist.rs#L4) + - **Line:** 4 + - **Context:** `Unknown` + ### JA-021 -**Locations:** 5 file(s) +**Locations:** 6 file(s) - **File:** [`src/lib/components/sessions/SessionsList.svelte`](src/lib/components/sessions/SessionsList.svelte#L1) - **Line:** 1 @@ -852,6 +1280,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/components/sessions/SessionPickerModal.svelte`](src/lib/components/sessions/SessionPickerModal.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src/lib/stores/lmsSync.ts`](src/lib/stores/lmsSync.ts#L12) + - **Line:** 12 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/sessions.rs`](src-tauri/src/commands/sessions.rs#L1) - **Line:** 1 - **Context:** `Unknown` @@ -864,23 +1295,29 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### JA-022 -**Locations:** 2 file(s) +**Locations:** 3 file(s) - **File:** [`src/lib/components/sessions/RemoteControls.svelte`](src/lib/components/sessions/RemoteControls.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### JA-023 -**Locations:** 2 file(s) +**Locations:** 3 file(s) - **File:** [`src/lib/components/sessions/RemoteControls.svelte`](src/lib/components/sessions/RemoteControls.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` @@ -891,32 +1328,49 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/components/sessions/RemoteControls.svelte`](src/lib/components/sessions/RemoteControls.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### JA-025 -**Locations:** 2 file(s) +**Locations:** 4 file(s) - **File:** [`src/lib/components/sessions/SessionPickerModal.svelte`](src/lib/components/sessions/SessionPickerModal.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src/lib/stores/lmsSync.ts`](src/lib/stores/lmsSync.ts#L12) + - **Line:** 12 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### JA-026 -**Locations:** 2 file(s) +**Locations:** 3 file(s) - **File:** [`src/lib/components/sessions/RemoteControls.svelte`](src/lib/components/sessions/RemoteControls.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` +### JA-028 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/storage/thumbnails.rs`](src-tauri/src/commands/storage/thumbnails.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + ### JA-029 **Locations:** 3 file(s) @@ -953,33 +1407,77 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **Line:** 4 - **Context:** `Unknown` +### JA-032 + +**Locations:** 9 file(s) + +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1246) + - **Line:** 1246 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L178) + - **Line:** 178 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L394) + - **Line:** 394 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L411) + - **Line:** 411 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L444) + - **Line:** 444 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/hybrid.rs`](src-tauri/src/repository/hybrid.rs#L80) + - **Line:** 80 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L435) + - **Line:** 435 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2084) + - **Line:** 2084 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2125) + - **Line:** 2125 + - **Context:** `Unknown` + ### UR-002 -**Locations:** 10 file(s) +**Locations:** 14 file(s) -- **File:** [`src/lib/stores/connectivity.ts`](src/lib/stores/connectivity.ts#L5) - - **Line:** 5 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1956) + - **Line:** 1956 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/connectivity.ts`](src/lib/stores/connectivity.ts#L10) + - **Line:** 10 - **Context:** `Unknown` - **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L116) - - **Line:** 116 - - **Context:** `pub enum MediaType {` -- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L133) - - **Line:** 133 +- **File:** [`src/lib/services/offlineCatalog.ts`](src/lib/services/offlineCatalog.ts#L14) + - **Line:** 14 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - - **Line:** 2 +- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L117) + - **Line:** 117 + - **Context:** `pub enum MediaType {` +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/sync.rs`](src-tauri/src/commands/sync.rs#L5) - **Line:** 5 - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 +- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L136) + - **Line:** 136 - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 +- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) + - **Line:** 2 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 - **Context:** `pub fn file_size(&self) -> Option {` - **File:** [`src-tauri/src/storage/schema.rs`](src-tauri/src/storage/schema.rs#L3) - **Line:** 3 @@ -990,16 +1488,37 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### UR-003 -**Locations:** 9 file(s) +**Locations:** 16 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1860) + - **Line:** 1860 + - **Context:** `export type PlaybackProgress = { itemId: string; positionTicks: number...` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1956) + - **Line:** 1956 + - **Context:** `Unknown` - **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/index.ts`](src/lib/player/adapters/index.ts#L11) + - **Line:** 11 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/nativeAdapter.ts`](src/lib/player/adapters/nativeAdapter.ts#L16) + - **Line:** 16 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/rustReportHost.ts`](src/lib/player/adapters/rustReportHost.ts#L13) + - **Line:** 13 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` - **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L39) - **Line:** 39 - **Context:** `pub fn playback_failed>(message: S) -> Self {` -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/mpv_backend_test.rs`](src-tauri/src/player/mpv_backend_test.rs#L8) - **Line:** 8 @@ -1007,37 +1526,43 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L42) - **Line:** 42 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L116) - - **Line:** 116 +- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L117) + - **Line:** 117 - **Context:** `pub enum MediaType {` - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) - - **Line:** 1 - - **Context:** `Unknown` ### UR-004 -**Locations:** 11 file(s) +**Locations:** 13 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1860) + - **Line:** 1860 + - **Context:** `export type PlaybackProgress = { itemId: string; positionTicks: number...` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1956) + - **Line:** 1956 + - **Context:** `Unknown` - **File:** [`src/lib/components/player/AudioPlayer.svelte`](src/lib/components/player/AudioPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) - - **Line:** 5 - - **Context:** `Unknown` - **File:** [`src/lib/services/preload.test.ts`](src/lib/services/preload.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) + - **Line:** 5 + - **Context:** `Unknown` - **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L39) - **Line:** 39 - **Context:** `pub fn playback_failed>(message: S) -> Self {` -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/mpv_backend_test.rs`](src-tauri/src/player/mpv_backend_test.rs#L8) - **Line:** 8 @@ -1045,113 +1570,134 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L42) - **Line:** 42 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L116) - - **Line:** 116 +- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L117) + - **Line:** 117 - **Context:** `pub enum MediaType {` - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) - - **Line:** 1 - - **Context:** `Unknown` ### UR-005 -**Locations:** 40 file(s) +**Locations:** 54 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1974) + - **Line:** 1974 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2031) + - **Line:** 2031 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2179) + - **Line:** 2179 + - **Context:** `export type RemoteSessionStatus = { position: number; duration: number...` +- **File:** [`src/lib/components/player/Controls.svelte`](src/lib/components/player/Controls.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/player/AudioPlayer.svelte`](src/lib/components/player/AudioPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` - **File:** [`src/lib/components/player/MiniPlayer.svelte`](src/lib/components/player/MiniPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/components/player/Controls.svelte`](src/lib/components/player/Controls.svelte#L1) - - **Line:** 1 - - **Context:** `Unknown` - **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/stores/appState.ts`](src/lib/stores/appState.ts#L2) - - **Line:** 2 +- **File:** [`src/lib/components/BottomUi.svelte`](src/lib/components/BottomUi.svelte#L16) + - **Line:** 16 - **Context:** `Unknown` - **File:** [`src/lib/stores/queue.test.ts`](src/lib/stores/queue.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` -- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8) - - **Line:** 8 - - **Context:** `Unknown` -- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L29) - - **Line:** 29 - - **Context:** `Unknown` - **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L106) - - **Line:** 106 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L105) + - **Line:** 105 - **Context:** `Unknown` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L111) - - **Line:** 111 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L110) + - **Line:** 110 - **Context:** `async function next() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L116) - - **Line:** 116 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L115) + - **Line:** 115 - **Context:** `async function previous() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L121) - - **Line:** 121 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L120) + - **Line:** 120 - **Context:** `async function skipTo(index: number) {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L126) - - **Line:** 126 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L125) + - **Line:** 125 - **Context:** `async function toggleShuffle() {` +- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8) + - **Line:** 8 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L31) + - **Line:** 31 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/playerVisibility.test.ts`](src/lib/stores/playerVisibility.test.ts#L9) + - **Line:** 9 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/appState.ts`](src/lib/stores/appState.ts#L2) + - **Line:** 2 + - **Context:** `Unknown` - **File:** [`src/lib/utils/duration.test.ts`](src/lib/utils/duration.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/utils/layoutShell.test.ts`](src/lib/utils/layoutShell.test.ts#L14) + - **Line:** 14 + - **Context:** `Unknown` +- **File:** [`src/lib/utils/layoutShell.ts`](src/lib/utils/layoutShell.ts#L17) + - **Line:** 17 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` - **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) - **Line:** 10 - **Context:** `Unknown` - **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L21) - **Line:** 21 - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L64) - - **Line:** 64 +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L58) + - **Line:** 58 - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L99) - - **Line:** 99 +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L89) + - **Line:** 89 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) - - **Line:** 4 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) - - **Line:** 4 +- **File:** [`src/lib/services/playerEvents.regression.test.ts`](src/lib/services/playerEvents.regression.test.ts#L9) + - **Line:** 9 - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L26) - - **Line:** 26 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L156) + - **Line:** 156 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L160) - - **Line:** 160 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L186) + - **Line:** 186 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L170) - - **Line:** 170 - - **Context:** `function handlePositionUpdate(position: number, duration: number): voi...` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) - - **Line:** 6 +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L20) - - **Line:** 20 +- **File:** [`src/lib/player/adapters/nativeAdapter.ts`](src/lib/player/adapters/nativeAdapter.ts#L16) + - **Line:** 16 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L8) - - **Line:** 8 +- **File:** [`src/lib/player/adapters/rustReportHost.ts`](src/lib/player/adapters/rustReportHost.ts#L13) + - **Line:** 13 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` +- **File:** [`src/lib/player/index.ts`](src/lib/player/index.ts#L15) + - **Line:** 15 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L20) - - **Line:** 20 - - **Context:** `pub enum RepeatMode {` -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L508) - - **Line:** 508 - - **Context:** `pub enum AddPosition {` - **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L42) - **Line:** 42 - **Context:** `Unknown` @@ -1173,32 +1719,80 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src-tauri/src/player/state.rs`](src-tauri/src/player/state.rs#L25) - **Line:** 25 - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L8) + - **Line:** 8 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L20) + - **Line:** 20 + - **Context:** `pub enum RepeatMode {` +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535) + - **Line:** 535 + - **Context:** `pub enum AddPosition {` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) + - **Line:** 6 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L23) + - **Line:** 23 + - **Context:** `Unknown` - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/session.rs`](src-tauri/src/commands/player/session.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) - - **Line:** 1 +- **File:** [`src-tauri/src/commands/conversions.rs`](src-tauri/src/commands/conversions.rs#L3) + - **Line:** 3 - **Context:** `Unknown` ### UR-007 -**Locations:** 6 file(s) +**Locations:** 15 file(s) +- **File:** [`src/routes/library/music/+page.svelte`](src/routes/library/music/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/tv/+page.svelte`](src/routes/library/tv/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/lib/components/library/AlphabetScrollBar.svelte`](src/lib/components/library/AlphabetScrollBar.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/library/GenericMediaListPage.svelte`](src/lib/components/library/GenericMediaListPage.svelte#L1) - **Line:** 1 - **Context:** `Unknown` - **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/services/imageCache.ts`](src/lib/services/imageCache.ts#L2) - - **Line:** 2 +- **File:** [`src/lib/stores/movies.ts`](src/lib/stores/movies.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/tv.ts`](src/lib/stores/tv.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/music.ts`](src/lib/stores/music.ts#L3) + - **Line:** 3 - **Context:** `Unknown` - **File:** [`src/lib/services/imageCache.test.ts`](src/lib/services/imageCache.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/imageCache.ts`](src/lib/services/imageCache.ts#L2) + - **Line:** 2 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/thumbnails.rs`](src-tauri/src/commands/storage/thumbnails.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L4) - **Line:** 4 - **Context:** `Unknown` @@ -1208,51 +1802,60 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### UR-008 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src/lib/components/library/GenericMediaListPage.searchEvent.test.ts`](src/lib/components/library/GenericMediaListPage.searchEvent.test.ts#L12) + - **Line:** 12 + - **Context:** `Unknown` - **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L2) - **Line:** 2 - **Context:** `Unknown` ### UR-009 -**Locations:** 13 file(s) +**Locations:** 15 file(s) -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L6) - - **Line:** 6 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L521) + - **Line:** 521 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L269) - - **Line:** 269 - - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L296) - - **Line:** 296 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L537) + - **Line:** 537 - **Context:** `Unknown` - **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L6) + - **Line:** 6 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L266) + - **Line:** 266 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L293) + - **Line:** 293 + - **Context:** `Unknown` - **File:** [`src/lib/utils/validation.test.ts`](src/lib/utils/validation.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/deviceId.test.ts`](src/lib/services/deviceId.test.ts#L7) + - **Line:** 7 + - **Context:** `Unknown` - **File:** [`src/lib/services/deviceId.ts`](src/lib/services/deviceId.ts#L8) - **Line:** 8 - **Context:** `Unknown` - **File:** [`src/lib/services/deviceId.ts`](src/lib/services/deviceId.ts#L23) - **Line:** 23 - **Context:** `Unknown` -- **File:** [`src/lib/services/deviceId.test.ts`](src/lib/services/deviceId.test.ts#L7) - - **Line:** 7 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - - **Line:** 2 - - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L4) - **Line:** 4 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L24) - **Line:** 24 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L80) - - **Line:** 80 +- **File:** [`src-tauri/src/commands/device.rs`](src-tauri/src/commands/device.rs#L81) + - **Line:** 81 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) + - **Line:** 2 - **Context:** `Unknown` - **File:** [`src-tauri/src/jellyfin/client.rs`](src-tauri/src/jellyfin/client.rs#L1) - **Line:** 1 @@ -1260,8 +1863,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### UR-010 -**Locations:** 12 file(s) +**Locations:** 15 file(s) +- **File:** [`src/lib/components/sessions/SessionCard.svelte`](src/lib/components/sessions/SessionCard.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **File:** [`src/lib/components/sessions/SessionsList.svelte`](src/lib/components/sessions/SessionsList.svelte#L1) - **Line:** 1 - **Context:** `Unknown` @@ -1274,84 +1880,99 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/components/sessions/SessionPickerModal.svelte`](src/lib/components/sessions/SessionPickerModal.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/components/sessions/SessionCard.svelte`](src/lib/components/sessions/SessionCard.svelte#L1) - - **Line:** 1 - - **Context:** `Unknown` - **File:** [`src/lib/stores/sessions.test.ts`](src/lib/stores/sessions.test.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/stores/playbackMode.test.ts`](src/lib/stores/playbackMode.test.ts#L2) - - **Line:** 2 - - **Context:** `Unknown` -- **File:** [`src/lib/stores/playbackMode.ts`](src/lib/stores/playbackMode.ts#L9) - - **Line:** 9 - - **Context:** `Unknown` - **File:** [`src/lib/stores/sessions.ts`](src/lib/stores/sessions.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src/lib/stores/lmsSync.ts`](src/lib/stores/lmsSync.ts#L12) + - **Line:** 12 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/playbackMode.ts`](src/lib/stores/playbackMode.ts#L9) + - **Line:** 9 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/playbackMode.test.ts`](src/lib/stores/playbackMode.test.ts#L2) + - **Line:** 2 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/sessions.rs`](src-tauri/src/commands/sessions.rs#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/playback_mode.rs`](src-tauri/src/commands/playback_mode.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/session_poller/mod.rs`](src-tauri/src/session_poller/mod.rs#L6) - **Line:** 6 - **Context:** `Unknown` ### UR-011 -**Locations:** 8 file(s) +**Locations:** 10 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1956) + - **Line:** 1956 + - **Context:** `Unknown` - **File:** [`src/lib/stores/downloads.test.ts`](src/lib/stores/downloads.test.ts#L2) - **Line:** 2 - **Context:** `Unknown` - **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) - - **Line:** 5 - - **Context:** `Unknown` - **File:** [`src/lib/services/preload.test.ts`](src/lib/services/preload.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L116) - - **Line:** 116 +- **File:** [`src/lib/services/preload.ts`](src/lib/services/preload.ts#L5) + - **Line:** 5 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L117) + - **Line:** 117 - **Context:** `pub enum MediaType {` -- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L133) - - **Line:** 133 +- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L1841) + - **Line:** 1841 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L136) + - **Line:** 136 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/download.rs`](src-tauri/src/commands/download.rs#L1552) - - **Line:** 1552 - - **Context:** `Unknown` ### UR-012 -**Locations:** 8 file(s) +**Locations:** 9 file(s) +- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` - **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L6) - **Line:** 6 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L296) - - **Line:** 296 +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L293) + - **Line:** 293 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L466) - - **Line:** 466 +- **File:** [`src/lib/stores/auth.ts`](src/lib/stores/auth.ts#L445) + - **Line:** 445 - **Context:** `Unknown` -- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4) - - **Line:** 4 +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 - **Context:** `pub fn file_size(&self) -> Option {` - **File:** [`src-tauri/src/credentials.rs`](src-tauri/src/credentials.rs#L10) - **Line:** 10 @@ -1368,9 +1989,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **Line:** 2 - **Context:** `Unknown` +### UR-014 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/playlist.rs`](src-tauri/src/commands/playlist.rs#L4) + - **Line:** 4 + - **Context:** `Unknown` + ### UR-015 -**Locations:** 12 file(s) +**Locations:** 13 file(s) - **File:** [`src/lib/stores/queue.test.ts`](src/lib/stores/queue.test.ts#L4) - **Line:** 4 @@ -1378,36 +2007,39 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L106) - - **Line:** 106 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L105) + - **Line:** 105 - **Context:** `Unknown` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L111) - - **Line:** 111 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L110) + - **Line:** 110 - **Context:** `async function next() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L116) - - **Line:** 116 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L115) + - **Line:** 115 - **Context:** `async function previous() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L121) - - **Line:** 121 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L120) + - **Line:** 120 - **Context:** `async function skipTo(index: number) {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L126) - - **Line:** 126 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L125) + - **Line:** 125 - **Context:** `async function toggleShuffle() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L131) - - **Line:** 131 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L130) + - **Line:** 130 - **Context:** `async function cycleRepeat() {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L136) - - **Line:** 136 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L135) + - **Line:** 135 - **Context:** `async function removeFromQueue(index: number) {` -- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L141) - - **Line:** 141 +- **File:** [`src/lib/stores/queue.ts`](src/lib/stores/queue.ts#L140) + - **Line:** 140 - **Context:** `async function moveInQueue(fromIndex: number, toIndex: number) {` - **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L20) - **Line:** 20 - **Context:** `pub enum RepeatMode {` -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L508) - - **Line:** 508 +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535) + - **Line:** 535 - **Context:** `pub enum AddPosition {` +- **File:** [`src-tauri/src/commands/player/queue.rs`](src-tauri/src/commands/player/queue.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### UR-016 @@ -1416,38 +2048,41 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L62) - **Line:** 62 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L241) - - **Line:** 241 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L251) + - **Line:** 251 - **Context:** `Unknown` ### UR-017 -**Locations:** 8 file(s) +**Locations:** 9 file(s) - **File:** [`src/lib/components/player/MiniPlayer.svelte`](src/lib/components/player/MiniPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/lib/services/favorites.ts`](src/lib/services/favorites.ts#L2) - - **Line:** 2 - - **Context:** `Unknown` - **File:** [`src/lib/services/favorites.test.ts`](src/lib/services/favorites.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` - **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L58) - - **Line:** 58 +- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L64) + - **Line:** 64 - **Context:** `Unknown` -- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L170) - - **Line:** 170 +- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L188) + - **Line:** 188 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) +- **File:** [`src/lib/services/favorites.ts`](src/lib/services/favorites.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/sync.rs`](src-tauri/src/commands/sync.rs#L5) - **Line:** 5 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) + - **Line:** 2 + - **Context:** `Unknown` ### UR-018 @@ -1459,167 +2094,160 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/download.rs`](src-tauri/src/commands/download.rs#L1552) - - **Line:** 1552 +- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L1841) + - **Line:** 1841 - **Context:** `Unknown` ### UR-019 -**Locations:** 10 file(s) +**Locations:** 12 file(s) -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) - - **Line:** 10 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) - - **Line:** 4 +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2031) + - **Line:** 2031 - **Context:** `Unknown` - **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) + - **Line:** 10 + - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L26) - - **Line:** 26 - - **Context:** `Unknown` - **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) - **Line:** 6 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L20) - - **Line:** 20 +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L23) + - **Line:** 23 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 +- **File:** [`src-tauri/src/commands/playback_reporting.rs`](src-tauri/src/commands/playback_reporting.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 - **Context:** `pub fn file_size(&self) -> Option {` ### UR-020 -**Locations:** 2 file(s) +**Locations:** 4 file(s) - **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### UR-021 -**Locations:** 2 file(s) +**Locations:** 5 file(s) - **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player.rs`](src-tauri/src/commands/player.rs#L1) +- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19) + - **Line:** 19 + - **Context:** `Unknown` +- **File:** [`src/lib/player/adapters/types.ts`](src/lib/player/adapters/types.ts#L15) + - **Line:** 15 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/series_prefs.rs`](src-tauri/src/commands/storage/series_prefs.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### UR-023 -**Locations:** 14 file(s) +**Locations:** 19 file(s) +- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/tv/+page.svelte`](src/routes/library/tv/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` - **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#L2031) + - **Line:** 2031 + - **Context:** `Unknown` - **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9) - **Line:** 9 - **Context:** `Unknown` +- **File:** [`src/lib/stores/movies.ts`](src/lib/stores/movies.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/tv.ts`](src/lib/stores/tv.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src/lib/stores/home.ts`](src/lib/stores/home.ts#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src/lib/services/nextEpisodeService.ts`](src/lib/services/nextEpisodeService.ts#L7) - - **Line:** 7 - - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4) - **Line:** 4 - **Context:** `Unknown` +- **File:** [`src/lib/services/nextEpisodeService.ts`](src/lib/services/nextEpisodeService.ts#L11) + - **Line:** 11 + - **Context:** `Unknown` - **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L26) - - **Line:** 26 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L263) - - **Line:** 263 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L264) + - **Line:** 264 - **Context:** `function handleMediaLoaded(duration: number): void {` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L309) - - **Line:** 309 - - **Context:** `function handleSleepTimerChanged(mode: SleepTimerMode, remainingSecond...` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) - - **Line:** 6 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L20) - - **Line:** 20 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L340) + - **Line:** 340 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/autoplay.rs`](src-tauri/src/player/autoplay.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) + - **Line:** 6 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L23) + - **Line:** 23 + - **Context:** `Unknown` - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` -### UR-024 - -**Locations:** 1 file(s) - -- **File:** [`src/lib/stores/home.ts`](src/lib/stores/home.ts#L2) - - **Line:** 2 - - **Context:** `Unknown` - -### UR-025 - -**Locations:** 12 file(s) - -- **File:** [`src/lib/utils/validation.test.ts`](src/lib/utils/validation.test.ts#L4) - - **Line:** 4 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) - - **Line:** 10 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L21) - - **Line:** 21 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L99) - - **Line:** 99 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L136) - - **Line:** 136 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) - - **Line:** 4 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L160) - - **Line:** 160 - - **Context:** `Unknown` -- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L7) - - **Line:** 7 - - **Context:** `Unknown` -- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L58) - - **Line:** 58 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) - - **Line:** 2 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/sync.rs`](src-tauri/src/commands/sync.rs#L5) - - **Line:** 5 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - ### UR-026 -**Locations:** 14 file(s) +**Locations:** 16 file(s) +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2031) + - **Line:** 2031 + - **Context:** `Unknown` +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2221) + - **Line:** 2221 + - **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 - **Context:** `Unknown` @@ -1635,20 +2263,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L26) - - **Line:** 26 - - **Context:** `Unknown` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L263) - - **Line:** 263 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L264) + - **Line:** 264 - **Context:** `function handleMediaLoaded(duration: number): void {` -- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L300) - - **Line:** 300 +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L301) + - **Line:** 301 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) - - **Line:** 6 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L20) - - **Line:** 20 +- **File:** [`src-tauri/src/player/autoplay.rs`](src-tauri/src/player/autoplay.rs#L2) + - **Line:** 2 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/sleep_timer.rs`](src-tauri/src/player/sleep_timer.rs#L4) - **Line:** 4 @@ -1656,12 +2278,18 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src-tauri/src/player/sleep_timer.rs`](src-tauri/src/player/sleep_timer.rs#L81) - **Line:** 81 - **Context:** `pub fn cancel(&mut self) {` -- **File:** [`src-tauri/src/player/autoplay.rs`](src-tauri/src/player/autoplay.rs#L2) - - **Line:** 2 +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L6) + - **Line:** 6 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/events.rs`](src-tauri/src/player/events.rs#L23) + - **Line:** 23 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` ### UR-028 @@ -1698,21 +2326,42 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, ### UR-034 -**Locations:** 3 file(s) +**Locations:** 10 file(s) -- **File:** [`src/lib/components/home/HeroBanner.svelte`](src/lib/components/home/HeroBanner.svelte#L1) +- **File:** [`src/routes/library/music/+page.svelte`](src/routes/library/music/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/routes/library/tv/+page.svelte`](src/routes/library/tv/+page.svelte#L1) - **Line:** 1 - **Context:** `Unknown` - **File:** [`src/lib/components/home/Carousel.svelte`](src/lib/components/home/Carousel.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src/lib/components/home/HeroBanner.svelte`](src/lib/components/home/HeroBanner.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/movies.ts`](src/lib/stores/movies.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/tv.ts`](src/lib/stores/tv.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src/lib/stores/music.ts`](src/lib/stores/music.ts#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src/lib/stores/home.ts`](src/lib/stores/home.ts#L2) - **Line:** 2 - **Context:** `Unknown` +- **File:** [`src/lib/utils/heroMix.ts`](src/lib/utils/heroMix.ts#L8) + - **Line:** 8 + - **Context:** `Unknown` ### UR-035 -**Locations:** 3 file(s) +**Locations:** 4 file(s) - **File:** [`src/lib/components/library/CastSection.svelte`](src/lib/components/library/CastSection.svelte#L1) - **Line:** 1 @@ -1720,17 +2369,282 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **File:** [`src/lib/components/library/CrewLinks.svelte`](src/lib/components/library/CrewLinks.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/people.rs`](src-tauri/src/commands/storage/people.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L4) - **Line:** 4 - **Context:** `Unknown` -### UR-036 +### UR-040 + +**Locations:** 18 file(s) + +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L38) + - **Line:** 38 + - **Context:** `Unknown` +- **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#L1246) + - **Line:** 1246 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L178) + - **Line:** 178 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L394) + - **Line:** 394 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L411) + - **Line:** 411 + - **Context:** `Unknown` +- **File:** [`src/lib/components/player/backgroundAudioHandoff.ts`](src/lib/components/player/backgroundAudioHandoff.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1) + - **Line:** 1 + - **Context:** `Unknown` +- **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10) + - **Line:** 10 + - **Context:** `Unknown` +- **File:** [`src/lib/utils/backgroundAudio.ts`](src/lib/utils/backgroundAudio.ts#L4) + - **Line:** 4 + - **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#L543) + - **Line:** 543 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L623) + - **Line:** 623 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L444) + - **Line:** 444 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/hybrid.rs`](src-tauri/src/repository/hybrid.rs#L80) + - **Line:** 80 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L435) + - **Line:** 435 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2084) + - **Line:** 2084 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2125) + - **Line:** 2125 + - **Context:** `Unknown` + +### IT-013 + +**Locations:** 4 file(s) + +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L38) + - **Line:** 38 + - **Context:** `Unknown` +- **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#L543) + - **Line:** 543 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L623) + - **Line:** 623 + - **Context:** `Unknown` + +### UT-010 + +**Locations:** 1 file(s) + +- **File:** [`src/lib/stores/downloads.test.ts`](src/lib/stores/downloads.test.ts#L2) + - **Line:** 2 + - **Context:** `Unknown` + +### UT-017 **Locations:** 2 file(s) +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-018 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-019 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-021 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-022 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-023 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-024 + +**Locations:** 1 file(s) + +- **File:** [`src/lib/stores/downloads.test.ts`](src/lib/stores/downloads.test.ts#L2) + - **Line:** 2 + - **Context:** `Unknown` + +### UT-060 + +**Locations:** 2 file(s) + +- **File:** [`src/lib/components/player/backgroundAudioHandoff.ts`](src/lib/components/player/backgroundAudioHandoff.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10) + - **Line:** 10 + - **Context:** `Unknown` + +### UT-061 + +**Locations:** 9 file(s) + +- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L38) + - **Line:** 38 + - **Context:** `Unknown` +- **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#L1246) + - **Line:** 1246 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L394) + - **Line:** 394 + - **Context:** `Unknown` +- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L411) + - **Line:** 411 + - **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#L543) + - **Line:** 543 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L623) + - **Line:** 623 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L444) + - **Line:** 444 + - **Context:** `Unknown` + +### UR-024 + +**Locations:** 2 file(s) + +- **File:** [`src/lib/stores/home.ts`](src/lib/stores/home.ts#L2) + - **Line:** 2 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### UR-025 + +**Locations:** 14 file(s) + +- **File:** [`src/lib/utils/validation.test.ts`](src/lib/utils/validation.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.test.ts`](src/lib/services/playbackReporting.test.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L10) + - **Line:** 10 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L21) + - **Line:** 21 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L89) + - **Line:** 89 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playbackReporting.ts`](src/lib/services/playbackReporting.ts#L122) + - **Line:** 122 + - **Context:** `Unknown` +- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L7) + - **Line:** 7 + - **Context:** `Unknown` +- **File:** [`src/lib/services/syncService.ts`](src/lib/services/syncService.ts#L64) + - **Line:** 64 + - **Context:** `Unknown` +- **File:** [`src/lib/services/playerEvents.ts`](src/lib/services/playerEvents.ts#L156) + - **Line:** 156 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/sync.rs`](src-tauri/src/commands/sync.rs#L5) + - **Line:** 5 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/mod.rs`](src-tauri/src/commands/mod.rs#L2) + - **Line:** 2 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/playback_reporting.rs`](src-tauri/src/commands/playback_reporting.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UR-036 + +**Locations:** 3 file(s) + - **File:** [`src/lib/components/library/PersonDetailView.svelte`](src/lib/components/library/PersonDetailView.svelte#L1) - **Line:** 1 - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/storage/people.rs`](src-tauri/src/commands/storage/people.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L4) - **Line:** 4 - **Context:** `Unknown` @@ -1743,6 +2657,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **Line:** 1 - **Context:** `Unknown` +### UR-041 + +**Locations:** 1 file(s) + +- **File:** [`src/lib/utils/pictureInPicture.ts`](src/lib/utils/pictureInPicture.ts#L4) + - **Line:** 4 + - **Context:** `Unknown` + ### IT-003 **Locations:** 1 file(s) @@ -1763,34 +2685,26 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L508) - - **Line:** 508 +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535) + - **Line:** 535 - **Context:** `pub enum AddPosition {` ### UT-004 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L508) - - **Line:** 508 +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535) + - **Line:** 535 - **Context:** `pub enum AddPosition {` ### UT-005 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L508) - - **Line:** 508 +- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535) + - **Line:** 535 - **Context:** `pub enum AddPosition {` -### UT-010 - -**Locations:** 1 file(s) - -- **File:** [`src/lib/stores/downloads.test.ts`](src/lib/stores/downloads.test.ts#L2) - - **Line:** 2 - - **Context:** `Unknown` - ### UT-012 **Locations:** 1 file(s) @@ -1799,248 +2713,253 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-017, - **Line:** 81 - **Context:** `pub fn cancel(&mut self) {` -### UT-014 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-015 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-016 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-017 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-018 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-019 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-020 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-021 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-022 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-023 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - -### UT-024 - -**Locations:** 1 file(s) - -- **File:** [`src/lib/stores/downloads.test.ts`](src/lib/stores/downloads.test.ts#L2) - - **Line:** 2 - - **Context:** `Unknown` - -### UT-025 - -**Locations:** 2 file(s) - -- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L288) - - **Line:** 288 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L159) - - **Line:** 159 - - **Context:** `pub fn file_size(&self) -> Option {` - ### UT-026 **Locations:** 2 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L241) - - **Line:** 241 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L251) + - **Line:** 251 - **Context:** `Unknown` ### UT-027 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` ### UT-028 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` ### UT-029 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` ### UT-030 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` ### UT-031 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` ### UT-032 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` ### UT-033 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L235) - - **Line:** 235 +- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245) + - **Line:** 245 - **Context:** `Unknown` -### UT-042 +### UR-022 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/commands/download.rs`](src-tauri/src/commands/download.rs#L1552) - - **Line:** 1552 - - **Context:** `Unknown` - -### UT-043 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/commands/download.rs`](src-tauri/src/commands/download.rs#L1552) - - **Line:** 1552 - - **Context:** `Unknown` - -### UT-044 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L133) - - **Line:** 133 +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 - **Context:** `Unknown` ### UR-031 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### UR-032 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` ### UR-033 -**Locations:** 1 file(s) +**Locations:** 2 file(s) +- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1) - **Line:** 1 - **Context:** `Unknown` +### UR-044 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/download/pinning.rs`](src-tauri/src/commands/download/pinning.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### UR-045 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/download/smart_cache.rs`](src-tauri/src/commands/download/smart_cache.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### UR-046 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/player/remote.rs`](src-tauri/src/commands/player/remote.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### UT-042 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L1841) + - **Line:** 1841 + - **Context:** `Unknown` + +### UT-043 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L1841) + - **Line:** 1841 + - **Context:** `Unknown` + +### UR-047 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/storage/mod.rs`](src-tauri/src/commands/storage/mod.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### UT-044 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L136) + - **Line:** 136 + - **Context:** `Unknown` + +### UR-042 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/auth.rs`](src-tauri/src/commands/auth.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### UR-043 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/commands/connectivity.rs`](src-tauri/src/commands/connectivity.rs#L3) + - **Line:** 3 + - **Context:** `Unknown` + +### UT-014 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-015 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-016 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-020 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-025 + +**Locations:** 2 file(s) + +- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311) + - **Line:** 311 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157) + - **Line:** 157 + - **Context:** `pub fn file_size(&self) -> Option {` + +### UT-059 + +**Locations:** 3 file(s) + +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L435) + - **Line:** 435 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2084) + - **Line:** 2084 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2125) + - **Line:** 2125 + - **Context:** `Unknown` + diff --git a/src-tauri/android/src/main/java/com/dtourolle/jellytau/MainActivity.kt b/src-tauri/android/src/main/java/com/dtourolle/jellytau/MainActivity.kt index f263ff83..5c566156 100644 --- a/src-tauri/android/src/main/java/com/dtourolle/jellytau/MainActivity.kt +++ b/src-tauri/android/src/main/java/com/dtourolle/jellytau/MainActivity.kt @@ -23,13 +23,33 @@ class MainActivity : TauriActivity() { private val audioManager by lazy { getSystemService(Context.AUDIO_SERVICE) as AudioManager } /** - * Whether backgrounding the app during video should auto-enter PiP. - * The frontend clears this when video isn't the active local surface - * (e.g. remote/cast playback) via AndroidPictureInPicture.setAutoEnterEnabled. + * Coarse override for whether backgrounding the app should auto-enter PiP. + * + * This is NOT what excludes audio/browsing/cast from PiP — that is the + * PictureInPictureManager.canEnterPip guard, which requires a local video + * surface to be actively rendering and is re-checked in onUserLeaveHint. This + * flag is only toggled by the background-audio feature (via + * AndroidPictureInPicture.setAutoEnterEnabled) so background-audio mode and + * auto-PiP stay mutually exclusive. */ @Volatile private var autoEnterPipEnabled = true + /** + * Whether the user armed background-audio mode on the current video (UR-040). + * When true, leaving the app hands audio off to the native ExoPlayer audio + * service (frontend-driven) instead of entering PiP, and video decode stops. + * The frontend sets this via AndroidBackgroundAudio.setEnabled. + */ + @Volatile + private var backgroundAudioEnabled = false + + /** + * The WebView carrying the Svelte UI, cached once found so lifecycle overrides + * can dispatch DOM events into it (native → frontend signalling). + */ + private var mediaWebView: WebView? = null + override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) @@ -49,15 +69,62 @@ class MainActivity : TauriActivity() { * Called when the user leaves the app via Home or the gesture equivalent * (but NOT via Back). This is the standard hook for auto-entering PiP so * video keeps playing in a floating window instead of being backgrounded. + * + * TRACES: UR-041 | IR-026 | DR-053 */ override fun onUserLeaveHint() { super.onUserLeaveHint() - if (autoEnterPipEnabled && PictureInPictureManager.canEnterPip(this)) { + // Never enter PiP while background-audio mode is armed — the two are mutually + // exclusive (the handoff runs from onStop instead). + if (autoEnterPipEnabled && !backgroundAudioEnabled && + PictureInPictureManager.canEnterPip(this)) { android.util.Log.d("MainActivity", "User leaving with video active - entering PiP") PictureInPictureManager.enterPip(this) } } + /** + * The app is no longer visible (Home, app switch, or screen lock). When + * background-audio mode is armed, tell the frontend to hand video playback off + * to the native audio service. onStop (rather than onUserLeaveHint) is used + * because it fires on screen-lock too, which is the primary use case (UR-040). + * + * TRACES: UR-040 | IR-025 + */ + override fun onStop() { + super.onStop() + if (backgroundAudioEnabled) { + dispatchWebEvent("jellytau-background") + } + } + + /** The app is visible again — tell the frontend to resume WebView video. */ + override fun onStart() { + super.onStart() + if (backgroundAudioEnabled) { + dispatchWebEvent("jellytau-foreground") + } + } + + /** + * Dispatch a DOM CustomEvent into the WebView (native → frontend). Mirrors the + * evaluateJavascript pattern already used to unmute video elements. Posted to + * the WebView thread; safe no-op if the WebView isn't found yet. + */ + private fun dispatchWebEvent(name: String) { + val webView = mediaWebView ?: run { + android.util.Log.w("MainActivity", "dispatchWebEvent('$name'): no WebView") + return + } + webView.post { + webView.evaluateJavascript( + "window.dispatchEvent(new CustomEvent('$name'));", + null + ) + android.util.Log.d("MainActivity", "Dispatched web event: $name") + } + } + override fun onPictureInPictureModeChanged( isInPictureInPictureMode: Boolean, newConfig: android.content.res.Configuration @@ -85,6 +152,7 @@ class MainActivity : TauriActivity() { } android.util.Log.d("MainActivity", "WebView found! Configuring settings...") + mediaWebView = webView // Add JavaScript interface for audio focus control webView.addJavascriptInterface(object : Any() { @@ -129,6 +197,23 @@ class MainActivity : TauriActivity() { }, "AndroidPictureInPicture") android.util.Log.d("MainActivity", "JavaScript interface 'AndroidPictureInPicture' added") + // Add JavaScript interface for background-audio mode (UR-040). The frontend + // arms/disarms it via the player toggle; the Activity uses the flag in its + // lifecycle overrides to decide between the audio handoff and PiP. + webView.addJavascriptInterface(object : Any() { + /** Frontend arms/disarms background-audio mode for the current video. */ + @JavascriptInterface + fun setEnabled(enabled: Boolean) { + backgroundAudioEnabled = enabled + android.util.Log.d("MainActivity", "backgroundAudioEnabled = $enabled") + } + + /** Whether background audio is available on this device (needs PiP-era APIs unnecessary; audio service always present on Android). */ + @JavascriptInterface + fun isSupported(): Boolean = true + }, "AndroidBackgroundAudio") + android.util.Log.d("MainActivity", "JavaScript interface 'AndroidBackgroundAudio' added") + // Set WebChromeClient to handle video playback and audio focus webView.webChromeClient = object : WebChromeClient() { override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { @@ -152,7 +237,6 @@ class MainActivity : TauriActivity() { domStorageEnabled = true allowFileAccess = true allowContentAccess = true - setRenderPriority(WebSettings.RenderPriority.HIGH) mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW android.util.Log.d("MainActivity", "WebView fully configured for media playback") diff --git a/src-tauri/android/src/main/java/com/dtourolle/jellytau/PictureInPictureManager.kt b/src-tauri/android/src/main/java/com/dtourolle/jellytau/PictureInPictureManager.kt index 731a4b2c..c2a61edc 100644 --- a/src-tauri/android/src/main/java/com/dtourolle/jellytau/PictureInPictureManager.kt +++ b/src-tauri/android/src/main/java/com/dtourolle/jellytau/PictureInPictureManager.kt @@ -18,6 +18,8 @@ import com.dtourolle.jellytau.player.JellyTauPlayer /** * Drives Android picture-in-picture for native (ExoPlayer) video playback. * + * TRACES: UR-041 | IR-026 | DR-053 + * * PiP shrinks the whole Activity into a floating window, so the only thing that * should remain visible is the video SurfaceView that [VideoOverlayManager] * attached at the bottom of the z-order. The WebView carrying the Svelte UI is diff --git a/src-tauri/src/auth/mod.rs b/src-tauri/src/auth/mod.rs index f14dbd0d..e4bc17ca 100644 --- a/src-tauri/src/auth/mod.rs +++ b/src-tauri/src/auth/mod.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; -use crate::jellyfin::http_client::HttpClient; use crate::connectivity::ConnectivityMonitor; +use crate::jellyfin::http_client::HttpClient; pub use session_verifier::SessionVerifier; @@ -99,7 +99,10 @@ impl AuthManager { } /// Set the connectivity monitor (for marking server reachability) - pub fn set_connectivity_monitor(&mut self, monitor: Arc>) { + pub fn set_connectivity_monitor( + &mut self, + monitor: Arc>, + ) { self.connectivity_monitor = Some(monitor); } @@ -133,9 +136,17 @@ impl AuthManager { log::info!("[AuthManager] Connecting to server: {}", normalized_url); - match self.http_client.get_json_fast::(&endpoint).await { + match self + .http_client + .get_json_fast::(&endpoint) + .await + { Ok(info) => { - log::info!("[AuthManager] Connected to server: {} ({})", info.server_name, info.version); + log::info!( + "[AuthManager] Connected to server: {} ({})", + info.server_name, + info.version + ); // Mark server as reachable if let Some(monitor) = &self.connectivity_monitor { @@ -181,7 +192,10 @@ impl AuthManager { let auth_header = HttpClient::build_auth_header(None, device_id); // Build request manually for custom headers - let request = self.http_client.client.post(&endpoint) + let request = self + .http_client + .client + .post(&endpoint) .header("Content-Type", "application/json") .header("X-Emby-Authorization", auth_header) .json(&serde_json::json!({ @@ -192,19 +206,31 @@ impl AuthManager { .map_err(|e| format!("Failed to build request: {}", e))?; // Use retry logic - let response = self.http_client.request_with_retry(request).await + let response = self + .http_client + .request_with_retry(request) + .await .map_err(|e| format!("Login request failed: {}", e))?; if !response.status().is_success() { let status = response.status(); - let error_text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string()); + let error_text = response + .text() + .await + .unwrap_or_else(|_| "Unknown error".to_string()); return Err(format!("Login failed: HTTP {}: {}", status, error_text)); } - let auth_response: AuthenticateByNameResponse = response.json().await + let auth_response: AuthenticateByNameResponse = response + .json() + .await .map_err(|e| format!("Failed to parse login response: {}", e))?; - log::info!("[AuthManager] Login successful for user: {} ({})", auth_response.user.name, auth_response.user.id); + log::info!( + "[AuthManager] Login successful for user: {} ({})", + auth_response.user.name, + auth_response.user.id + ); // Mark server as reachable if let Some(monitor) = &self.connectivity_monitor { @@ -243,13 +269,19 @@ impl AuthManager { let auth_header = HttpClient::build_auth_header(Some(access_token), device_id); // Build request manually for custom headers - let request = self.http_client.client.get(&endpoint) + let request = self + .http_client + .client + .get(&endpoint) .header("X-Emby-Authorization", auth_header) .build() .map_err(|e| format!("Failed to build request: {}", e))?; // Use retry logic - let response = self.http_client.request_with_retry(request).await + let response = self + .http_client + .request_with_retry(request) + .await .map_err(|e| { log::warn!("[AuthManager] Session verification failed: {}", e); format!("Session verification failed: {}", e) @@ -257,24 +289,34 @@ impl AuthManager { if !response.status().is_success() { let status = response.status(); - let error_text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string()); + let error_text = response + .text() + .await + .unwrap_or_else(|_| "Unknown error".to_string()); // Mark server as unreachable for auth errors if status.as_u16() == 401 || status.as_u16() == 403 { log::warn!("[AuthManager] Session invalid: HTTP {}", status); if let Some(monitor) = &self.connectivity_monitor { let monitor = monitor.lock().await; - monitor.mark_unreachable(Some(format!("Authentication failed: {}", status))).await; + monitor + .mark_unreachable(Some(format!("Authentication failed: {}", status))) + .await; } } return Err(format!("HTTP {}: {}", status, error_text)); } - let user_response: JellyfinUser = response.json().await + let user_response: JellyfinUser = response + .json() + .await .map_err(|e| format!("Failed to parse user response: {}", e))?; - log::info!("[AuthManager] Session verified successfully for: {}", user_response.name); + log::info!( + "[AuthManager] Session verified successfully for: {}", + user_response.name + ); // Mark server as reachable if let Some(monitor) = &self.connectivity_monitor { @@ -306,7 +348,10 @@ impl AuthManager { let auth_header = HttpClient::build_auth_header(Some(access_token), device_id); // Build request - let request = self.http_client.client.post(&endpoint) + let request = self + .http_client + .client + .post(&endpoint) .header("X-Emby-Authorization", auth_header) .build() .map_err(|e| format!("Failed to build request: {}", e))?; diff --git a/src-tauri/src/auth/session_verifier.rs b/src-tauri/src/auth/session_verifier.rs index 429c82bb..45ea8276 100644 --- a/src-tauri/src/auth/session_verifier.rs +++ b/src-tauri/src/auth/session_verifier.rs @@ -1,8 +1,8 @@ +use serde::Serialize; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tauri::{AppHandle, Emitter}; -use serde::Serialize; use super::{AuthManager, User}; @@ -65,7 +65,10 @@ impl SessionVerifier { let session = auth_manager.get_session().await; if let Some(session) = session { - log::debug!("[SessionVerifier] Verifying session for: {}", session.username); + log::debug!( + "[SessionVerifier] Verifying session for: {}", + session.username + ); // Verify the session match auth_manager @@ -113,7 +116,10 @@ impl SessionVerifier { reason: "Session expired".to_string(), }; if let Err(e) = app.emit("auth:needs-reauth", event) { - log::error!("[SessionVerifier] Failed to emit event: {}", e); + log::error!( + "[SessionVerifier] Failed to emit event: {}", + e + ); } } @@ -131,12 +137,18 @@ impl SessionVerifier { message: e.clone(), }; if let Err(e) = app.emit("auth:network-error", event) { - log::error!("[SessionVerifier] Failed to emit event: {}", e); + log::error!( + "[SessionVerifier] Failed to emit event: {}", + e + ); } } } else { // Unknown error - log but don't invalidate - log::error!("[SessionVerifier] Unknown error during verification: {}", e); + log::error!( + "[SessionVerifier] Unknown error during verification: {}", + e + ); } } } diff --git a/src-tauri/src/commands/auth.rs b/src-tauri/src/commands/auth.rs index 20f2c348..cba5215e 100644 --- a/src-tauri/src/commands/auth.rs +++ b/src-tauri/src/commands/auth.rs @@ -1,7 +1,11 @@ +//! Authentication and session-lifecycle commands. +//! +//! TRACES: UR-042 | IR-009, IR-014, JA-002 | DR-054 + use std::sync::Arc; use tauri::State; -use crate::auth::{AuthManager, SessionVerifier, ServerInfo, AuthResult, Session}; +use crate::auth::{AuthManager, AuthResult, ServerInfo, Session, SessionVerifier}; /// Wrapper for AuthManager to manage in Tauri state pub struct AuthManagerWrapper(pub Arc); @@ -27,17 +31,18 @@ pub async fn auth_initialize( log::info!("[AuthManager] Restoring session from storage..."); // Use the existing storage_get_active_session function - let active_session = match crate::commands::storage::storage_get_active_session(database, credentials).await { - Ok(Some(session)) => session, - Ok(None) => { - log::info!("[AuthManager] No active session in storage"); - return Ok(None); - } - Err(e) => { - log::error!("[AuthManager] Failed to get active session: {}", e); - return Err(e); - } - }; + let active_session = + match crate::commands::storage::storage_get_active_session(database, credentials).await { + Ok(Some(session)) => session, + Ok(None) => { + log::info!("[AuthManager] No active session in storage"); + return Ok(None); + } + Err(e) => { + log::error!("[AuthManager] Failed to get active session: {}", e); + return Err(e); + } + }; // Create session object from active session with normalized URL let normalized_url = crate::auth::AuthManager::normalize_url(&active_session.server_url)?; @@ -56,7 +61,11 @@ pub async fn auth_initialize( // Store in AuthManager auth_manager.0.set_session(Some(session.clone())).await; - log::info!("[AuthManager] Session restored for user: {} with normalized URL: {}", session.username, session.server_url); + log::info!( + "[AuthManager] Session restored for user: {} with normalized URL: {}", + session.username, + session.server_url + ); Ok(Some(session)) } @@ -80,7 +89,10 @@ pub async fn auth_login( device_id: String, auth_manager: State<'_, AuthManagerWrapper>, ) -> Result { - let result = auth_manager.0.login(&server_url, &username, &password, &device_id).await?; + let result = auth_manager + .0 + .login(&server_url, &username, &password, &device_id) + .await?; // Create session from auth result with normalized URL let normalized_url = crate::auth::AuthManager::normalize_url(&server_url)?; @@ -111,7 +123,11 @@ pub async fn auth_verify_session( device_id: String, auth_manager: State<'_, AuthManagerWrapper>, ) -> Result { - match auth_manager.0.verify_session(&server_url, &user_id, &access_token, &device_id).await { + match auth_manager + .0 + .verify_session(&server_url, &user_id, &access_token, &device_id) + .await + { Ok(_) => Ok(true), Err(e) => { log::warn!("[AuthCommands] Session verification failed: {}", e); @@ -138,7 +154,10 @@ pub async fn auth_logout( drop(verifier_guard); // Call Jellyfin logout endpoint - auth_manager.0.logout(&server_url, &access_token, &device_id).await?; + auth_manager + .0 + .logout(&server_url, &access_token, &device_id) + .await?; // Clear session auth_manager.0.set_session(None).await; @@ -228,11 +247,22 @@ pub async fn auth_reauthenticate( auth_manager: State<'_, AuthManagerWrapper>, ) -> Result { // Get current session to extract server_url and username - let session = auth_manager.0.get_session().await + let session = auth_manager + .0 + .get_session() + .await .ok_or_else(|| "No active session to re-authenticate".to_string())?; // Re-login with stored credentials - let result = auth_manager.0.login(&session.server_url, &session.username, &password, &device_id).await?; + let result = auth_manager + .0 + .login( + &session.server_url, + &session.username, + &password, + &device_id, + ) + .await?; // Update session with new token let updated_session = Session { diff --git a/src-tauri/src/commands/catalog.rs b/src-tauri/src/commands/catalog.rs index 679302bb..664dbc35 100644 --- a/src-tauri/src/commands/catalog.rs +++ b/src-tauri/src/commands/catalog.rs @@ -1,5 +1,7 @@ //! Tauri commands for the offline "browse & queue" feature. //! +//! TRACES: UR-002, UR-007, UR-024 | JA-004, JA-016 | DR-012, DR-027 +//! //! Two backend pieces support browsing the full server catalog while offline //! and queueing downloads that fire on reconnect: //! @@ -17,8 +19,8 @@ use std::sync::Arc; use log::{info, warn}; use tauri::State; -use crate::commands::repository::RepositoryManagerWrapper; use crate::commands::download::{pump_download_queue, DownloadManagerWrapper}; +use crate::commands::repository::RepositoryManagerWrapper; use crate::commands::storage::DatabaseWrapper; use crate::repository::types::GetItemsOptions; use crate::storage::db_service::{DatabaseService, Query, QueryParam}; @@ -79,7 +81,10 @@ pub async fn sync_full_catalog( }; let libraries = repo.get_libraries().await.map_err(|e| e.to_string())?; - info!("[Catalog] Full sync starting across {} libraries", libraries.len()); + info!( + "[Catalog] Full sync starting across {} libraries", + libraries.len() + ); let include_types: Vec = CATALOG_ITEM_TYPES.iter().map(|s| s.to_string()).collect(); @@ -104,7 +109,10 @@ pub async fn sync_full_catalog( items_cached += items.len(); } Err(e) => { - warn!("[Catalog] Failed to sync library '{}': {:?}", library.name, e); + warn!( + "[Catalog] Failed to sync library '{}': {:?}", + library.name, e + ); libraries_failed += 1; } } @@ -208,10 +216,16 @@ where .map_err(|e| e.to_string())?; if rows.is_empty() { - return Ok(ResumeQueuedResult { resolved: 0, failed: 0 }); + return Ok(ResumeQueuedResult { + resolved: 0, + failed: 0, + }); } - info!("[Catalog] Resolving {} offline-queued downloads on reconnect", rows.len()); + info!( + "[Catalog] Resolving {} offline-queued downloads on reconnect", + rows.len() + ); let mut resolved = 0usize; let mut failed = 0usize; @@ -240,7 +254,10 @@ where Ok(n) if n > 0 => resolved += 1, Ok(_) => {} // already resolved by someone else; not a failure Err(e) => { - warn!("[Catalog] Failed to persist URL for download {}: {}", download_id, e); + warn!( + "[Catalog] Failed to persist URL for download {}: {}", + download_id, e + ); failed += 1; } } @@ -309,17 +326,22 @@ pub async fn resume_queued_downloads( let repo = Arc::clone(&repo_for_resolve); async move { if media_type == "video" { - Some(::get_video_download_url( - repo.as_ref(), - &item_id, - &quality, - None, - )) + Some( + ::get_video_download_url( + repo.as_ref(), + &item_id, + &quality, + None, + ), + ) } else { match repo.get_audio_stream_url(&item_id).await { Ok(url) => Some(url), Err(e) => { - warn!("[Catalog] Failed to resolve audio URL for {}: {:?}", item_id, e); + warn!( + "[Catalog] Failed to resolve audio URL for {}: {:?}", + item_id, e + ); None } } @@ -341,7 +363,10 @@ pub async fn resume_queued_downloads( pump_download_queue(app, db_service, active_downloads).await; } - info!("[Catalog] Resume complete: {} resolved, {} failed", resolved, failed); + info!( + "[Catalog] Resume complete: {} resolved, {} failed", + resolved, failed + ); Ok(ResumeQueuedResult { resolved, failed }) } @@ -384,14 +409,21 @@ mod tests { vec![ QueryParam::String(item_id.to_string()), QueryParam::String(status.to_string()), - stream_url.map(|s| QueryParam::String(s.to_string())).unwrap_or(QueryParam::Null), - media_type.map(|s| QueryParam::String(s.to_string())).unwrap_or(QueryParam::Null), + stream_url + .map(|s| QueryParam::String(s.to_string())) + .unwrap_or(QueryParam::Null), + media_type + .map(|s| QueryParam::String(s.to_string())) + .unwrap_or(QueryParam::Null), ], ); db.execute(q).await.unwrap(); } - async fn get_row(db: &Arc, item_id: &str) -> (String, Option, Option) { + async fn get_row( + db: &Arc, + item_id: &str, + ) -> (String, Option, Option) { let q = Query::with_params( "SELECT status, stream_url, target_dir FROM downloads WHERE item_id = ?", vec![QueryParam::String(item_id.to_string())], @@ -411,11 +443,12 @@ mod tests { // A completed row: irrelevant. insert_download(&db, "done", "completed", Some("http://done/url"), None).await; - let out = resolve_pending_download_urls(&db, "/data/downloads", |item_id, _mt, _q| async move { - Some(format!("http://resolved/{item_id}")) - }) - .await - .unwrap(); + let out = + resolve_pending_download_urls(&db, "/data/downloads", |item_id, _mt, _q| async move { + Some(format!("http://resolved/{item_id}")) + }) + .await + .unwrap(); assert_eq!(out.resolved, 1); assert_eq!(out.failed, 0); @@ -455,12 +488,13 @@ mod tests { let db = test_db(); insert_download(&db, "vid-1", "pending", None, Some("video")).await; - let out = resolve_pending_download_urls(&db, "/data", |item_id, media_type, _q| async move { - assert_eq!(media_type, "video"); - Some(format!("http://transcode/{item_id}")) - }) - .await - .unwrap(); + let out = + resolve_pending_download_urls(&db, "/data", |item_id, media_type, _q| async move { + assert_eq!(media_type, "video"); + Some(format!("http://transcode/{item_id}")) + }) + .await + .unwrap(); assert_eq!(out.resolved, 1); let (_s, url, _t) = get_row(&db, "vid-1").await; diff --git a/src-tauri/src/commands/connectivity.rs b/src-tauri/src/commands/connectivity.rs index c6ba09b9..ee20dad1 100644 --- a/src-tauri/src/commands/connectivity.rs +++ b/src-tauri/src/commands/connectivity.rs @@ -1,6 +1,10 @@ +//! Server-reachability / connectivity commands. +//! +//! TRACES: UR-043 | IR-027 | DR-055 + +use crate::connectivity::{ConnectivityMonitor, ConnectivityStatus}; use std::sync::Arc; use tauri::State; -use crate::connectivity::{ConnectivityMonitor, ConnectivityStatus}; /// Wrapper for ConnectivityMonitor managed state pub struct ConnectivityMonitorWrapper(pub Arc>); diff --git a/src-tauri/src/commands/conversions.rs b/src-tauri/src/commands/conversions.rs index 4482c80f..52ab85c6 100644 --- a/src-tauri/src/commands/conversions.rs +++ b/src-tauri/src/commands/conversions.rs @@ -1,11 +1,12 @@ //! Tauri commands for unit conversions and formatting //! +//! TRACES: UR-005 | DR-009 +//! //! These commands expose conversion utilities to the frontend, //! allowing centralized conversion logic in Rust. use crate::utils::conversions::{ - format_time, format_time_long, calculate_progress, - ticks_to_seconds, percent_to_volume, + calculate_progress, format_time, format_time_long, percent_to_volume, ticks_to_seconds, }; /// Format time in seconds to MM:SS display string diff --git a/src-tauri/src/commands/device.rs b/src-tauri/src/commands/device.rs index f2b0b500..edd76ef7 100644 --- a/src-tauri/src/commands/device.rs +++ b/src-tauri/src/commands/device.rs @@ -81,7 +81,10 @@ pub async fn device_get_id(db: State<'_, DatabaseWrapper>) -> Result) -> Result<(), String> { +pub async fn device_set_id( + device_id: String, + db: State<'_, DatabaseWrapper>, +) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) diff --git a/src-tauri/src/commands/download/mod.rs b/src-tauri/src/commands/download/mod.rs index 75512d91..416c6578 100644 --- a/src-tauri/src/commands/download/mod.rs +++ b/src-tauri/src/commands/download/mod.rs @@ -2,14 +2,14 @@ #[cfg(test)] use crate::utils::lock::MutexSafe; +use log::{debug, error, info, warn}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use tauri::{Manager, State}; -use log::{debug, error, info, warn}; +use super::{DatabaseWrapper, SmartCacheWrapper}; use crate::download::{DownloadInfo, DownloadManager}; use crate::storage::db_service::{DatabaseService, Query, QueryParam}; -use super::{DatabaseWrapper, SmartCacheWrapper}; // Cohesive command clusters in their own submodules, re-exported so the command // names remain at `commands::download::*` (invoke_handler unchanged). @@ -111,7 +111,13 @@ pub async fn download_item_and_start( request: DownloadItemAndStartRequest, ) -> Result { let DownloadItemAndStartRequest { - item_id, user_id, stream_url, target_dir, item_name, artist_name, album_name, + item_id, + user_id, + stream_url, + target_dir, + item_name, + artist_name, + album_name, } = request; // Sanitize filename let safe_name = sanitize_filename(item_name.as_deref().unwrap_or(&item_id)); @@ -132,7 +138,8 @@ pub async fn download_item_and_start( album_name, expected_size: None, }, - ).await?; + ) + .await?; // Start the download immediately start_download( @@ -142,7 +149,8 @@ pub async fn download_item_and_start( download_id, stream_url, target_dir, - ).await?; + ) + .await?; Ok(download_id) } @@ -156,7 +164,15 @@ pub async fn download_item( request: DownloadItemRequest, ) -> Result { let DownloadItemRequest { - item_id, user_id, file_path, mime_type, priority, item_name, artist_name, album_name, expected_size, + item_id, + user_id, + file_path, + mime_type, + priority, + item_name, + artist_name, + album_name, + expected_size, } = request; let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -172,18 +188,24 @@ pub async fn download_item( }; // Check if we have space - let can_download = cache_arc.can_download_async(&db_service, &user_id, size as u64).await; + let can_download = cache_arc + .can_download_async(&db_service, &user_id, size as u64) + .await; if !can_download { warn!("Storage limit reached. Attempting to free space..."); // Try to evict LRU items to make space - match cache_arc.evict_lru_async(&db_service, &user_id, size as u64).await { + match cache_arc + .evict_lru_async(&db_service, &user_id, size as u64) + .await + { Ok(freed) if freed > 0 => { info!("Freed {} bytes, proceeding with download", freed); } Ok(_) => { - let storage_limit = cache_arc.get_config().map(|c| c.storage_limit).unwrap_or(0); + let storage_limit = + cache_arc.get_config().map(|c| c.storage_limit).unwrap_or(0); return Err(format!( "Storage limit reached ({} bytes). Unable to free enough space.", storage_limit @@ -220,7 +242,10 @@ pub async fn download_item( ], ); - db_service.execute(insert_query).await.map_err(|e| e.to_string())?; + db_service + .execute(insert_query) + .await + .map_err(|e| e.to_string())?; // Query for the download ID by unique constraint columns // NOTE: last_insert_rowid() doesn't work reliably with UPSERT - it only updates on INSERT, not UPDATE @@ -291,12 +316,18 @@ pub async fn download_album( ], ); - db_service.execute(insert_query).await.map_err(|e| e.to_string())?; + db_service + .execute(insert_query) + .await + .map_err(|e| e.to_string())?; // Query for the actual download ID (last_insert_rowid doesn't work with UPSERT) let id_query = Query::with_params( "SELECT id FROM downloads WHERE item_id = ? AND user_id = ?", - vec![QueryParam::String(track_id), QueryParam::String(user_id.clone())], + vec![ + QueryParam::String(track_id), + QueryParam::String(user_id.clone()), + ], ); let download_id: i64 = db_service @@ -317,8 +348,17 @@ pub async fn download_video( request: DownloadVideoRequest, ) -> Result { let DownloadVideoRequest { - item_id, user_id, file_path, mime_type, priority, item_name, quality_preset, - series_name, season_name, episode_number, season_number, + item_id, + user_id, + file_path, + mime_type, + priority, + item_name, + quality_preset, + series_name, + season_name, + episode_number, + season_number, } = request; let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -358,7 +398,10 @@ pub async fn download_video( ], ); - db_service.execute(insert_query).await.map_err(|e| e.to_string())?; + db_service + .execute(insert_query) + .await + .map_err(|e| e.to_string())?; // Query for the download ID by unique constraint columns let id_query = Query::with_params( @@ -403,7 +446,13 @@ pub async fn download_series( let episodes: Vec<(String, String, Option, Option, Option)> = db_service .query_many(episodes_query, |row| { - Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?)) + Ok(( + row.get(0)?, + row.get(1)?, + row.get(2)?, + row.get(3)?, + row.get(4)?, + )) }) .await .map_err(|e| e.to_string())?; @@ -413,7 +462,9 @@ pub async fn download_series( // Queue each episode with descending priority (first episodes download first) // Priority starts high and decreases so earlier episodes finish first let total_episodes = episodes.len() as i32; - for (idx, (episode_id, episode_name, season_name, episode_number, season_number)) in episodes.into_iter().enumerate() { + for (idx, (episode_id, episode_name, season_name, episode_number, season_number)) in + episodes.into_iter().enumerate() + { let priority = 1000 - idx as i32; // High priority for first episodes // Create path like: videos/SeriesName/S01E01_Title.mp4 @@ -425,7 +476,12 @@ pub async fn download_series( episode_num, sanitize_filename(&episode_name) ); - let file_path = format!("{}/{}/{}", base_path, sanitize_filename(&series_name), file_name); + let file_path = format!( + "{}/{}/{}", + base_path, + sanitize_filename(&series_name), + file_name + ); let insert_query = Query::with_params( "INSERT INTO downloads (item_id, user_id, file_path, status, priority, queued_at, @@ -450,17 +506,29 @@ pub async fn download_series( QueryParam::String(episode_name), QueryParam::String(quality.clone()), QueryParam::String(series_name.clone()), - season_name.map(QueryParam::String).unwrap_or(QueryParam::Null), - episode_number.map(QueryParam::Int).unwrap_or(QueryParam::Null), - season_number.map(QueryParam::Int).unwrap_or(QueryParam::Null), + season_name + .map(QueryParam::String) + .unwrap_or(QueryParam::Null), + episode_number + .map(QueryParam::Int) + .unwrap_or(QueryParam::Null), + season_number + .map(QueryParam::Int) + .unwrap_or(QueryParam::Null), ], ); - db_service.execute(insert_query).await.map_err(|e| e.to_string())?; + db_service + .execute(insert_query) + .await + .map_err(|e| e.to_string())?; let id_query = Query::with_params( "SELECT id FROM downloads WHERE item_id = ? AND user_id = ?", - vec![QueryParam::String(episode_id), QueryParam::String(user_id.clone())], + vec![ + QueryParam::String(episode_id), + QueryParam::String(user_id.clone()), + ], ); let download_id: i64 = db_service @@ -471,7 +539,10 @@ pub async fn download_series( download_ids.push(download_id); } - info!("[download_series] Queued {} episodes for series '{}'", total_episodes, series_name); + info!( + "[download_series] Queued {} episodes for series '{}'", + total_episodes, series_name + ); Ok(download_ids) } @@ -524,7 +595,12 @@ pub async fn download_season( episode_num, sanitize_filename(&episode_name) ); - let file_path = format!("{}/{}/{}", base_path, sanitize_filename(&series_name), file_name); + let file_path = format!( + "{}/{}/{}", + base_path, + sanitize_filename(&series_name), + file_name + ); let insert_query = Query::with_params( "INSERT INTO downloads (item_id, user_id, file_path, status, priority, queued_at, @@ -550,11 +626,17 @@ pub async fn download_season( ], ); - db_service.execute(insert_query).await.map_err(|e| e.to_string())?; + db_service + .execute(insert_query) + .await + .map_err(|e| e.to_string())?; let id_query = Query::with_params( "SELECT id FROM downloads WHERE item_id = ? AND user_id = ?", - vec![QueryParam::String(episode_id), QueryParam::String(user_id.clone())], + vec![ + QueryParam::String(episode_id), + QueryParam::String(user_id.clone()), + ], ); let download_id: i64 = db_service @@ -565,11 +647,15 @@ pub async fn download_season( download_ids.push(download_id); } - info!("[download_season] Queued {} episodes for {} - {}", download_ids.len(), series_name, season_name); + info!( + "[download_season] Queued {} episodes for {} - {}", + download_ids.len(), + series_name, + season_name + ); Ok(download_ids) } - /// Helper to compute download statistics from a list of downloads #[allow(dead_code)] fn compute_download_stats(downloads: &[DownloadInfo]) -> DownloadStats { @@ -674,7 +760,10 @@ pub async fn get_downloads( /// Pause a download #[tauri::command] #[specta::specta] -pub async fn pause_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> Result<(), String> { +pub async fn pause_download( + db: State<'_, DatabaseWrapper>, + download_id: i64, +) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) @@ -692,7 +781,10 @@ pub async fn pause_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> /// Resume a paused download #[tauri::command] #[specta::specta] -pub async fn resume_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> Result<(), String> { +pub async fn resume_download( + db: State<'_, DatabaseWrapper>, + download_id: i64, +) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) @@ -738,13 +830,20 @@ pub async fn cancel_download( vec![QueryParam::Int64(download_id)], ); - db_service.execute(delete_query).await.map_err(|e| e.to_string())?; + db_service + .execute(delete_query) + .await + .map_err(|e| e.to_string())?; // Unregister from download manager (in case it was active) { let manager = download_manager.0.lock().map_err(|e| e.to_string())?; manager.unregister_download(download_id); - info!("Cancelled download {}. Active downloads: {}", download_id, manager.active_count()); + info!( + "Cancelled download {}. Active downloads: {}", + download_id, + manager.active_count() + ); } // Delete partial file if exists @@ -800,7 +899,10 @@ pub async fn mark_download_failed( let query = Query::with_params( "UPDATE downloads SET status = 'failed', error_message = ? WHERE id = ?", - vec![QueryParam::String(error_message), QueryParam::Int64(download_id)], + vec![ + QueryParam::String(error_message), + QueryParam::Int64(download_id), + ], ); db_service.execute(query).await.map_err(|e| e.to_string())?; @@ -834,7 +936,10 @@ pub async fn start_download( })?; if !manager.can_start_download() { - warn!("Cannot start download: maximum concurrent downloads ({}) reached", manager.max_concurrent()); + warn!( + "Cannot start download: maximum concurrent downloads ({}) reached", + manager.max_concurrent() + ); debug!(" Active downloads: {}", manager.active_count()); return Err(format!( "Maximum concurrent downloads ({}) reached. Please wait for existing downloads to complete.", @@ -845,12 +950,19 @@ pub async fn start_download( // Register this download as active let registered = manager.register_download(download_id); if !registered { - warn!("Failed to register download {}: already registered or limit reached", download_id); + warn!( + "Failed to register download {}: already registered or limit reached", + download_id + ); return Err("Download already in progress or limit reached".to_string()); } - info!("Download {} registered. Active downloads: {}/{}", - download_id, manager.active_count(), manager.max_concurrent()); + info!( + "Download {} registered. Active downloads: {}/{}", + download_id, + manager.active_count(), + manager.max_concurrent() + ); } // Get download info from DB @@ -868,21 +980,23 @@ pub async fn start_download( ); let (item_id, file_path, file_size): (String, String, Option) = db_service - .query_one(info_query, |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?))) + .query_one(info_query, |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?)) + }) .await .map_err(|e| { error!("Failed to query download info: {}", e); e.to_string() })?; - debug!(" Retrieved: item_id={}, file_path={}, file_size={:?}", item_id, file_path, file_size); + debug!( + " Retrieved: item_id={}, file_path={}, file_size={:?}", + item_id, file_path, file_size + ); // Make a HEAD request to get the file size from Content-Length header debug!("Making HEAD request to get file size..."); - let head_response = reqwest::Client::new() - .head(&stream_url) - .send() - .await; + let head_response = reqwest::Client::new().head(&stream_url).send().await; let file_size_from_server = match head_response { Ok(response) => { @@ -893,7 +1007,11 @@ pub async fn start_download( .and_then(|v| v.parse::().ok()); if let Some(size) = size { - debug!(" Got file size from server: {} bytes ({} MB)", size, size / 1024 / 1024); + debug!( + " Got file size from server: {} bytes ({} MB)", + size, + size / 1024 / 1024 + ); } else { warn!(" Server didn't provide Content-Length header"); } @@ -929,7 +1047,10 @@ pub async fn start_download( ) }; - db_service.execute(update_query).await.map_err(|e| e.to_string())?; + db_service + .execute(update_query) + .await + .map_err(|e| e.to_string())?; // Emit started event let started_event = DownloadEvent::Started { @@ -937,7 +1058,10 @@ pub async fn start_download( item_id: item_id.clone(), }; debug!("Emitting download-event: {:?}", started_event); - debug!(" Serialized: {}", serde_json::to_string(&started_event).unwrap_or_default()); + debug!( + " Serialized: {}", + serde_json::to_string(&started_event).unwrap_or_default() + ); match app.emit("download-event", started_event) { Ok(_) => debug!(" Event emitted successfully"), Err(e) => error!(" Event emit failed: {:?}", e), @@ -998,7 +1122,10 @@ pub async fn enqueue_download( QueryParam::Int64(download_id), ], ); - db_service.execute(update_query).await.map_err(|e| e.to_string())?; + db_service + .execute(update_query) + .await + .map_err(|e| e.to_string())?; // Kick the pump: it will start as many pending downloads as there are slots. let active_downloads = { @@ -1056,7 +1183,9 @@ pub async fn enqueue_video_downloads( }; // Build the transcode URL (pure URL builder, no server round-trip). - let stream_url = repo.as_ref().get_video_download_url(&item_id, &quality, None); + let stream_url = repo + .as_ref() + .get_video_download_url(&item_id, &quality, None); let update_query = Query::with_params( "UPDATE downloads SET status = 'pending', stream_url = ?, target_dir = ? WHERE id = ?", @@ -1067,7 +1196,10 @@ pub async fn enqueue_video_downloads( ], ); if let Err(e) = db_service.execute(update_query).await { - warn!("[enqueue_video] Failed to persist URL for download {}: {}", download_id, e); + warn!( + "[enqueue_video] Failed to persist URL for download {}: {}", + download_id, e + ); } } @@ -1137,7 +1269,13 @@ pub(crate) async fn pump_download_queue( let candidates: Vec<(i64, String, String, String, String)> = match db_service .query_many(next_query, |row| { - Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?)) + Ok(( + row.get(0)?, + row.get(1)?, + row.get(2)?, + row.get(3)?, + row.get(4)?, + )) }) .await { @@ -1189,7 +1327,10 @@ pub(crate) async fn pump_download_queue( vec![QueryParam::Int64(download_id)], ); if let Err(e) = db_service.execute(update_query).await { - error!("[pump] Failed to mark download {} downloading: {}", download_id, e); + error!( + "[pump] Failed to mark download {} downloading: {}", + download_id, e + ); if let Ok(mut a) = active_downloads.lock() { a.remove(&download_id); } @@ -1228,8 +1369,8 @@ fn spawn_download_worker( target_path: std::path::PathBuf, active_downloads: Arc>>, ) { - use crate::download::{DownloadTask, DownloadWorker}; use crate::download::events::DownloadEvent; + use crate::download::{DownloadTask, DownloadWorker}; use tauri::Emitter; let task = DownloadTask { @@ -1265,7 +1406,11 @@ fn spawn_download_worker( // Free the slot before pumping so the next download can take it. if let Ok(mut active) = active_downloads.lock() { active.remove(&download_id); - debug!(" Unregistered download {}. Active downloads: {}", download_id, active.len()); + debug!( + " Unregistered download {}. Active downloads: {}", + download_id, + active.len() + ); } // The pump runs downloads in the background, so the terminal status MUST @@ -1281,7 +1426,10 @@ fn spawn_download_worker( let database = match db.0.lock() { Ok(d) => d, Err(e) => { - error!("[pump] Failed to lock database after download {}: {}", download_id, e); + error!( + "[pump] Failed to lock database after download {}: {}", + download_id, e + ); return; } }; @@ -1290,7 +1438,10 @@ fn spawn_download_worker( match result { Ok(res) => { - info!("Download completed successfully: {} bytes", res.bytes_downloaded); + info!( + "Download completed successfully: {} bytes", + res.bytes_downloaded + ); let file_path = target_path.to_string_lossy().to_string(); let update = Query::with_params( @@ -1305,7 +1456,10 @@ fn spawn_download_worker( ], ); if let Err(e) = db_service.execute(update).await { - error!("[pump] Failed to persist completed status for download {}: {}", download_id, e); + error!( + "[pump] Failed to persist completed status for download {}: {}", + download_id, e + ); } let completed_event = DownloadEvent::Completed { @@ -1329,7 +1483,10 @@ fn spawn_download_worker( ], ); if let Err(db_err) = db_service.execute(update).await { - error!("[pump] Failed to persist failed status for download {}: {}", download_id, db_err); + error!( + "[pump] Failed to persist failed status for download {}: {}", + download_id, db_err + ); } let failed_event = DownloadEvent::Failed { @@ -1352,7 +1509,10 @@ fn spawn_download_worker( /// Delete a completed download #[tauri::command] #[specta::specta] -pub async fn delete_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> Result<(), String> { +pub async fn delete_download( + db: State<'_, DatabaseWrapper>, + download_id: i64, +) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) @@ -1376,7 +1536,10 @@ pub async fn delete_download(db: State<'_, DatabaseWrapper>, download_id: i64) - vec![QueryParam::Int64(download_id)], ); - db_service.execute(delete_query).await.map_err(|e| e.to_string())?; + db_service + .execute(delete_query) + .await + .map_err(|e| e.to_string())?; // Delete actual file if exists if let Some(path) = file_path { @@ -1413,8 +1576,12 @@ fn map_download_row(row: &rusqlite::Row) -> rusqlite::Result { episode_number: row.get(20)?, season_number: row.get(21)?, quality_preset: row.get(22)?, - media_type: row.get::<_, Option>(23)?.unwrap_or_else(|| "audio".to_string()), - download_source: row.get::<_, Option>(24)?.unwrap_or_else(|| "user".to_string()), + media_type: row + .get::<_, Option>(23)? + .unwrap_or_else(|| "audio".to_string()), + download_source: row + .get::<_, Option>(24)? + .unwrap_or_else(|| "user".to_string()), }) } @@ -1500,7 +1667,10 @@ pub async fn get_download_storage_stats( /// Delete all downloads for a user #[tauri::command] #[specta::specta] -pub async fn delete_all_downloads(db: State<'_, DatabaseWrapper>, user_id: String) -> Result { +pub async fn delete_all_downloads( + db: State<'_, DatabaseWrapper>, + user_id: String, +) -> Result { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) @@ -1598,7 +1768,10 @@ pub async fn delete_album_downloads( "SELECT d.file_path FROM downloads d JOIN items i ON d.item_id = i.id WHERE d.user_id = ? AND i.album_id = ? AND d.status = 'completed'", - vec![QueryParam::String(user_id.clone()), QueryParam::String(album_id.clone())], + vec![ + QueryParam::String(user_id.clone()), + QueryParam::String(album_id.clone()), + ], ); let file_paths: Vec = db_service @@ -1665,7 +1838,6 @@ pub async fn set_max_concurrent_downloads( Ok(()) } - // TRACES: UR-011, UR-018 | DR-015, DR-018 | UT-042, UT-043 #[cfg(test)] mod tests { @@ -1834,7 +2006,10 @@ mod tests { ) .unwrap(); - assert_eq!(status, "pending", "Status should be reset to pending after UPSERT"); + assert_eq!( + status, "pending", + "Status should be reset to pending after UPSERT" + ); } #[test] @@ -2069,7 +2244,11 @@ mod tests { .unwrap(); let status: String = conn - .query_row("SELECT status FROM downloads WHERE id = ?1", params![id], |row| row.get(0)) + .query_row( + "SELECT status FROM downloads WHERE id = ?1", + params![id], + |row| row.get(0), + ) .unwrap(); assert_eq!(status, "downloading"); diff --git a/src-tauri/src/commands/download/pinning.rs b/src-tauri/src/commands/download/pinning.rs index 897179b1..b72a5c21 100644 --- a/src-tauri/src/commands/download/pinning.rs +++ b/src-tauri/src/commands/download/pinning.rs @@ -1,4 +1,6 @@ //! Pinning commands - protect an item's cached metadata from cache clearing. +//! +//! TRACES: UR-044 | DR-056 use std::sync::Arc; use tauri::State; @@ -45,7 +47,10 @@ pub async fn unpin_item(db: State<'_, DatabaseWrapper>, item_id: String) -> Resu /// Check if an item is pinned #[tauri::command] #[specta::specta] -pub async fn is_item_pinned(db: State<'_, DatabaseWrapper>, item_id: String) -> Result { +pub async fn is_item_pinned( + db: State<'_, DatabaseWrapper>, + item_id: String, +) -> Result { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) diff --git a/src-tauri/src/commands/download/smart_cache.rs b/src-tauri/src/commands/download/smart_cache.rs index 0da9a6db..8e2f61f1 100644 --- a/src-tauri/src/commands/download/smart_cache.rs +++ b/src-tauri/src/commands/download/smart_cache.rs @@ -1,7 +1,9 @@ //! Smart-cache statistics/config and album recommendation commands. +//! +//! TRACES: UR-045 | DR-057 -use std::sync::Arc; use log::info; +use std::sync::Arc; use tauri::State; use crate::commands::{DatabaseWrapper, SmartCacheWrapper}; diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 97c73327..c5ffa6fe 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -25,11 +25,11 @@ pub use device::*; pub use download::*; pub use offline::*; pub use playback_mode::*; -#[allow(unused_imports)] // Used when playback_reporting is fully integrated +#[allow(unused_imports)] // Used when playback_reporting is fully integrated pub use playback_reporting::*; pub use player::*; pub use playlist::*; -pub use repository::{*, RepositoryManager, RepositoryManagerWrapper}; +pub use repository::{RepositoryManager, RepositoryManagerWrapper, *}; pub use sessions::*; pub use storage::*; pub use sync::*; diff --git a/src-tauri/src/commands/playback_mode.rs b/src-tauri/src/commands/playback_mode.rs index d89f99eb..103107a0 100644 --- a/src-tauri/src/commands/playback_mode.rs +++ b/src-tauri/src/commands/playback_mode.rs @@ -1,3 +1,7 @@ +//! Playback-mode transfer commands (local ↔ remote). +//! +//! TRACES: UR-010 | DR-059 + use std::sync::Arc; use tauri::State; @@ -105,21 +109,30 @@ pub async fn playback_mode_get_remote_status( let controller = player.0.lock().await; let client_arc = controller.jellyfin_client(); let client_opt = client_arc.lock().map_err(|e| e.to_string())?; - client_opt.as_ref().ok_or("Jellyfin client not configured")?.clone() + client_opt + .as_ref() + .ok_or("Jellyfin client not configured")? + .clone() }; // Get session info match client.get_session(&session_id).await { Ok(Some(session)) => { - let position_ticks = session.play_state.as_ref() + let position_ticks = session + .play_state + .as_ref() .and_then(|ps| ps.position_ticks) .unwrap_or(0); - let duration_ticks = session.now_playing_item.as_ref() + let duration_ticks = session + .now_playing_item + .as_ref() .and_then(|item| item.run_time_ticks) .unwrap_or(0); - let is_paused = session.play_state.as_ref() + let is_paused = session + .play_state + .as_ref() .and_then(|ps| ps.is_paused) .unwrap_or(true); @@ -224,17 +237,20 @@ mod tests { fn test_playback_mode_deserialization_from_frontend() { // Test what frontend sends for Idle mode let idle_json = r#"{"type":"idle"}"#; - let mode: PlaybackMode = serde_json::from_str(idle_json).expect("Failed to deserialize idle"); + let mode: PlaybackMode = + serde_json::from_str(idle_json).expect("Failed to deserialize idle"); assert_eq!(mode, PlaybackMode::Idle); // Test what frontend sends for Local mode let local_json = r#"{"type":"local"}"#; - let mode: PlaybackMode = serde_json::from_str(local_json).expect("Failed to deserialize local"); + let mode: PlaybackMode = + serde_json::from_str(local_json).expect("Failed to deserialize local"); assert_eq!(mode, PlaybackMode::Local); // Test what frontend sends for Remote mode let remote_json = r#"{"type":"remote","session_id":"session-123"}"#; - let mode: PlaybackMode = serde_json::from_str(remote_json).expect("Failed to deserialize remote"); + let mode: PlaybackMode = + serde_json::from_str(remote_json).expect("Failed to deserialize remote"); match mode { PlaybackMode::Remote { session_id } => assert_eq!(session_id, "session-123"), _ => panic!("Expected Remote mode"), @@ -247,8 +263,8 @@ mod tests { // Test Search context (the recently fixed issue) let search_json = r#"{"type":"search","searchQuery":"test query"}"#; - let context: PlayTracksContext = serde_json::from_str(search_json) - .expect("Failed to deserialize search context"); + let context: PlayTracksContext = + serde_json::from_str(search_json).expect("Failed to deserialize search context"); match context { PlayTracksContext::Search { search_query } => { assert_eq!(search_query, "test query"); @@ -257,11 +273,15 @@ mod tests { } // Test Playlist context - let playlist_json = r#"{"type":"playlist","playlistId":"pl-123","playlistName":"My Playlist"}"#; - let context: PlayTracksContext = serde_json::from_str(playlist_json) - .expect("Failed to deserialize playlist context"); + let playlist_json = + r#"{"type":"playlist","playlistId":"pl-123","playlistName":"My Playlist"}"#; + let context: PlayTracksContext = + serde_json::from_str(playlist_json).expect("Failed to deserialize playlist context"); match context { - PlayTracksContext::Playlist { playlist_id, playlist_name } => { + PlayTracksContext::Playlist { + playlist_id, + playlist_name, + } => { assert_eq!(playlist_id, "pl-123"); assert_eq!(playlist_name, "My Playlist"); } @@ -270,8 +290,8 @@ mod tests { // Test Custom context let custom_json = r#"{"type":"custom","label":"Custom Queue"}"#; - let context: PlayTracksContext = serde_json::from_str(custom_json) - .expect("Failed to deserialize custom context"); + let context: PlayTracksContext = + serde_json::from_str(custom_json).expect("Failed to deserialize custom context"); match context { PlayTracksContext::Custom { label } => { assert_eq!(label, Some("Custom Queue".to_string())); diff --git a/src-tauri/src/commands/playback_reporting.rs b/src-tauri/src/commands/playback_reporting.rs index f55521d1..22698ea0 100644 --- a/src-tauri/src/commands/playback_reporting.rs +++ b/src-tauri/src/commands/playback_reporting.rs @@ -1,5 +1,7 @@ //! Tauri commands for playback reporting operations //! +//! TRACES: UR-025, UR-019 | IR-015, JA-010, JA-011, JA-012 | DR-028 +//! //! These commands provide frontend access to the Rust playback reporting system, //! replacing the TypeScript implementation with native Rust reporting. //! @@ -16,7 +18,7 @@ use crate::commands::connectivity::ConnectivityMonitorWrapper; use crate::commands::storage::DatabaseWrapper; use crate::jellyfin::client::JellyfinClient; use crate::jellyfin::JellyfinConfig; -use crate::playback_reporting::{PlaybackReporter, PlaybackOperation, PlaybackContext}; +use crate::playback_reporting::{PlaybackContext, PlaybackOperation, PlaybackReporter}; use crate::utils::conversions::seconds_to_ticks; /// Tauri state wrapper for PlaybackReporter @@ -61,7 +63,10 @@ pub async fn playback_reporter_init( // Store in wrapper *reporter_wrapper.0.lock().await = Some(reporter); - log::info!("[PlaybackReporter] Initialized successfully for user: {}", user_id); + log::info!( + "[PlaybackReporter] Initialized successfully for user: {}", + user_id + ); Ok(()) } @@ -205,7 +210,12 @@ mod tests { }; // Verify enum variant can be created and pattern matched - if let PlaybackOperation::Start { item_id, position_ticks, context } = operation { + if let PlaybackOperation::Start { + item_id, + position_ticks, + context, + } = operation + { assert_eq!(item_id, "item-123"); assert_eq!(position_ticks, 15_000_000); assert!(context.is_some()); @@ -225,7 +235,10 @@ mod tests { context: None, }; - if let PlaybackOperation::Start { item_id, context, .. } = operation { + if let PlaybackOperation::Start { + item_id, context, .. + } = operation + { assert_eq!(item_id, "item-789"); assert!(context.is_none()); } else { @@ -241,7 +254,12 @@ mod tests { is_paused: true, }; - if let PlaybackOperation::Progress { item_id, position_ticks, is_paused } = operation { + if let PlaybackOperation::Progress { + item_id, + position_ticks, + is_paused, + } = operation + { assert_eq!(item_id, "item-999"); assert_eq!(position_ticks, 30_000_000); assert!(is_paused); @@ -272,7 +290,11 @@ mod tests { position_ticks: 120_000_000, }; - if let PlaybackOperation::Stopped { item_id, position_ticks } = operation { + if let PlaybackOperation::Stopped { + item_id, + position_ticks, + } = operation + { assert_eq!(item_id, "item-111"); assert_eq!(position_ticks, 120_000_000); } else { @@ -364,7 +386,10 @@ mod tests { }; let cloned = operation.clone(); - if let PlaybackOperation::Progress { item_id, is_paused, .. } = cloned { + if let PlaybackOperation::Progress { + item_id, is_paused, .. + } = cloned + { assert_eq!(item_id, "item-clone"); assert!(is_paused); } else { diff --git a/src-tauri/src/commands/player/mod.rs b/src-tauri/src/commands/player/mod.rs index d635eeea..726bf59a 100644 --- a/src-tauri/src/commands/player/mod.rs +++ b/src-tauri/src/commands/player/mod.rs @@ -18,18 +18,20 @@ use log::{debug, error, info, warn}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use tokio::sync::Mutex as TokioMutex; use tauri::State; +use tokio::sync::Mutex as TokioMutex; use super::DatabaseWrapper; use crate::download::cache::{CacheConfig, SmartCache}; use crate::jellyfin::{JellyfinClient, JellyfinConfig}; use crate::player::{ - determine_video_seek_strategy, MediaItem, MediaSource, MediaType, - MediaSessionManager, PlayerController, PlayerState, PlayerStatusEvent, QueueContext, RepeatMode, - VideoSeekStrategy, + determine_video_seek_strategy, MediaItem, MediaSessionManager, MediaSource, MediaType, + PlayerController, PlayerState, PlayerStatusEvent, QueueContext, RepeatMode, VideoSeekStrategy, +}; +use crate::repository::{ + types::{GetItemsOptions, ImageOptions, ImageType}, + MediaRepository, }; -use crate::repository::{MediaRepository, types::{GetItemsOptions, ImageOptions, ImageType}}; use crate::settings::VideoSettings; use crate::storage::db_service::{DatabaseService, Query, QueryParam}; @@ -120,13 +122,17 @@ impl From<&crate::jellyfin::client::NowPlayingItem> for MergedMediaItem { Self { id: item.id.clone().unwrap_or_default(), title: item.name.clone().unwrap_or_else(|| "Unknown".to_string()), - artist: item.album_artist.clone() + artist: item + .album_artist + .clone() .or_else(|| item.artists.as_ref().and_then(|a| a.first().cloned())), album: item.album.clone(), album_id: item.album_id.clone(), duration: item.run_time_ticks.map(|ticks| ticks as f64 / 10_000_000.0), primary_image_tag: item.primary_image_tag.clone(), - media_type: item.item_type.clone() + media_type: item + .item_type + .clone() .unwrap_or_else(|| "audio".to_string()) .to_lowercase(), } @@ -169,6 +175,15 @@ pub struct PlayItemRequest { pub video_codec: String, /// Whether the video requires server-side transcoding pub needs_transcoding: bool, + /// Optional now-playing metadata. Used by the background-audio handoff so the + /// lockscreen/miniplayer show the item (title/subtitle/artwork). Defaulted so + /// existing video-only callers need not send them. + #[serde(default)] + pub artist: Option, + #[serde(default)] + pub primary_image_tag: Option, + #[serde(default)] + pub server_id: Option, } /// Queue context for remote transfer - what type of queue is this? @@ -322,22 +337,22 @@ pub(super) async fn create_media_item( id: req.id.clone(), title: req.title.clone(), name: Some(req.title.clone()), - artist: None, // Not available from video-only request - album: None, // Not available from video-only request - album_name: None, // Not available from video-only request - album_id: None, // Not available from video-only request - artist_items: None, // Not available from video-only request - artists: None, // Not available from video-only request + artist: None, // Not available from video-only request + album: None, // Not available from video-only request + album_name: None, // Not available from video-only request + album_id: None, // Not available from video-only request + artist_items: None, // Not available from video-only request + artists: None, // Not available from video-only request primary_image_tag: None, // Not available from video-only request - item_type: None, // Not available from video-only request - playlist_id: None, // Not available from video-only request - duration: None, // Not available from video-only request - artwork_url: None, // Not available from video-only request + item_type: None, // Not available from video-only request + playlist_id: None, // Not available from video-only request + duration: None, // Not available from video-only request + artwork_url: None, // Not available from video-only request media_type: crate::player::MediaType::Video, // Video-only request source, video_codec: Some(req.video_codec), needs_transcoding: req.needs_transcoding, - video_width: None, // Not available from video-only request + video_width: None, // Not available from video-only request video_height: None, // Not available from video-only request subtitles: vec![], series_id: None, // Not available from video-only request @@ -370,7 +385,10 @@ pub(super) async fn check_for_local_download( if std::path::Path::new(file_path).exists() { Ok(path) } else { - warn!("[Player] Download entry exists in DB but file not found: {}", file_path); + warn!( + "[Player] Download entry exists in DB but file not found: {}", + file_path + ); Ok(None) } } else { @@ -398,7 +416,9 @@ pub(super) async fn refresh_queue_local_sources( .items() .iter() .filter_map(|item| match &item.source { - MediaSource::Remote { jellyfin_item_id, .. } => Some(jellyfin_item_id.clone()), + MediaSource::Remote { + jellyfin_item_id, .. + } => Some(jellyfin_item_id.clone()), _ => None, }) .collect() @@ -423,9 +443,15 @@ pub(super) async fn refresh_queue_local_sources( let mut queue_lock = queue.lock().map_err(|e| e.to_string())?; let mut switched = 0; for item in queue_lock.items_mut() { - if let MediaSource::Remote { jellyfin_item_id, .. } = &item.source { + if let MediaSource::Remote { + jellyfin_item_id, .. + } = &item.source + { if let Some((id, path)) = local_paths.iter().find(|(id, _)| id == jellyfin_item_id) { - info!("[Player] Switching queued track {} to local download: {}", id, path); + info!( + "[Player] Switching queued track {} to local download: {}", + id, path + ); item.source = MediaSource::Local { file_path: PathBuf::from(path), jellyfin_item_id: Some(id.clone()), @@ -454,7 +480,10 @@ pub async fn player_play_item( db: State<'_, DatabaseWrapper>, item: PlayItemRequest, ) -> Result { - info!("player_play_item called: {} - {}", item.title, item.stream_url); + info!( + "player_play_item called: {} - {}", + item.title, item.stream_url + ); // Create media item, checking for local download first let media_item = create_media_item(item, Some(&db)).await?; @@ -505,6 +534,119 @@ pub async fn player_play_item( Ok(get_player_status(&controller)) } +/// Enter background-audio mode: hand playback of the currently-watched video off +/// to the native ExoPlayer *audio* path so the audio keeps playing while the app +/// is backgrounded/locked, with no client-side video decode (UR-040). +/// +/// `stream_url` MUST be an audio-only URL (see +/// `get_audio_only_stream_url_for_video`). The item is created as +/// `MediaType::Audio` so it starts an audio session and loads into the native +/// backend with `mediaType="audio"` — the WebView `