Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a26a853f01 |
+161
-137
@@ -1,11 +1,11 @@
|
|||||||
# Code Traceability Matrix
|
# Code Traceability Matrix
|
||||||
|
|
||||||
**Generated:** 8/1/2026, 10:40:16 AM
|
**Generated:** 8/2/2026, 6:10:09 PM
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- **Total Files Scanned:** 305
|
- **Total Files Scanned:** 305
|
||||||
- **Total TRACES Found:** 337
|
- **Total TRACES Found:** 338
|
||||||
- **Requirements Covered:**
|
- **Requirements Covered:**
|
||||||
- User Requirements (UR): 58
|
- User Requirements (UR): 58
|
||||||
- Integration Requirements (IR): 15
|
- Integration Requirements (IR): 15
|
||||||
@@ -229,11 +229,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 17 file(s)
|
**Locations:** 17 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2256)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2258)
|
||||||
- **Line:** 2256
|
- **Line:** 2258
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315)
|
||||||
- **Line:** 2313
|
- **Line:** 2315
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8)
|
- **File:** [`src/lib/stores/player.ts`](src/lib/stores/player.ts#L8)
|
||||||
- **Line:** 8
|
- **Line:** 8
|
||||||
@@ -285,8 +285,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2134)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2136)
|
||||||
- **Line:** 2134
|
- **Line:** 2136
|
||||||
- **Context:** `export type PlaybackProgress = { itemId: string;`
|
- **Context:** `export type PlaybackProgress = { itemId: string;`
|
||||||
- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L42)
|
- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L42)
|
||||||
- **Line:** 42
|
- **Line:** 42
|
||||||
@@ -296,8 +296,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240)
|
||||||
- **Line:** 2238
|
- **Line:** 2240
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L124)
|
- **File:** [`src-tauri/src/player/media.rs`](src-tauri/src/player/media.rs#L124)
|
||||||
- **Line:** 124
|
- **Line:** 124
|
||||||
@@ -339,8 +339,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 13 file(s)
|
**Locations:** 13 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2510)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2512)
|
||||||
- **Line:** 2510
|
- **Line:** 2512
|
||||||
- **Context:** `export type RemoteSessionStatus = { position: number; duration: number...`
|
- **Context:** `export type RemoteSessionStatus = { position: number; duration: number...`
|
||||||
- **File:** [`src/lib/stores/queue.test.ts`](src/lib/stores/queue.test.ts#L4)
|
- **File:** [`src/lib/stores/queue.test.ts`](src/lib/stores/queue.test.ts#L4)
|
||||||
- **Line:** 4
|
- **Line:** 4
|
||||||
@@ -475,11 +475,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 9 file(s)
|
**Locations:** 9 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L531)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L533)
|
||||||
- **Line:** 531
|
- **Line:** 533
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L547)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L549)
|
||||||
- **Line:** 547
|
- **Line:** 549
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L2)
|
- **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L2)
|
||||||
- **Line:** 2
|
- **Line:** 2
|
||||||
@@ -738,8 +738,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 18 file(s)
|
**Locations:** 18 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315)
|
||||||
- **Line:** 2313
|
- **Line:** 2315
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/utils/duration.test.ts`](src/lib/utils/duration.test.ts#L4)
|
- **File:** [`src/lib/utils/duration.test.ts`](src/lib/utils/duration.test.ts#L4)
|
||||||
- **Line:** 4
|
- **Line:** 4
|
||||||
@@ -797,8 +797,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 10 file(s)
|
**Locations:** 10 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2570)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2572)
|
||||||
- **Line:** 2570
|
- **Line:** 2572
|
||||||
- **Context:** `export type SessionInfo = { id?: string | null; userId?: string | null...`
|
- **Context:** `export type SessionInfo = { id?: string | null; userId?: string | null...`
|
||||||
- **File:** [`src/lib/components/player/SleepTimerModal.svelte`](src/lib/components/player/SleepTimerModal.svelte#L1)
|
- **File:** [`src/lib/components/player/SleepTimerModal.svelte`](src/lib/components/player/SleepTimerModal.svelte#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
@@ -838,8 +838,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L181)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L181)
|
||||||
- **Line:** 181
|
- **Line:** 181
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1778)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1780)
|
||||||
- **Line:** 1778
|
- **Line:** 1780
|
||||||
- **Context:** `export type DownloadsResponse = { downloads: DownloadInfo[]; stats: Do...`
|
- **Context:** `export type DownloadsResponse = { downloads: DownloadInfo[]; stats: Do...`
|
||||||
- **File:** [`src-tauri/src/player/android/mod.rs`](src-tauri/src/player/android/mod.rs#L545)
|
- **File:** [`src-tauri/src/player/android/mod.rs`](src-tauri/src/player/android/mod.rs#L545)
|
||||||
- **Line:** 545
|
- **Line:** 545
|
||||||
@@ -1137,10 +1137,13 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
### DR-047
|
### DR-047
|
||||||
|
|
||||||
**Locations:** 13 file(s)
|
**Locations:** 15 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241)
|
||||||
- **Line:** 2313
|
- **Line:** 241
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315)
|
||||||
|
- **Line:** 2315
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9)
|
- **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9)
|
||||||
- **Line:** 9
|
- **Line:** 9
|
||||||
@@ -1178,6 +1181,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3)
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3)
|
||||||
- **Line:** 3
|
- **Line:** 3
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145)
|
||||||
|
- **Line:** 145
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### DR-048
|
### DR-048
|
||||||
|
|
||||||
@@ -1234,7 +1240,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
### DR-052
|
### DR-052
|
||||||
|
|
||||||
**Locations:** 14 file(s)
|
**Locations:** 15 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L38)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L38)
|
||||||
- **Line:** 38
|
- **Line:** 38
|
||||||
@@ -1242,6 +1248,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
||||||
- **Line:** 51
|
- **Line:** 51
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241)
|
||||||
|
- **Line:** 241
|
||||||
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10)
|
- **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10)
|
||||||
- **Line:** 10
|
- **Line:** 10
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
@@ -1269,14 +1278,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L1264)
|
- **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L1264)
|
||||||
- **Line:** 1264
|
- **Line:** 1264
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L68)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571)
|
||||||
- **Line:** 68
|
- **Line:** 571
|
||||||
- **Context:** `pub struct VideoSettingsWrapper(pub Mutex<VideoSettings>);`
|
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583)
|
|
||||||
- **Line:** 583
|
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663)
|
||||||
- **Line:** 675
|
- **Line:** 663
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145)
|
||||||
|
- **Line:** 145
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### DR-053
|
### DR-053
|
||||||
@@ -1381,11 +1390,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/routes/search/+page.svelte`](src/routes/search/+page.svelte#L14)
|
- **File:** [`src/routes/search/+page.svelte`](src/routes/search/+page.svelte#L14)
|
||||||
- **Line:** 14
|
- **Line:** 14
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1298)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1300)
|
||||||
- **Line:** 1298
|
- **Line:** 1300
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2545)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2547)
|
||||||
- **Line:** 2545
|
- **Line:** 2547
|
||||||
- **Context:** `export type SearchResult = { items: MediaItem[]; totalRecordCount: num...`
|
- **Context:** `export type SearchResult = { items: MediaItem[]; totalRecordCount: num...`
|
||||||
- **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L236)
|
- **File:** [`src/lib/stores/library.ts`](src/lib/stores/library.ts#L236)
|
||||||
- **Line:** 236
|
- **Line:** 236
|
||||||
@@ -1502,17 +1511,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 17 file(s)
|
**Locations:** 17 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L948)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L950)
|
||||||
- **Line:** 948
|
- **Line:** 950
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L959)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L961)
|
||||||
- **Line:** 959
|
- **Line:** 961
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1970)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1972)
|
||||||
- **Line:** 1970
|
- **Line:** 1972
|
||||||
- **Context:** `export type MergedMediaItem = { id: string; title: string; artist: str...`
|
- **Context:** `export type MergedMediaItem = { id: string; title: string; artist: str...`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1979)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1981)
|
||||||
- **Line:** 1979
|
- **Line:** 1981
|
||||||
- **Context:** `export type NetworkStateWrapperArg = { networkType: NetworkType; unmet...`
|
- **Context:** `export type NetworkStateWrapperArg = { networkType: NetworkType; unmet...`
|
||||||
- **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L81)
|
- **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L81)
|
||||||
- **Line:** 81
|
- **Line:** 81
|
||||||
@@ -1653,11 +1662,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105)
|
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105)
|
||||||
- **Line:** 105
|
- **Line:** 105
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1221)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1223)
|
||||||
- **Line:** 1221
|
- **Line:** 1223
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1229)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1231)
|
||||||
- **Line:** 1229
|
- **Line:** 1231
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/downloads/DownloadedBrowse.svelte`](src/lib/components/downloads/DownloadedBrowse.svelte#L10)
|
- **File:** [`src/lib/components/downloads/DownloadedBrowse.svelte`](src/lib/components/downloads/DownloadedBrowse.svelte#L10)
|
||||||
- **Line:** 10
|
- **Line:** 10
|
||||||
@@ -1715,11 +1724,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105)
|
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105)
|
||||||
- **Line:** 105
|
- **Line:** 105
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L805)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L807)
|
||||||
- **Line:** 805
|
- **Line:** 807
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1229)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1231)
|
||||||
- **Line:** 1229
|
- **Line:** 1231
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/library/MediaCard.svelte`](src/lib/components/library/MediaCard.svelte#L24)
|
- **File:** [`src/lib/components/library/MediaCard.svelte`](src/lib/components/library/MediaCard.svelte#L24)
|
||||||
- **Line:** 24
|
- **Line:** 24
|
||||||
@@ -1788,11 +1797,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L113)
|
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L113)
|
||||||
- **Line:** 113
|
- **Line:** 113
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1237)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1239)
|
||||||
- **Line:** 1237
|
- **Line:** 1239
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1723)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1725)
|
||||||
- **Line:** 1723
|
- **Line:** 1725
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/library/LibraryGrid.svelte`](src/lib/components/library/LibraryGrid.svelte#L17)
|
- **File:** [`src/lib/components/library/LibraryGrid.svelte`](src/lib/components/library/LibraryGrid.svelte#L17)
|
||||||
- **Line:** 17
|
- **Line:** 17
|
||||||
@@ -2267,8 +2276,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L203)
|
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L203)
|
||||||
- **Line:** 203
|
- **Line:** 203
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1324)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1326)
|
||||||
- **Line:** 1324
|
- **Line:** 1326
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520)
|
- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520)
|
||||||
- **Line:** 520
|
- **Line:** 520
|
||||||
@@ -2293,8 +2302,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 16 file(s)
|
**Locations:** 16 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240)
|
||||||
- **Line:** 2238
|
- **Line:** 2240
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/stores/connectivity.ts`](src/lib/stores/connectivity.ts#L10)
|
- **File:** [`src/lib/stores/connectivity.ts`](src/lib/stores/connectivity.ts#L10)
|
||||||
- **Line:** 10
|
- **Line:** 10
|
||||||
@@ -2346,11 +2355,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 20 file(s)
|
**Locations:** 20 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2134)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2136)
|
||||||
- **Line:** 2134
|
- **Line:** 2136
|
||||||
- **Context:** `export type PlaybackProgress = { itemId: string;`
|
- **Context:** `export type PlaybackProgress = { itemId: string;`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240)
|
||||||
- **Line:** 2238
|
- **Line:** 2240
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1)
|
- **File:** [`src/lib/components/player/VideoPlayer.svelte`](src/lib/components/player/VideoPlayer.svelte#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
@@ -2411,11 +2420,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 15 file(s)
|
**Locations:** 15 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2134)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2136)
|
||||||
- **Line:** 2134
|
- **Line:** 2136
|
||||||
- **Context:** `export type PlaybackProgress = { itemId: string;`
|
- **Context:** `export type PlaybackProgress = { itemId: string;`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240)
|
||||||
- **Line:** 2238
|
- **Line:** 2240
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/player/AudioPlayer.svelte`](src/lib/components/player/AudioPlayer.svelte#L1)
|
- **File:** [`src/lib/components/player/AudioPlayer.svelte`](src/lib/components/player/AudioPlayer.svelte#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
@@ -2461,14 +2470,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 66 file(s)
|
**Locations:** 66 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2256)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2258)
|
||||||
- **Line:** 2256
|
- **Line:** 2258
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315)
|
||||||
- **Line:** 2313
|
- **Line:** 2315
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2510)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2512)
|
||||||
- **Line:** 2510
|
- **Line:** 2512
|
||||||
- **Context:** `export type RemoteSessionStatus = { position: number; duration: number...`
|
- **Context:** `export type RemoteSessionStatus = { position: number; duration: number...`
|
||||||
- **File:** [`src/lib/components/player/Controls.svelte`](src/lib/components/player/Controls.svelte#L1)
|
- **File:** [`src/lib/components/player/Controls.svelte`](src/lib/components/player/Controls.svelte#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
@@ -2714,11 +2723,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 15 file(s)
|
**Locations:** 15 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L531)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L533)
|
||||||
- **Line:** 531
|
- **Line:** 533
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L547)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L549)
|
||||||
- **Line:** 547
|
- **Line:** 549
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4)
|
- **File:** [`src/lib/stores/auth.test.ts`](src/lib/stores/auth.test.ts#L4)
|
||||||
- **Line:** 4
|
- **Line:** 4
|
||||||
@@ -2764,8 +2773,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 11 file(s)
|
**Locations:** 11 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2238)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2240)
|
||||||
- **Line:** 2238
|
- **Line:** 2240
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L2)
|
- **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L2)
|
||||||
- **Line:** 2
|
- **Line:** 2
|
||||||
@@ -2842,8 +2851,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 12 file(s)
|
**Locations:** 12 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315)
|
||||||
- **Line:** 2313
|
- **Line:** 2315
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4)
|
- **File:** [`src/lib/services/playerEvents.test.ts`](src/lib/services/playerEvents.test.ts#L4)
|
||||||
- **Line:** 4
|
- **Line:** 4
|
||||||
@@ -2881,7 +2890,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
### UR-023
|
### UR-023
|
||||||
|
|
||||||
**Locations:** 22 file(s)
|
**Locations:** 24 file(s)
|
||||||
|
|
||||||
- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1)
|
- **File:** [`src/routes/library/movies/+page.svelte`](src/routes/library/movies/+page.svelte#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
@@ -2892,8 +2901,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/routes/settings/+page.svelte`](src/routes/settings/+page.svelte#L1)
|
- **File:** [`src/routes/settings/+page.svelte`](src/routes/settings/+page.svelte#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241)
|
||||||
- **Line:** 2313
|
- **Line:** 241
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315)
|
||||||
|
- **Line:** 2315
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9)
|
- **File:** [`src/lib/stores/nextEpisode.ts`](src/lib/stores/nextEpisode.ts#L9)
|
||||||
- **Line:** 9
|
- **Line:** 9
|
||||||
@@ -2946,19 +2958,25 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3)
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3)
|
||||||
- **Line:** 3
|
- **Line:** 3
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145)
|
||||||
|
- **Line:** 145
|
||||||
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1)
|
- **File:** [`src-tauri/src/settings.rs`](src-tauri/src/settings.rs#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UR-026
|
### UR-026
|
||||||
|
|
||||||
**Locations:** 17 file(s)
|
**Locations:** 19 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2313)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241)
|
||||||
- **Line:** 2313
|
- **Line:** 241
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2570)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2315)
|
||||||
- **Line:** 2570
|
- **Line:** 2315
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2572)
|
||||||
|
- **Line:** 2572
|
||||||
- **Context:** `export type SessionInfo = { id?: string | null; userId?: string | null...`
|
- **Context:** `export type SessionInfo = { id?: string | null; userId?: string | null...`
|
||||||
- **File:** [`src/lib/components/player/SleepTimerModal.svelte`](src/lib/components/player/SleepTimerModal.svelte#L1)
|
- **File:** [`src/lib/components/player/SleepTimerModal.svelte`](src/lib/components/player/SleepTimerModal.svelte#L1)
|
||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
@@ -3005,6 +3023,9 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3)
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L3)
|
||||||
- **Line:** 3
|
- **Line:** 3
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145)
|
||||||
|
- **Line:** 145
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UR-027
|
### UR-027
|
||||||
|
|
||||||
@@ -3016,8 +3037,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L181)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L181)
|
||||||
- **Line:** 181
|
- **Line:** 181
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1778)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1780)
|
||||||
- **Line:** 1778
|
- **Line:** 1780
|
||||||
- **Context:** `export type DownloadsResponse = { downloads: DownloadInfo[]; stats: Do...`
|
- **Context:** `export type DownloadsResponse = { downloads: DownloadInfo[]; stats: Do...`
|
||||||
- **File:** [`src-tauri/src/player/android/mod.rs`](src-tauri/src/player/android/mod.rs#L545)
|
- **File:** [`src-tauri/src/player/android/mod.rs`](src-tauri/src/player/android/mod.rs#L545)
|
||||||
- **Line:** 545
|
- **Line:** 545
|
||||||
@@ -3176,7 +3197,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
### UR-040
|
### UR-040
|
||||||
|
|
||||||
**Locations:** 26 file(s)
|
**Locations:** 27 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L434)
|
- **File:** [`src/lib/api/repository-client.test.ts`](src/lib/api/repository-client.test.ts#L434)
|
||||||
- **Line:** 434
|
- **Line:** 434
|
||||||
@@ -3193,8 +3214,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
||||||
- **Line:** 51
|
- **Line:** 51
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1324)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L241)
|
||||||
- **Line:** 1324
|
- **Line:** 241
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1326)
|
||||||
|
- **Line:** 1326
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10)
|
- **File:** [`src/lib/components/player/backgroundAudioHandoff.test.ts`](src/lib/components/player/backgroundAudioHandoff.test.ts#L10)
|
||||||
- **Line:** 10
|
- **Line:** 10
|
||||||
@@ -3229,14 +3253,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L1264)
|
- **File:** [`src-tauri/src/player/mod.rs`](src-tauri/src/player/mod.rs#L1264)
|
||||||
- **Line:** 1264
|
- **Line:** 1264
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L68)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571)
|
||||||
- **Line:** 68
|
- **Line:** 571
|
||||||
- **Context:** `pub struct VideoSettingsWrapper(pub Mutex<VideoSettings>);`
|
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583)
|
|
||||||
- **Line:** 583
|
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663)
|
||||||
- **Line:** 675
|
- **Line:** 663
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/commands/player/timers.rs`](src-tauri/src/commands/player/timers.rs#L145)
|
||||||
|
- **Line:** 145
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520)
|
- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520)
|
||||||
- **Line:** 520
|
- **Line:** 520
|
||||||
@@ -3298,11 +3322,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/routes/search/+page.svelte`](src/routes/search/+page.svelte#L22)
|
- **File:** [`src/routes/search/+page.svelte`](src/routes/search/+page.svelte#L22)
|
||||||
- **Line:** 22
|
- **Line:** 22
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1298)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1300)
|
||||||
- **Line:** 1298
|
- **Line:** 1300
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2545)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2547)
|
||||||
- **Line:** 2545
|
- **Line:** 2547
|
||||||
- **Context:** `export type SearchResult = { items: MediaItem[]; totalRecordCount: num...`
|
- **Context:** `export type SearchResult = { items: MediaItem[]; totalRecordCount: num...`
|
||||||
- **File:** [`src/lib/components/search/SearchScopeChips.svelte`](src/lib/components/search/SearchScopeChips.svelte#L7)
|
- **File:** [`src/lib/components/search/SearchScopeChips.svelte`](src/lib/components/search/SearchScopeChips.svelte#L7)
|
||||||
- **Line:** 7
|
- **Line:** 7
|
||||||
@@ -3342,8 +3366,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 9 file(s)
|
**Locations:** 9 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1298)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1300)
|
||||||
- **Line:** 1298
|
- **Line:** 1300
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/search/SearchResults.svelte`](src/lib/components/search/SearchResults.svelte#L9)
|
- **File:** [`src/lib/components/search/SearchResults.svelte`](src/lib/components/search/SearchResults.svelte#L9)
|
||||||
- **Line:** 9
|
- **Line:** 9
|
||||||
@@ -3374,17 +3398,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 17 file(s)
|
**Locations:** 17 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L948)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L950)
|
||||||
- **Line:** 948
|
- **Line:** 950
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L959)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L961)
|
||||||
- **Line:** 959
|
- **Line:** 961
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1970)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1972)
|
||||||
- **Line:** 1970
|
- **Line:** 1972
|
||||||
- **Context:** `export type MergedMediaItem = { id: string; title: string; artist: str...`
|
- **Context:** `export type MergedMediaItem = { id: string; title: string; artist: str...`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1979)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1981)
|
||||||
- **Line:** 1979
|
- **Line:** 1981
|
||||||
- **Context:** `export type NetworkStateWrapperArg = { networkType: NetworkType; unmet...`
|
- **Context:** `export type NetworkStateWrapperArg = { networkType: NetworkType; unmet...`
|
||||||
- **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L81)
|
- **File:** [`src/lib/stores/downloads.ts`](src/lib/stores/downloads.ts#L81)
|
||||||
- **Line:** 81
|
- **Line:** 81
|
||||||
@@ -3439,14 +3463,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105)
|
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L105)
|
||||||
- **Line:** 105
|
- **Line:** 105
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L805)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L807)
|
||||||
- **Line:** 805
|
- **Line:** 807
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1221)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1223)
|
||||||
- **Line:** 1221
|
- **Line:** 1223
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1229)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1231)
|
||||||
- **Line:** 1229
|
- **Line:** 1231
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/library/MediaCard.svelte`](src/lib/components/library/MediaCard.svelte#L24)
|
- **File:** [`src/lib/components/library/MediaCard.svelte`](src/lib/components/library/MediaCard.svelte#L24)
|
||||||
- **Line:** 24
|
- **Line:** 24
|
||||||
@@ -3522,11 +3546,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L113)
|
- **File:** [`src/lib/api/repository-client.ts`](src/lib/api/repository-client.ts#L113)
|
||||||
- **Line:** 113
|
- **Line:** 113
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1237)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1239)
|
||||||
- **Line:** 1237
|
- **Line:** 1239
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1723)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1725)
|
||||||
- **Line:** 1723
|
- **Line:** 1725
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/library/LibraryGrid.svelte`](src/lib/components/library/LibraryGrid.svelte#L17)
|
- **File:** [`src/lib/components/library/LibraryGrid.svelte`](src/lib/components/library/LibraryGrid.svelte#L17)
|
||||||
- **Line:** 17
|
- **Line:** 17
|
||||||
@@ -3611,11 +3635,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
||||||
- **Line:** 51
|
- **Line:** 51
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571)
|
||||||
- **Line:** 583
|
- **Line:** 571
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663)
|
||||||
- **Line:** 675
|
- **Line:** 663
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### IT-016
|
### IT-016
|
||||||
@@ -3903,17 +3927,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L51)
|
||||||
- **Line:** 51
|
- **Line:** 51
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1324)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L1326)
|
||||||
- **Line:** 1324
|
- **Line:** 1326
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/player/backgroundAudio.commands.test.ts`](src/lib/player/backgroundAudio.commands.test.ts#L7)
|
- **File:** [`src/lib/player/backgroundAudio.commands.test.ts`](src/lib/player/backgroundAudio.commands.test.ts#L7)
|
||||||
- **Line:** 7
|
- **Line:** 7
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L583)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L571)
|
||||||
- **Line:** 583
|
- **Line:** 571
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L675)
|
- **File:** [`src-tauri/src/commands/player/mod.rs`](src-tauri/src/commands/player/mod.rs#L663)
|
||||||
- **Line:** 675
|
- **Line:** 663
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520)
|
- **File:** [`src-tauri/src/commands/repository.rs`](src-tauri/src/commands/repository.rs#L520)
|
||||||
- **Line:** 520
|
- **Line:** 520
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jellytau",
|
"name": "jellytau",
|
||||||
"version": "0.2.8",
|
"version": "0.2.9",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.5",
|
"packageManager": "bun@1.3.5",
|
||||||
|
|||||||
Generated
+1
-1
@@ -1994,7 +1994,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jellytau"
|
name = "jellytau"
|
||||||
version = "0.2.8"
|
version = "0.2.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "jellytau"
|
name = "jellytau"
|
||||||
version = "0.2.8"
|
version = "0.2.9"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -57,18 +57,6 @@ pub struct MediaSessionManagerWrapper(pub Mutex<MediaSessionManager>);
|
|||||||
/// @req: DR-048 - Video settings (auto-play toggle, countdown duration)
|
/// @req: DR-048 - Video settings (auto-play toggle, countdown duration)
|
||||||
pub struct VideoSettingsWrapper(pub Mutex<VideoSettings>);
|
pub struct VideoSettingsWrapper(pub Mutex<VideoSettings>);
|
||||||
|
|
||||||
/// Base offset (seconds) for the active background-audio handoff.
|
|
||||||
///
|
|
||||||
/// The audio-only stream is requested with `StartTimeTicks` = the handoff
|
|
||||||
/// position, so the server makes that point the stream's zero. ExoPlayer then
|
|
||||||
/// reports position RELATIVE to that zero. To convert back to an absolute
|
|
||||||
/// position on exit (so the video resumes where the audio actually reached), we
|
|
||||||
/// add this stored base to the native player's reported position.
|
|
||||||
///
|
|
||||||
/// TRACES: UR-040 | DR-052
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BackgroundAudioOffset(pub Mutex<f64>);
|
|
||||||
|
|
||||||
/// Response for player state queries
|
/// Response for player state queries
|
||||||
#[derive(specta::Type, Debug, Serialize)]
|
#[derive(specta::Type, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -586,7 +574,6 @@ pub async fn player_play_item(
|
|||||||
pub async fn player_enter_background_audio(
|
pub async fn player_enter_background_audio(
|
||||||
player: State<'_, PlayerStateWrapper>,
|
player: State<'_, PlayerStateWrapper>,
|
||||||
session: State<'_, MediaSessionManagerWrapper>,
|
session: State<'_, MediaSessionManagerWrapper>,
|
||||||
bg_offset: State<'_, BackgroundAudioOffset>,
|
|
||||||
item: PlayItemRequest,
|
item: PlayItemRequest,
|
||||||
position_seconds: f64,
|
position_seconds: f64,
|
||||||
) -> Result<PlayerStatus, String> {
|
) -> Result<PlayerStatus, String> {
|
||||||
@@ -636,17 +623,18 @@ pub async fn player_enter_background_audio(
|
|||||||
session_mgr.start_audio_session(media_item.clone());
|
session_mgr.start_audio_session(media_item.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember where the video was: the audio stream's zero == this position
|
|
||||||
// (the URL was built with StartTimeTicks=position_seconds), so on exit we add
|
|
||||||
// this base to the native player's relative position to get the absolute one.
|
|
||||||
*bg_offset.0.lock().map_err(|e| e.to_string())? = position_seconds.max(0.0);
|
|
||||||
|
|
||||||
// Same base offset drives the lockscreen scrubber: ExoPlayer reports position
|
// Same base offset drives the lockscreen scrubber: ExoPlayer reports position
|
||||||
// relative to the stream's StartTimeTicks zero, but the metadata duration is
|
// relative to the stream's StartTimeTicks zero, but the metadata duration is
|
||||||
// absolute, so shift the reported position back to absolute for the scrubber.
|
// absolute, so shift the reported position back to absolute for the scrubber.
|
||||||
let _ = crate::player::set_lockscreen_position_offset(position_seconds.max(0.0));
|
let _ = crate::player::set_lockscreen_position_offset(position_seconds.max(0.0));
|
||||||
|
|
||||||
let controller = player.0.lock().await;
|
let controller = player.0.lock().await;
|
||||||
|
// Remember where the video was: the audio stream's zero == this position
|
||||||
|
// (the URL was built with StartTimeTicks=position_seconds), so on exit we add
|
||||||
|
// this base to the native player's relative position to get the absolute one.
|
||||||
|
// The controller owns it so a backend-driven advance to the next episode
|
||||||
|
// clears it along with the stream it described.
|
||||||
|
controller.set_background_audio_base(position_seconds);
|
||||||
controller
|
controller
|
||||||
.play_item(media_item)
|
.play_item(media_item)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
@@ -677,21 +665,15 @@ pub async fn player_enter_background_audio(
|
|||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn player_exit_background_audio(
|
pub async fn player_exit_background_audio(
|
||||||
player: State<'_, PlayerStateWrapper>,
|
player: State<'_, PlayerStateWrapper>,
|
||||||
bg_offset: State<'_, BackgroundAudioOffset>,
|
|
||||||
) -> Result<f64, String> {
|
) -> Result<f64, String> {
|
||||||
// The base offset (handoff position) + native player's relative position =
|
|
||||||
// the absolute position to resume the video at. Read/reset the base first.
|
|
||||||
let base = {
|
|
||||||
let mut off = bg_offset.0.lock().map_err(|e| e.to_string())?;
|
|
||||||
let b = *off;
|
|
||||||
*off = 0.0;
|
|
||||||
b
|
|
||||||
};
|
|
||||||
|
|
||||||
// Back to foreground playback: the lockscreen scrubber is absolute again.
|
// Back to foreground playback: the lockscreen scrubber is absolute again.
|
||||||
let _ = crate::player::set_lockscreen_position_offset(0.0);
|
let _ = crate::player::set_lockscreen_position_offset(0.0);
|
||||||
|
|
||||||
let controller = player.0.lock().await;
|
let controller = player.0.lock().await;
|
||||||
|
// The base offset (handoff position) + native player's relative position =
|
||||||
|
// the absolute position to resume the video at. Zero after a backend-driven
|
||||||
|
// episode advance, whose stream already starts at its own zero.
|
||||||
|
let base = controller.take_background_audio_base();
|
||||||
// Capture position into a `let` BEFORE stop() — never hold work across a lock
|
// Capture position into a `let` BEFORE stop() — never hold work across a lock
|
||||||
// re-entrant call (deadlock discipline, CLAUDE.md).
|
// re-entrant call (deadlock discipline, CLAUDE.md).
|
||||||
let relative = controller.position();
|
let relative = controller.position();
|
||||||
|
|||||||
@@ -141,6 +141,8 @@ pub async fn player_play_next_episode(
|
|||||||
/// - Frontend when HTML5 video ends (Linux/desktop) - passes itemId + repositoryHandle for the video
|
/// - Frontend when HTML5 video ends (Linux/desktop) - passes itemId + repositoryHandle for the video
|
||||||
/// - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed
|
/// - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed
|
||||||
/// - Android JNI callback also triggers this logic directly
|
/// - Android JNI callback also triggers this logic directly
|
||||||
|
///
|
||||||
|
/// TRACES: UR-023, UR-026, UR-040 | DR-047, DR-052
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn player_on_playback_ended(
|
pub async fn player_on_playback_ended(
|
||||||
@@ -242,12 +244,17 @@ pub async fn player_on_playback_ended(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start countdown if auto_advance enabled
|
// Advance if auto_advance is enabled. This is the path that actually
|
||||||
|
// runs on Android: the JNI callback's own decision is swallowed by the
|
||||||
|
// NewTrackLoaded end reason set at load, so it returns Stop, emits
|
||||||
|
// PlaybackEnded, and the frontend echoes it back into this command —
|
||||||
|
// which is where the real decision lands.
|
||||||
if auto_advance {
|
if auto_advance {
|
||||||
controller_arc
|
controller_arc
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.start_autoplay_countdown(next_episode, countdown_seconds);
|
.auto_advance_to_next_episode(next_episode, countdown_seconds)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1196,9 +1196,6 @@ pub fn run() {
|
|||||||
let video_settings = VideoSettingsWrapper(Mutex::new(VideoSettings::default()));
|
let video_settings = VideoSettingsWrapper(Mutex::new(VideoSettings::default()));
|
||||||
app.manage(video_settings);
|
app.manage(video_settings);
|
||||||
|
|
||||||
// Background-audio handoff base offset (UR-040).
|
|
||||||
app.manage(commands::player::BackgroundAudioOffset::default());
|
|
||||||
|
|
||||||
// Initialize thumbnail cache
|
// Initialize thumbnail cache
|
||||||
info!("[INIT] Initializing thumbnail cache...");
|
info!("[INIT] Initializing thumbnail cache...");
|
||||||
let app_data_dir = if let Ok(test_data_dir) = std::env::var("JELLYTAU_DATA_DIR") {
|
let app_data_dir = if let Ok(test_data_dir) = std::env::var("JELLYTAU_DATA_DIR") {
|
||||||
|
|||||||
@@ -915,39 +915,19 @@ pub extern "system" fn Java_com_dtourolle_jellytau_player_JellyTauPlayer_nativeO
|
|||||||
}
|
}
|
||||||
|
|
||||||
if auto_advance {
|
if auto_advance {
|
||||||
// Background audio-only episode: the frontend that normally
|
// Shared with the frontend-invoked command path
|
||||||
// performs the advance (goto /player/<id>) is suspended, so
|
// (player_on_playback_ended) so the two dispatchers cannot
|
||||||
// the backend must load the next episode's audio-only stream
|
// disagree about how a background audio-only episode
|
||||||
// itself — otherwise playback just stops at the boundary.
|
// advances — they did, and the command's copy was missing
|
||||||
let is_bg_audio_episode =
|
// the case entirely. That copy is the one that actually
|
||||||
controller.lock().await.current_is_audio_episode();
|
// decides here: the end reason set at load makes this
|
||||||
if is_bg_audio_episode {
|
// callback's own decision Stop, and the frontend echoes the
|
||||||
log::info!(
|
// resulting PlaybackEnded back into the command.
|
||||||
"[Autoplay] Background audio episode — advancing to {} in backend",
|
controller
|
||||||
next_episode.id
|
.lock()
|
||||||
);
|
.await
|
||||||
let ctrl = controller.lock().await;
|
.auto_advance_to_next_episode(next_episode, countdown_seconds)
|
||||||
if let Err(e) = ctrl
|
.await;
|
||||||
.advance_to_next_episode_audio_only(&next_episode.id)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
log::error!(
|
|
||||||
"[Autoplay] Background audio advance failed: {} — stopping",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
if let Some(emitter) = EVENT_EMITTER.get() {
|
|
||||||
emitter.emit(PlayerStatusEvent::PlaybackEnded);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctrl.emit_queue_changed();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Foreground: frontend drives the advance off the countdown.
|
|
||||||
controller
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.start_autoplay_countdown(next_episode, countdown_seconds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
+202
-4
@@ -105,7 +105,7 @@ pub fn set_lockscreen_position_offset(_offset_seconds: f64) -> Result<(), String
|
|||||||
}
|
}
|
||||||
|
|
||||||
use crate::utils::lock::MutexSafe;
|
use crate::utils::lock::MutexSafe;
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, info, warn};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::Mutex as TokioMutex;
|
use tokio::sync::Mutex as TokioMutex;
|
||||||
@@ -153,6 +153,21 @@ pub struct PlayerController {
|
|||||||
// Auto-play episode counter (session-based, resets on manual play)
|
// Auto-play episode counter (session-based, resets on manual play)
|
||||||
autoplay_episode_count: Arc<Mutex<u32>>,
|
autoplay_episode_count: Arc<Mutex<u32>>,
|
||||||
|
|
||||||
|
// Base offset (seconds) of the active background-audio handoff.
|
||||||
|
//
|
||||||
|
// The audio-only stream is requested with `StartTimeTicks` = the position the
|
||||||
|
// video was handed off at, so the server makes that point the stream's zero
|
||||||
|
// and the native player reports position RELATIVE to it. Adding this base back
|
||||||
|
// yields the absolute position to resume the video at on the way out.
|
||||||
|
//
|
||||||
|
// Lives on the controller (not beside the command) because the queue and this
|
||||||
|
// offset describe the same stream: whenever the controller loads a different
|
||||||
|
// one — notably the backend-driven advance to the next episode — the base has
|
||||||
|
// to move with it.
|
||||||
|
//
|
||||||
|
// TRACES: UR-040 | DR-052
|
||||||
|
background_audio_base: Arc<Mutex<f64>>,
|
||||||
|
|
||||||
// Last state reported by a webview-rendered HTML5 <video>/<audio> element.
|
// Last state reported by a webview-rendered HTML5 <video>/<audio> element.
|
||||||
//
|
//
|
||||||
// Webview-rendered media is played by an element the native backend cannot
|
// Webview-rendered media is played by an element the native backend cannot
|
||||||
@@ -185,6 +200,7 @@ impl PlayerController {
|
|||||||
position_throttler,
|
position_throttler,
|
||||||
end_reason: Arc::new(Mutex::new(None)),
|
end_reason: Arc::new(Mutex::new(None)),
|
||||||
autoplay_episode_count: Arc::new(Mutex::new(0)),
|
autoplay_episode_count: Arc::new(Mutex::new(0)),
|
||||||
|
background_audio_base: Arc::new(Mutex::new(0.0)),
|
||||||
html5_playing: Arc::new(Mutex::new(None)),
|
html5_playing: Arc::new(Mutex::new(None)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1170,6 +1186,68 @@ impl PlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Record the base offset of a background-audio handoff (the position the
|
||||||
|
/// video was handed off at, which is the audio stream's zero).
|
||||||
|
///
|
||||||
|
/// TRACES: UR-040 | DR-052
|
||||||
|
pub fn set_background_audio_base(&self, seconds: f64) {
|
||||||
|
*self.background_audio_base.lock_safe() = seconds.max(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read and clear the background-audio base offset.
|
||||||
|
///
|
||||||
|
/// TRACES: UR-040 | DR-052
|
||||||
|
pub fn take_background_audio_base(&self) -> f64 {
|
||||||
|
let mut base = self.background_audio_base.lock_safe();
|
||||||
|
std::mem::replace(&mut *base, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform the auto-advance for a `ShowNextEpisodePopup` decision.
|
||||||
|
///
|
||||||
|
/// Single place both end-of-playback dispatchers agree on: the Android JNI
|
||||||
|
/// callback (`nativeOnPlaybackEnded`) and the frontend-invoked command
|
||||||
|
/// (`player_on_playback_ended`). They used to each carry their own copy of
|
||||||
|
/// this branch, and the command's copy was missing the background-audio case
|
||||||
|
/// entirely — so an audio-only episode ending while backgrounded only ever
|
||||||
|
/// started a countdown that nothing could act on.
|
||||||
|
///
|
||||||
|
/// TRACES: UR-040, UR-023 | DR-052
|
||||||
|
pub async fn auto_advance_to_next_episode(
|
||||||
|
&self,
|
||||||
|
next_episode: crate::repository::types::MediaItem,
|
||||||
|
countdown_seconds: u32,
|
||||||
|
) {
|
||||||
|
// Background audio-only episode: the countdown only emits ticks — the
|
||||||
|
// advance itself is a `goto('/player/<id>')` in the webview, which cannot
|
||||||
|
// start audio while the app is backgrounded. Load the next episode's
|
||||||
|
// audio-only stream here instead, or playback stalls at the boundary.
|
||||||
|
if self.current_is_audio_episode() {
|
||||||
|
info!(
|
||||||
|
"[PlayerController] Background audio episode — advancing to {} in backend",
|
||||||
|
next_episode.id
|
||||||
|
);
|
||||||
|
match self
|
||||||
|
.advance_to_next_episode_audio_only(&next_episode.id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => self.emit_queue_changed(),
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"[PlayerController] Background audio advance failed: {} — stopping",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
if let Some(emitter) = self.event_emitter() {
|
||||||
|
emitter.emit(PlayerStatusEvent::PlaybackEnded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreground: the frontend drives the advance off the countdown ticks.
|
||||||
|
self.start_autoplay_countdown(next_episode, countdown_seconds);
|
||||||
|
}
|
||||||
|
|
||||||
/// Advance to the next episode while playing audio-only in the background.
|
/// Advance to the next episode while playing audio-only in the background.
|
||||||
///
|
///
|
||||||
/// The normal autoplay-next path navigates the frontend to `/player/<id>`,
|
/// The normal autoplay-next path navigates the frontend to `/player/<id>`,
|
||||||
@@ -1180,10 +1258,10 @@ impl PlayerController {
|
|||||||
///
|
///
|
||||||
/// `next_episode_id` is the Jellyfin item ID of the episode to play next.
|
/// `next_episode_id` is the Jellyfin item ID of the episode to play next.
|
||||||
///
|
///
|
||||||
/// Called from the Android autoplay dispatch (`#[cfg(android)]`); compiled and
|
/// Reached through `auto_advance_to_next_episode`, which gates it on
|
||||||
/// unit-tested on the host, hence `allow(dead_code)` off-Android.
|
/// `current_is_audio_episode()` — only ever true after a background-audio
|
||||||
|
/// handoff (Android), but compiled and unit-tested on every platform.
|
||||||
/// TRACES: UR-040, UR-023 | DR-052
|
/// TRACES: UR-040, UR-023 | DR-052
|
||||||
#[cfg_attr(not(target_os = "android"), allow(dead_code))]
|
|
||||||
pub async fn advance_to_next_episode_audio_only(
|
pub async fn advance_to_next_episode_audio_only(
|
||||||
&self,
|
&self,
|
||||||
next_episode_id: &str,
|
next_episode_id: &str,
|
||||||
@@ -1238,6 +1316,13 @@ impl PlayerController {
|
|||||||
server_id: Some(next.server_id.clone()),
|
server_id: Some(next.server_id.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The previous episode's handoff base described the stream we are leaving.
|
||||||
|
// This one is built without StartTimeTicks, so its timeline is already
|
||||||
|
// absolute: clear the base (used to resolve the resume position on the way
|
||||||
|
// back to the foreground) and the lockscreen scrubber's matching shift.
|
||||||
|
self.set_background_audio_base(0.0);
|
||||||
|
let _ = set_lockscreen_position_offset(0.0);
|
||||||
|
|
||||||
self.play_item(media_item).map_err(|e| e.to_string())
|
self.play_item(media_item).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2994,6 +3079,119 @@ mod tests {
|
|||||||
assert!(controller.current_is_audio_episode());
|
assert!(controller.current_is_audio_episode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The handoff base offset describes ONE stream: the audio-only URL built
|
||||||
|
/// with `StartTimeTicks` = the position the video was handed off at, whose
|
||||||
|
/// timeline therefore starts at that point. The next episode is loaded from
|
||||||
|
/// its own beginning, so its timeline is already absolute and the base must
|
||||||
|
/// be cleared — otherwise returning to the foreground resolves the resume
|
||||||
|
/// position as `old_base + position_in_new_episode` and the video jumps to a
|
||||||
|
/// point that has nothing to do with what was playing.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_advance_to_next_episode_audio_only_clears_handoff_base() {
|
||||||
|
let controller = PlayerController::default();
|
||||||
|
controller.set_repository(Arc::new(MockEpisodeRepo::season(3)));
|
||||||
|
|
||||||
|
// Handed off 20 minutes into the previous episode.
|
||||||
|
controller.set_background_audio_base(1200.0);
|
||||||
|
|
||||||
|
controller
|
||||||
|
.advance_to_next_episode_audio_only("ep2")
|
||||||
|
.await
|
||||||
|
.expect("advance should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
controller.take_background_audio_base(),
|
||||||
|
0.0,
|
||||||
|
"the next episode starts at its own zero, so the previous handoff \
|
||||||
|
base must not survive the advance"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A background audio-only episode must advance IN THE BACKEND when the
|
||||||
|
/// autoplay decision comes back as ShowNextEpisodePopup — never by starting a
|
||||||
|
/// countdown the frontend is supposed to act on.
|
||||||
|
///
|
||||||
|
/// The countdown only emits CountdownTick events; the actual advance is a
|
||||||
|
/// `goto('/player/<id>')` in the webview. While the app is backgrounded that
|
||||||
|
/// navigation cannot start audio, so playback stalls at the episode boundary
|
||||||
|
/// with ExoPlayer parked in STATE_ENDED — and any later play intent
|
||||||
|
/// (lockscreen, headset, Bluetooth reconnect) replays the ended item from the
|
||||||
|
/// start, which is what surfaces to the user as "the episode randomly
|
||||||
|
/// restarted".
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_auto_advance_background_audio_episode_advances_in_backend() {
|
||||||
|
let controller = PlayerController::default();
|
||||||
|
controller.set_repository(Arc::new(MockEpisodeRepo::season(3)));
|
||||||
|
|
||||||
|
// Currently playing: ep2 handed off to audio-only background playback.
|
||||||
|
let episode = MediaItem {
|
||||||
|
id: "ep2".to_string(),
|
||||||
|
item_type: Some("Episode".to_string()),
|
||||||
|
media_type: MediaType::Audio,
|
||||||
|
series_id: Some("series1".to_string()),
|
||||||
|
source: MediaSource::Remote {
|
||||||
|
stream_url: "http://example.com/ep2-audio.mp3".to_string(),
|
||||||
|
jellyfin_item_id: "ep2".to_string(),
|
||||||
|
},
|
||||||
|
..create_test_items(1).remove(0)
|
||||||
|
};
|
||||||
|
controller.play_queue(vec![episode], 0).unwrap();
|
||||||
|
|
||||||
|
let next = make_repo_episode("ep3", 3);
|
||||||
|
controller.auto_advance_to_next_episode(next, 10).await;
|
||||||
|
|
||||||
|
let current = controller
|
||||||
|
.queue
|
||||||
|
.lock_safe()
|
||||||
|
.current()
|
||||||
|
.cloned()
|
||||||
|
.expect("an item should still be loaded");
|
||||||
|
assert_eq!(
|
||||||
|
current.id, "ep3",
|
||||||
|
"background audio-only episode must advance in the backend, not wait \
|
||||||
|
for a frontend navigation that cannot happen while backgrounded"
|
||||||
|
);
|
||||||
|
assert_eq!(current.media_type, MediaType::Audio);
|
||||||
|
assert!(controller.current_is_audio_episode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Foreground video playback keeps the countdown-driven advance: the frontend
|
||||||
|
/// owns the navigation there, so the backend must NOT load the next episode
|
||||||
|
/// itself (that would race the page transition and double-start playback).
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_auto_advance_foreground_video_episode_uses_countdown() {
|
||||||
|
let controller = PlayerController::default();
|
||||||
|
controller.set_repository(Arc::new(MockEpisodeRepo::season(3)));
|
||||||
|
|
||||||
|
let episode = MediaItem {
|
||||||
|
id: "ep2".to_string(),
|
||||||
|
item_type: Some("Episode".to_string()),
|
||||||
|
media_type: MediaType::Video,
|
||||||
|
series_id: Some("series1".to_string()),
|
||||||
|
source: MediaSource::Remote {
|
||||||
|
stream_url: "http://example.com/ep2.m3u8".to_string(),
|
||||||
|
jellyfin_item_id: "ep2".to_string(),
|
||||||
|
},
|
||||||
|
..create_test_items(1).remove(0)
|
||||||
|
};
|
||||||
|
controller.play_queue(vec![episode], 0).unwrap();
|
||||||
|
|
||||||
|
let next = make_repo_episode("ep3", 3);
|
||||||
|
controller.auto_advance_to_next_episode(next, 10).await;
|
||||||
|
|
||||||
|
let current = controller
|
||||||
|
.queue
|
||||||
|
.lock_safe()
|
||||||
|
.current()
|
||||||
|
.cloned()
|
||||||
|
.expect("an item should still be loaded");
|
||||||
|
assert_eq!(
|
||||||
|
current.id, "ep2",
|
||||||
|
"foreground video advance is frontend-driven; the backend must not \
|
||||||
|
swap the queue item out from under it"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Without a controller repository the Android episode path must still
|
/// Without a controller repository the Android episode path must still
|
||||||
/// stop gracefully (previous behavior) rather than error.
|
/// stop gracefully (previous behavior) rather than error.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "jellytau",
|
"productName": "jellytau",
|
||||||
"version": "0.2.8",
|
"version": "0.2.9",
|
||||||
"identifier": "com.dtourolle.jellytau",
|
"identifier": "com.dtourolle.jellytau",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
|||||||
@@ -237,6 +237,8 @@ async playerPlayNextEpisode(item: PlayItemRequest) : Promise<PlayerStatus> {
|
|||||||
* - Frontend when HTML5 video ends (Linux/desktop) - passes itemId + repositoryHandle for the video
|
* - Frontend when HTML5 video ends (Linux/desktop) - passes itemId + repositoryHandle for the video
|
||||||
* - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed
|
* - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed
|
||||||
* - Android JNI callback also triggers this logic directly
|
* - Android JNI callback also triggers this logic directly
|
||||||
|
*
|
||||||
|
* TRACES: UR-023, UR-026, UR-040 | DR-047, DR-052
|
||||||
*/
|
*/
|
||||||
async playerOnPlaybackEnded(itemId: string | null, repositoryHandle: string | null) : Promise<null> {
|
async playerOnPlaybackEnded(itemId: string | null, repositoryHandle: string | null) : Promise<null> {
|
||||||
return await TAURI_INVOKE("player_on_playback_ended", { itemId, repositoryHandle });
|
return await TAURI_INVOKE("player_on_playback_ended", { itemId, repositoryHandle });
|
||||||
|
|||||||
Reference in New Issue
Block a user