fix(player): re-entering a video no longer opens the audio player (DR-100)
Leaving a video and returning to it rendered the movie/episode in AudioPlayer. Closing a webview-rendered video deliberately emits no "stopped" state (that would break the autoplay handoff), and the direct-play path does not stop the backend on unmount, so the Rust controller still reported that item as its loaded media. Re-entering the route therefore took the "already playing, just show the UI" shortcut, which returns before a stream URL is fetched, and the render fell through to the audio surface. Mostly visible on Android, where video direct-plays; Linux transcodes and stops the backend on unmount. Both decisions move into playerSurface.ts as pure functions: shouldReuseActivePlayback excludes video, so video always takes the full load path and gets its stream URL and resume position; resolvePlayerSurface maps video-without-a-stream-URL to "pending" (spinner) rather than falling through to audio.
This commit is contained in:
@@ -253,6 +253,7 @@ Internal architecture, components, and application logic.
|
|||||||
| DR-097 | Transport authority (play/pause/toggle) lives in Rust for **webview-rendered** media, not just native. The controller tracks the state the HTML5 element reports (`html5_playing`, fed by `report_html5_state`, which now *stores* rather than only re-emitting); `play`/`pause`/`toggle_playback` consult it and drive the element by emitting a `ControlCommand` that `playerEvents.handleControlCommand` executes against the active adapter. A `stopped`/`idle` report clears it so the native backend (MPV/ExoPlayer) regains authority for music. The frontend facade no longer short-circuits transport into the adapter: `adapter.toggle()` previously decided play-vs-pause by reading `el.paused` off the DOM, a value that flips transiently while an element buffers or settles a seek — so two intents ~150 ms apart read *different* values, performed *opposing* actions, and self-sustained a play/pause loop needing no further input (observed on Android with a fully-buffered `readyState=4 networkState=1` element). Same "backend decides, adapter executes the primitive" split as `player_seek_video` | Player | UR-005 | Done |
|
| DR-097 | Transport authority (play/pause/toggle) lives in Rust for **webview-rendered** media, not just native. The controller tracks the state the HTML5 element reports (`html5_playing`, fed by `report_html5_state`, which now *stores* rather than only re-emitting); `play`/`pause`/`toggle_playback` consult it and drive the element by emitting a `ControlCommand` that `playerEvents.handleControlCommand` executes against the active adapter. A `stopped`/`idle` report clears it so the native backend (MPV/ExoPlayer) regains authority for music. The frontend facade no longer short-circuits transport into the adapter: `adapter.toggle()` previously decided play-vs-pause by reading `el.paused` off the DOM, a value that flips transiently while an element buffers or settles a seek — so two intents ~150 ms apart read *different* values, performed *opposing* actions, and self-sustained a play/pause loop needing no further input (observed on Android with a fully-buffered `readyState=4 networkState=1` element). Same "backend decides, adapter executes the primitive" split as `player_seek_video` | Player | UR-005 | Done |
|
||||||
| DR-096 | `Html5PlayerAdapter.play()` is resilient to stall recovery: an in-flight attempt is memoised so concurrent callers (UI plus hls.js gap-controller recovery) share one `element.play()` instead of stacking calls, and an `AbortError` ("play() request was interrupted by a call to pause()") is logged at debug rather than pushed to `host.onError`. The browser raises it whenever a pending play promise is superseded by a pause/seek/source change, which hls.js does routinely while nudging past a stall — reporting it surfaced a player error roughly once per second for the whole stall and left the UI stuck showing paused | Player | UR-005 | Done |
|
| DR-096 | `Html5PlayerAdapter.play()` is resilient to stall recovery: an in-flight attempt is memoised so concurrent callers (UI plus hls.js gap-controller recovery) share one `element.play()` instead of stacking calls, and an `AbortError` ("play() request was interrupted by a call to pause()") is logged at debug rather than pushed to `host.onError`. The browser raises it whenever a pending play promise is superseded by a pause/seek/source change, which hls.js does routinely while nudging past a stall — reporting it surfaced a player error roughly once per second for the whole stall and left the UI stuck showing paused | Player | UR-005 | Done |
|
||||||
| DR-095 | Seek targets clamp strictly *inside* the media (`clampSeekTarget`, `END_SEEK_MARGIN_SECONDS` = 6 s ≈ one HLS segment) instead of to the exact `duration`. Landing on the duration makes hls.js request the segment whose start time lies past the end of the media (e.g. a 6330.324 s item → segment 1055 starting at 6336.33 s), which Jellyfin never produces; the fetch times out and hls.js' gap-controller stalls at the last buffered position, presenting as "unpausing or skipping bounces straight back to paused". Applied on both seek paths — the relative-skip `resolveSeekTarget` and the seek-bar drag, whose range input `max` is the duration itself — and floored at 0 so media shorter than the margin still seeks to the start | UI | UR-061 | Done |
|
| DR-095 | Seek targets clamp strictly *inside* the media (`clampSeekTarget`, `END_SEEK_MARGIN_SECONDS` = 6 s ≈ one HLS segment) instead of to the exact `duration`. Landing on the duration makes hls.js request the segment whose start time lies past the end of the media (e.g. a 6330.324 s item → segment 1055 starting at 6336.33 s), which Jellyfin never produces; the fetch times out and hls.js' gap-controller stalls at the last buffered position, presenting as "unpausing or skipping bounces straight back to paused". Applied on both seek paths — the relative-skip `resolveSeekTarget` and the seek-bar drag, whose range input `max` is the duration itself — and floored at 0 so media shorter than the margin still seeks to the start | UI | UR-061 | Done |
|
||||||
|
| DR-100 | Leaving a video and re-entering it renders the **video** player, never the audio one. Both halves of the `/player/[id]` decision are pure and unit-tested in `playerSurface.ts`. (a) `shouldReuseActivePlayback` excludes video: the "already playing, just show the UI" shortcut (added for expanding the audio mini player) returns *before* a stream URL is fetched, which is fine for audio — the backend owns the stream and the route only mirrors it — but leaves `<VideoPlayer>` with nothing to render. Closing a webview-rendered video deliberately emits no `stopped` state (that would break the autoplay handoff, see DR-047), so the Rust controller still reports that movie/episode as its loaded media and re-entering the same item hit the shortcut. (b) `resolvePlayerSurface` maps video-without-a-stream-URL to `pending` (spinner) instead of falling through to `<AudioPlayer>`, so no future path can put video content in the audio surface. Video now always takes the full load path, which fetches the stream URL and applies the stored resume position | UI | UR-005 | Done |
|
||||||
| DR-093 | Traceability coverage gate derives its requirement denominators from `requirements.md` at run time rather than hardcoded literals: `countDefinedRequirements` counts an ID only where it leads a markdown table row (ignoring the "Traces To" column and prose) and deduplicates IDs listed both in the definition tables and in the §3 traceability matrix; `computeCoverage` reports the *intersection* of traced and defined IDs so an ID traced in code but absent from `requirements.md` is surfaced as `orphaned` instead of inflating the ratio past 100%. UT/IT test identifiers are excluded as a separate taxonomy. CI and `bun run traces:coverage` share this computation and fail on both a sub-threshold and an impossible >100% result | Tooling | - | Done |
|
| DR-093 | Traceability coverage gate derives its requirement denominators from `requirements.md` at run time rather than hardcoded literals: `countDefinedRequirements` counts an ID only where it leads a markdown table row (ignoring the "Traces To" column and prose) and deduplicates IDs listed both in the definition tables and in the §3 traceability matrix; `computeCoverage` reports the *intersection* of traced and defined IDs so an ID traced in code but absent from `requirements.md` is surfaced as `orphaned` instead of inflating the ratio past 100%. UT/IT test identifiers are excluded as a separate taxonomy. CI and `bun run traces:coverage` share this computation and fail on both a sub-threshold and an impossible >100% result | Tooling | - | Done |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -420,6 +421,8 @@ Internal architecture, components, and application logic.
|
|||||||
| UT-089 | A touch drag on the video seek bar seeks to the dragged position, never toggles play/pause, and never alters brightness — the container gesture layer stays out of a control drag entirely | DR-098, DR-099 | Done |
|
| UT-089 | A touch drag on the video seek bar seeks to the dragged position, never toggles play/pause, and never alters brightness — the container gesture layer stays out of a control drag entirely | DR-098, DR-099 | Done |
|
||||||
| UT-090 | The seek bar commits its seek on `touchend` even when the engine never fires `change`, and commits exactly once when both signals arrive | DR-099 | Done |
|
| UT-090 | The seek bar commits its seek on `touchend` even when the engine never fires `change`, and commits exactly once when both signals arrive | DR-099 | Done |
|
||||||
| UT-091 | Transport intents (play/pause/toggle) reach the backend even while a video adapter is registered, and never call the adapter's own `play`/`pause`/`toggle` — the webview must not decide play-vs-pause from the DOM | DR-097 | Done |
|
| UT-091 | Transport intents (play/pause/toggle) reach the backend even while a video adapter is registered, and never call the adapter's own `play`/`pause`/`toggle` — the webview must not decide play-vs-pause from the DOM | DR-097 | Done |
|
||||||
|
| UT-092 | `shouldReuseActivePlayback` reuses backend playback for an already-loaded audio track but never for video, and never when an explicit start position or a next-episode restart was requested | DR-100 | Done |
|
||||||
|
| UT-093 | `resolvePlayerSurface` returns `video` only with a stream URL, `pending` for video whose stream URL is still missing (never `audio`), and `audio` for audio content | DR-100 | Done |
|
||||||
|
|
||||||
### Integration Tests
|
### Integration Tests
|
||||||
|
|
||||||
|
|||||||
+296
-262
@@ -1,15 +1,15 @@
|
|||||||
# Code Traceability Matrix
|
# Code Traceability Matrix
|
||||||
|
|
||||||
**Generated:** 8/2/2026, 6:10:09 PM
|
**Generated:** 8/3/2026, 6:07:57 PM
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- **Total Files Scanned:** 305
|
- **Total Files Scanned:** 307
|
||||||
- **Total TRACES Found:** 338
|
- **Total TRACES Found:** 340
|
||||||
- **Requirements Covered:**
|
- **Requirements Covered:**
|
||||||
- User Requirements (UR): 58
|
- User Requirements (UR): 58
|
||||||
- Integration Requirements (IR): 15
|
- Integration Requirements (IR): 15
|
||||||
- Development Requirements (DR): 90
|
- Development Requirements (DR): 91
|
||||||
- Jellyfin API Requirements (JA): 24
|
- Jellyfin API Requirements (JA): 24
|
||||||
|
|
||||||
## Requirements by Type
|
## Requirements by Type
|
||||||
@@ -26,7 +26,7 @@ IR-003, IR-004, IR-009, IR-010, IR-011, IR-012, IR-013, IR-014, IR-015, IR-020,
|
|||||||
|
|
||||||
### Development Requirements (DR)
|
### 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-022, DR-023, DR-024, DR-025, DR-026, DR-027, DR-028, DR-029, DR-030, DR-032, DR-033, DR-034, DR-035, DR-036, DR-037, DR-038, DR-039, DR-040, DR-041, DR-043, 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, DR-061, DR-062, DR-063, DR-064, DR-065, DR-066, DR-067, DR-068, DR-069, DR-070, DR-074, DR-075, DR-076, DR-077, DR-078, DR-079, DR-080, DR-081, DR-082, DR-083, DR-084, DR-085, DR-086, DR-087, DR-088, DR-089, DR-090, DR-091, DR-092, DR-093, DR-095, DR-096, DR-097, DR-098, DR-099
|
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-030, DR-032, DR-033, DR-034, DR-035, DR-036, DR-037, DR-038, DR-039, DR-040, DR-041, DR-043, 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, DR-061, DR-062, DR-063, DR-064, DR-065, DR-066, DR-067, DR-068, DR-069, DR-070, DR-074, DR-075, DR-076, DR-077, DR-078, DR-079, DR-080, DR-081, DR-082, DR-083, DR-084, DR-085, DR-086, DR-087, DR-088, DR-089, DR-090, DR-091, DR-092, DR-093, DR-095, DR-096, DR-097, DR-098, DR-099, DR-100
|
||||||
```
|
```
|
||||||
|
|
||||||
### Jellyfin API Requirements (JA)
|
### Jellyfin API Requirements (JA)
|
||||||
@@ -2004,6 +2004,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 21
|
- **Line:** 21
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### DR-100
|
||||||
|
|
||||||
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.test.ts`](src/lib/components/player/playerSurface.test.ts#L13)
|
||||||
|
- **Line:** 13
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.ts`](src/lib/components/player/playerSurface.ts#L8)
|
||||||
|
- **Line:** 8
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### JA-001
|
### JA-001
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -2468,7 +2479,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
### UR-005
|
### UR-005
|
||||||
|
|
||||||
**Locations:** 66 file(s)
|
**Locations:** 68 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2258)
|
- **File:** [`src/lib/api/bindings.ts`](src/lib/api/bindings.ts#L2258)
|
||||||
- **Line:** 2258
|
- **Line:** 2258
|
||||||
@@ -2503,6 +2514,12 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **File:** [`src/lib/components/player/VideoPlayer.touchScrub.test.ts`](src/lib/components/player/VideoPlayer.touchScrub.test.ts#L21)
|
- **File:** [`src/lib/components/player/VideoPlayer.touchScrub.test.ts`](src/lib/components/player/VideoPlayer.touchScrub.test.ts#L21)
|
||||||
- **Line:** 21
|
- **Line:** 21
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.test.ts`](src/lib/components/player/playerSurface.test.ts#L13)
|
||||||
|
- **Line:** 13
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.ts`](src/lib/components/player/playerSurface.ts#L8)
|
||||||
|
- **Line:** 8
|
||||||
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/components/BottomUi.svelte`](src/lib/components/BottomUi.svelte#L16)
|
- **File:** [`src/lib/components/BottomUi.svelte`](src/lib/components/BottomUi.svelte#L16)
|
||||||
- **Line:** 16
|
- **Line:** 16
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
@@ -2807,14 +2824,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 441
|
- **Line:** 441
|
||||||
- **Context:** `Unknown`
|
- **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-017
|
### UR-017
|
||||||
|
|
||||||
**Locations:** 9 file(s)
|
**Locations:** 9 file(s)
|
||||||
@@ -2965,6 +2974,59 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UR-025
|
||||||
|
|
||||||
|
**Locations:** 16 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/skipReporting.ts`](src/lib/services/skipReporting.ts#L14)
|
||||||
|
- **Line:** 14
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/services/skipReporting.test.ts`](src/lib/services/skipReporting.test.ts#L9)
|
||||||
|
- **Line:** 9
|
||||||
|
- **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/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/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#L157)
|
||||||
|
- **Line:** 157
|
||||||
|
- **Context:** `pub fn file_size(&self) -> Option<u64> {`
|
||||||
|
|
||||||
### UR-026
|
### UR-026
|
||||||
|
|
||||||
**Locations:** 19 file(s)
|
**Locations:** 19 file(s)
|
||||||
@@ -3617,6 +3679,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 37
|
- **Line:** 37
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### IT-003
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/player/mpv_backend_test.rs`](src-tauri/src/player/mpv_backend_test.rs#L8)
|
||||||
|
- **Line:** 8
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### IT-004
|
### IT-004
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -3650,15 +3720,15 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 2396
|
- **Line:** 2396
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### IT-017
|
### UT-003
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L441)
|
- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535)
|
||||||
- **Line:** 441
|
- **Line:** 535
|
||||||
- **Context:** `Unknown`
|
- **Context:** `pub enum AddPosition {`
|
||||||
|
|
||||||
### UT-003
|
### UT-004
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
@@ -3682,16 +3752,13 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 2
|
- **Line:** 2
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-014
|
### UT-012
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311)
|
- **File:** [`src-tauri/src/player/sleep_timer.rs`](src-tauri/src/player/sleep_timer.rs#L81)
|
||||||
- **Line:** 311
|
- **Line:** 81
|
||||||
- **Context:** `Unknown`
|
- **Context:** `pub fn cancel(&mut self) {`
|
||||||
- **File:** [`src-tauri/src/storage/mod.rs`](src-tauri/src/storage/mod.rs#L157)
|
|
||||||
- **Line:** 157
|
|
||||||
- **Context:** `pub fn file_size(&self) -> Option<u64> {`
|
|
||||||
|
|
||||||
### UT-015
|
### UT-015
|
||||||
|
|
||||||
@@ -3719,17 +3786,6 @@ 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-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<u64> {`
|
|
||||||
|
|
||||||
### UT-018
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311)
|
- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311)
|
||||||
- **Line:** 311
|
- **Line:** 311
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
@@ -3763,17 +3819,6 @@ 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-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<u64> {`
|
|
||||||
|
|
||||||
### UT-022
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311)
|
- **File:** [`src-tauri/src/storage/db_service.rs`](src-tauri/src/storage/db_service.rs#L311)
|
||||||
- **Line:** 311
|
- **Line:** 311
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
@@ -3830,7 +3875,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 245
|
- **Line:** 245
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-028
|
### UT-029
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
@@ -3838,7 +3883,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 245
|
- **Line:** 245
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-029
|
### UT-030
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
@@ -3850,14 +3895,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **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#L245)
|
- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245)
|
||||||
- **Line:** 245
|
- **Line:** 245
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
@@ -3870,12 +3907,12 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 245
|
- **Line:** 245
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-051
|
### UT-044
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](src-tauri/src/repository/offline.rs#L2900)
|
- **File:** [`src-tauri/src/commands/offline.rs`](src-tauri/src/commands/offline.rs#L136)
|
||||||
- **Line:** 2900
|
- **Line:** 136
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-052
|
### UT-052
|
||||||
@@ -3886,20 +3923,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 315
|
- **Line:** 315
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### 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#L2109)
|
|
||||||
- **Line:** 2109
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2150)
|
|
||||||
- **Line:** 2150
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-060
|
### UT-060
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -3968,14 +3991,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 7
|
- **Line:** 7
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-067
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](src-tauri/src/repository/offline.rs#L2396)
|
|
||||||
- **Line:** 2396
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-071
|
### UT-071
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -4043,11 +4058,73 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
|
|
||||||
### UT-092
|
### UT-092
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 3 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/components/player/VideoPlayer.tapSurface.test.ts`](src/lib/components/player/VideoPlayer.tapSurface.test.ts#L5)
|
- **File:** [`src/lib/components/player/VideoPlayer.tapSurface.test.ts`](src/lib/components/player/VideoPlayer.tapSurface.test.ts#L5)
|
||||||
- **Line:** 5
|
- **Line:** 5
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.test.ts`](src/lib/components/player/playerSurface.test.ts#L13)
|
||||||
|
- **Line:** 13
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.ts`](src/lib/components/player/playerSurface.ts#L8)
|
||||||
|
- **Line:** 8
|
||||||
|
- **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-020
|
||||||
|
|
||||||
|
**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/types.ts`](src/lib/player/adapters/types.ts#L15)
|
||||||
|
- **Line:** 15
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19)
|
||||||
|
- **Line:** 19
|
||||||
|
- **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:** 5 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/types.ts`](src/lib/player/adapters/types.ts#L15)
|
||||||
|
- **Line:** 15
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19)
|
||||||
|
- **Line:** 19
|
||||||
|
- **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`
|
||||||
|
|
||||||
|
### UT-093
|
||||||
|
|
||||||
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.test.ts`](src/lib/components/player/playerSurface.test.ts#L13)
|
||||||
|
- **Line:** 13
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src/lib/components/player/playerSurface.ts`](src/lib/components/player/playerSurface.ts#L8)
|
||||||
|
- **Line:** 8
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UR-008
|
### UR-008
|
||||||
|
|
||||||
@@ -4211,43 +4288,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 2027
|
- **Line:** 2027
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UR-020
|
|
||||||
|
|
||||||
**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/types.ts`](src/lib/player/adapters/types.ts#L15)
|
|
||||||
- **Line:** 15
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19)
|
|
||||||
- **Line:** 19
|
|
||||||
- **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:** 5 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/types.ts`](src/lib/player/adapters/types.ts#L15)
|
|
||||||
- **Line:** 15
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src/lib/player/adapters/html5Adapter.ts`](src/lib/player/adapters/html5Adapter.ts#L19)
|
|
||||||
- **Line:** 19
|
|
||||||
- **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-024
|
### UR-024
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -4259,59 +4299,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 3
|
- **Line:** 3
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UR-025
|
|
||||||
|
|
||||||
**Locations:** 16 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/skipReporting.ts`](src/lib/services/skipReporting.ts#L14)
|
|
||||||
- **Line:** 14
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src/lib/services/skipReporting.test.ts`](src/lib/services/skipReporting.test.ts#L9)
|
|
||||||
- **Line:** 9
|
|
||||||
- **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/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/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#L157)
|
|
||||||
- **Line:** 157
|
|
||||||
- **Context:** `pub fn file_size(&self) -> Option<u64> {`
|
|
||||||
|
|
||||||
### UR-030
|
### UR-030
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -4356,6 +4343,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 1
|
- **Line:** 1
|
||||||
- **Context:** `Unknown`
|
- **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
|
### UR-043
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -4367,6 +4362,22 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 3
|
- **Line:** 3
|
||||||
- **Context:** `Unknown`
|
- **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-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`
|
||||||
|
|
||||||
### UR-051
|
### UR-051
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -4464,20 +4475,83 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 106
|
- **Line:** 106
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-042
|
### IT-017
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L2027)
|
- **File:** [`src-tauri/src/commands/catalog.rs`](src-tauri/src/commands/catalog.rs#L441)
|
||||||
- **Line:** 2027
|
- **Line:** 441
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-043
|
### 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<u64> {`
|
||||||
|
|
||||||
|
### 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<u64> {`
|
||||||
|
|
||||||
|
### 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<u64> {`
|
||||||
|
|
||||||
|
### UT-028
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L2027)
|
- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245)
|
||||||
- **Line:** 2027
|
- **Line:** 245
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-032
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245)
|
||||||
|
- **Line:** 245
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-051
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](src-tauri/src/repository/offline.rs#L2900)
|
||||||
|
- **Line:** 2900
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### 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#L2109)
|
||||||
|
- **Line:** 2109
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/online.rs`](src-tauri/src/repository/online.rs#L2150)
|
||||||
|
- **Line:** 2150
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-066
|
### UT-066
|
||||||
@@ -4488,6 +4562,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 4
|
- **Line:** 4
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-067
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](src-tauri/src/repository/offline.rs#L2396)
|
||||||
|
- **Line:** 2396
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-072
|
### UT-072
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -4576,6 +4658,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 337
|
- **Line:** 337
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-084
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/player/mpv_backend.rs`](src-tauri/src/player/mpv_backend.rs#L685)
|
||||||
|
- **Line:** 685
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-091
|
### UT-091
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -4587,6 +4677,25 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 15
|
- **Line:** 15
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UR-016
|
||||||
|
|
||||||
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
|
- **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#L251)
|
||||||
|
- **Line:** 251
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UR-022
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3)
|
||||||
|
- **Line:** 3
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UR-032
|
### UR-032
|
||||||
|
|
||||||
**Locations:** 7 file(s)
|
**Locations:** 7 file(s)
|
||||||
@@ -4654,47 +4763,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 234
|
- **Line:** 234
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### IT-003
|
### UT-042
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/player/mpv_backend_test.rs`](src-tauri/src/player/mpv_backend_test.rs#L8)
|
- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L2027)
|
||||||
- **Line:** 8
|
- **Line:** 2027
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-004
|
### UT-043
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/player/queue.rs`](src-tauri/src/player/queue.rs#L535)
|
- **File:** [`src-tauri/src/commands/download/mod.rs`](src-tauri/src/commands/download/mod.rs#L2027)
|
||||||
- **Line:** 535
|
- **Line:** 2027
|
||||||
- **Context:** `pub enum AddPosition {`
|
|
||||||
|
|
||||||
### UT-012
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/player/sleep_timer.rs`](src-tauri/src/player/sleep_timer.rs#L81)
|
|
||||||
- **Line:** 81
|
|
||||||
- **Context:** `pub fn cancel(&mut self) {`
|
|
||||||
|
|
||||||
### UR-016
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **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#L251)
|
|
||||||
- **Line:** 251
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-030
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/player/backend.rs`](src-tauri/src/player/backend.rs#L245)
|
|
||||||
- **Line:** 245
|
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-083
|
### UT-083
|
||||||
@@ -4705,22 +4787,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 673
|
- **Line:** 673
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-084
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/player/mpv_backend.rs`](src-tauri/src/player/mpv_backend.rs#L685)
|
|
||||||
- **Line:** 685
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UR-022
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/commands/player/settings.rs`](src-tauri/src/commands/player/settings.rs#L3)
|
|
||||||
- **Line:** 3
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UR-044
|
### UR-044
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -4729,14 +4795,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 3
|
- **Line:** 3
|
||||||
- **Context:** `Unknown`
|
- **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
|
### UR-046
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -4745,28 +4803,4 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-007, JA-010, JA-011, JA-012, JA-016,
|
|||||||
- **Line:** 3
|
- **Line:** 3
|
||||||
- **Context:** `Unknown`
|
- **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`
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ describe("live requirements.md", () => {
|
|||||||
|
|
||||||
expect(defined.UR).toBe(61);
|
expect(defined.UR).toBe(61);
|
||||||
expect(defined.IR).toBe(29);
|
expect(defined.IR).toBe(29);
|
||||||
expect(defined.DR).toBe(96);
|
expect(defined.DR).toBe(97);
|
||||||
expect(defined.JA).toBe(32);
|
expect(defined.JA).toBe(32);
|
||||||
expect(defined.total).toBe(218);
|
expect(defined.total).toBe(219);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Regression tests for the `/player/[id]` surface decision.
|
||||||
|
*
|
||||||
|
* The bug these pin down: a video that was left and re-entered rendered in the
|
||||||
|
* AUDIO player. Exiting a webview-rendered video does not stop the Rust
|
||||||
|
* controller (`onReportStop` deliberately emits no `stopped` state, so the
|
||||||
|
* autoplay handoff survives), so the backend still reports that episode/movie as
|
||||||
|
* the loaded media. Re-entering the route therefore took the "already playing,
|
||||||
|
* just show the UI" shortcut, which returns *before* a stream URL is fetched —
|
||||||
|
* and the render then fell through to `<AudioPlayer>` because it treated
|
||||||
|
* "video without a stream URL" as audio.
|
||||||
|
*
|
||||||
|
* TRACES: UR-005 | DR-100 | UT-092, UT-093
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { shouldReuseActivePlayback, resolvePlayerSurface } from "./playerSurface";
|
||||||
|
|
||||||
|
describe("shouldReuseActivePlayback", () => {
|
||||||
|
it("reuses playback when the same audio track is already loaded", () => {
|
||||||
|
expect(
|
||||||
|
shouldReuseActivePlayback({
|
||||||
|
requestedId: "track-1",
|
||||||
|
activeMediaId: "track-1",
|
||||||
|
isVideo: false,
|
||||||
|
forceRestart: false,
|
||||||
|
})
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT reuse playback for video, even when the backend reports it loaded", () => {
|
||||||
|
// Video needs a full load: the shortcut skips fetching the stream URL, and
|
||||||
|
// <VideoPlayer> cannot render without one.
|
||||||
|
expect(
|
||||||
|
shouldReuseActivePlayback({
|
||||||
|
requestedId: "episode-1",
|
||||||
|
activeMediaId: "episode-1",
|
||||||
|
isVideo: true,
|
||||||
|
forceRestart: false,
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reuse playback for a different item", () => {
|
||||||
|
expect(
|
||||||
|
shouldReuseActivePlayback({
|
||||||
|
requestedId: "track-2",
|
||||||
|
activeMediaId: "track-1",
|
||||||
|
isVideo: false,
|
||||||
|
forceRestart: false,
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reuse playback when nothing is loaded", () => {
|
||||||
|
expect(
|
||||||
|
shouldReuseActivePlayback({
|
||||||
|
requestedId: "track-1",
|
||||||
|
activeMediaId: null,
|
||||||
|
isVideo: false,
|
||||||
|
forceRestart: false,
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reuse playback when an explicit start position is requested", () => {
|
||||||
|
expect(
|
||||||
|
shouldReuseActivePlayback({
|
||||||
|
requestedId: "track-1",
|
||||||
|
activeMediaId: "track-1",
|
||||||
|
isVideo: false,
|
||||||
|
startPosition: 42,
|
||||||
|
forceRestart: false,
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reuse playback when restarting (next-episode advance)", () => {
|
||||||
|
expect(
|
||||||
|
shouldReuseActivePlayback({
|
||||||
|
requestedId: "episode-2",
|
||||||
|
activeMediaId: "episode-2",
|
||||||
|
isVideo: true,
|
||||||
|
forceRestart: true,
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolvePlayerSurface", () => {
|
||||||
|
it("renders the video surface for video with a stream URL", () => {
|
||||||
|
expect(resolvePlayerSurface({ isVideo: true, streamUrl: "http://s/master.m3u8" })).toBe(
|
||||||
|
"video"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the audio surface for audio content", () => {
|
||||||
|
expect(resolvePlayerSurface({ isVideo: false, streamUrl: null })).toBe("audio");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("never renders video content in the audio surface when the stream URL is missing", () => {
|
||||||
|
// A video whose stream URL has not resolved yet is pending, not audio —
|
||||||
|
// otherwise the movie/episode shows up in the audio player.
|
||||||
|
expect(resolvePlayerSurface({ isVideo: true, streamUrl: null })).toBe("pending");
|
||||||
|
expect(resolvePlayerSurface({ isVideo: true, streamUrl: "" })).toBe("pending");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Pure decisions for the `/player/[id]` route: which player surface to render,
|
||||||
|
* and whether a load can be skipped because the backend is already playing the
|
||||||
|
* requested item.
|
||||||
|
*
|
||||||
|
* Kept free of Svelte so both can be unit-tested without mounting the route.
|
||||||
|
*
|
||||||
|
* TRACES: UR-005 | DR-100 | UT-092, UT-093
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Which player component the route should render. */
|
||||||
|
export type PlayerSurface = "video" | "audio" | "pending";
|
||||||
|
|
||||||
|
export interface ReuseActivePlaybackInput {
|
||||||
|
/** Item id the route was asked to play. */
|
||||||
|
requestedId: string;
|
||||||
|
/** Id of the media the backend currently reports as loaded, if any. */
|
||||||
|
activeMediaId: string | null | undefined;
|
||||||
|
/** Whether the requested item is video content. */
|
||||||
|
isVideo: boolean;
|
||||||
|
/** Explicit start position, if the caller asked for one. */
|
||||||
|
startPosition?: number;
|
||||||
|
/** Advancing to a next episode always restarts from the beginning. */
|
||||||
|
forceRestart: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the route can show its UI over the backend's existing playback
|
||||||
|
* instead of reloading the item (e.g. expanding the audio mini player).
|
||||||
|
*
|
||||||
|
* Never for video. The shortcut returns before a stream URL is fetched, which
|
||||||
|
* is fine for audio (the backend owns the stream and the UI only mirrors it)
|
||||||
|
* but leaves `<VideoPlayer>` with nothing to render. Leaving a webview-rendered
|
||||||
|
* video does not clear the Rust controller's media — closing the route emits no
|
||||||
|
* `stopped` state by design — so re-entering the same movie/episode hit this
|
||||||
|
* shortcut and rendered the audio player instead.
|
||||||
|
*/
|
||||||
|
export function shouldReuseActivePlayback(input: ReuseActivePlaybackInput): boolean {
|
||||||
|
return (
|
||||||
|
!input.isVideo &&
|
||||||
|
input.activeMediaId === input.requestedId &&
|
||||||
|
!input.startPosition &&
|
||||||
|
!input.forceRestart
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerSurfaceInput {
|
||||||
|
isVideo: boolean;
|
||||||
|
streamUrl: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which surface to render for the loaded item.
|
||||||
|
*
|
||||||
|
* Video without a stream URL is `pending`, never `audio` — falling through to
|
||||||
|
* the audio player is how a movie/episode ended up in it.
|
||||||
|
*/
|
||||||
|
export function resolvePlayerSurface(input: PlayerSurfaceInput): PlayerSurface {
|
||||||
|
if (input.isVideo) {
|
||||||
|
return input.streamUrl ? "video" : "pending";
|
||||||
|
}
|
||||||
|
return "audio";
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import AudioPlayer from "$lib/components/player/AudioPlayer.svelte";
|
import AudioPlayer from "$lib/components/player/AudioPlayer.svelte";
|
||||||
import VideoPlayer from "$lib/components/player/VideoPlayer.svelte";
|
import VideoPlayer from "$lib/components/player/VideoPlayer.svelte";
|
||||||
|
import { shouldReuseActivePlayback, resolvePlayerSurface } from "$lib/components/player/playerSurface";
|
||||||
import NextEpisodePopup from "$lib/components/player/NextEpisodePopup.svelte";
|
import NextEpisodePopup from "$lib/components/player/NextEpisodePopup.svelte";
|
||||||
import {
|
import {
|
||||||
reportPlaybackStart,
|
reportPlaybackStart,
|
||||||
@@ -72,6 +73,10 @@
|
|||||||
let pollInterval: ReturnType<typeof setInterval> | null = null;
|
let pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
let loadedItemId: string | null = null;
|
let loadedItemId: string | null = null;
|
||||||
|
|
||||||
|
// Which player component to render. Video without a stream URL is "pending"
|
||||||
|
// (still resolving), never audio — see playerSurface.ts.
|
||||||
|
const surface = $derived(resolvePlayerSurface({ isVideo, streamUrl }));
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Start position polling (only for audio via MPV backend)
|
// Start position polling (only for audio via MPV backend)
|
||||||
pollInterval = setInterval(updateStatus, 1000);
|
pollInterval = setInterval(updateStatus, 1000);
|
||||||
@@ -137,30 +142,33 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this track is already playing in the backend, just show the UI
|
|
||||||
// without restarting playback (e.g., when expanding from MiniPlayer).
|
|
||||||
// forceRestart bypasses this so advancing to the next episode always
|
|
||||||
// restarts from the beginning even if it were already loaded.
|
|
||||||
const alreadyPlayingMedia = get(storeCurrentMedia);
|
|
||||||
if (alreadyPlayingMedia?.id === id && !startPosition && !forceRestart) {
|
|
||||||
console.log("loadAndPlay: Track already playing, showing UI without restarting");
|
|
||||||
isLive = item.kind === "liveChannel";
|
|
||||||
isVideo = item.kind === "movie" || item.kind === "episode" || isLive || isVideoChannelItem(item);
|
|
||||||
isPlaying = true;
|
|
||||||
loading = false;
|
|
||||||
// hasNext/hasPrevious come from the event-driven queue store.
|
|
||||||
// Fetch next episode for video skip button
|
|
||||||
if (isVideo) {
|
|
||||||
fetchNextEpisode(item);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if this is video content (Movie, Episode, live TV channels, and
|
// Determine if this is video content (Movie, Episode, live TV channels, and
|
||||||
// channel leaf items that carry a video stream).
|
// channel leaf items that carry a video stream).
|
||||||
isLive = item.kind === "liveChannel";
|
isLive = item.kind === "liveChannel";
|
||||||
isVideo = item.kind === "movie" || item.kind === "episode" || isLive || isVideoChannelItem(item);
|
isVideo = item.kind === "movie" || item.kind === "episode" || isLive || isVideoChannelItem(item);
|
||||||
|
|
||||||
|
// If this track is already playing in the backend, just show the UI
|
||||||
|
// without restarting playback (e.g., when expanding from MiniPlayer).
|
||||||
|
// Audio only, and forceRestart bypasses it so advancing to the next
|
||||||
|
// episode always restarts from the beginning — see playerSurface.ts for
|
||||||
|
// why video must never take this shortcut.
|
||||||
|
const alreadyPlayingMedia = get(storeCurrentMedia);
|
||||||
|
if (
|
||||||
|
shouldReuseActivePlayback({
|
||||||
|
requestedId: id,
|
||||||
|
activeMediaId: alreadyPlayingMedia?.id,
|
||||||
|
isVideo,
|
||||||
|
startPosition,
|
||||||
|
forceRestart,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
console.log("loadAndPlay: Track already playing, showing UI without restarting");
|
||||||
|
isPlaying = true;
|
||||||
|
loading = false;
|
||||||
|
// hasNext/hasPrevious come from the event-driven queue store.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// When switching to video, stop audio playback and clear the queue
|
// When switching to video, stop audio playback and clear the queue
|
||||||
// This prevents audio from continuing in the background and clears stale state
|
// This prevents audio from continuing in the background and clears stale state
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
@@ -650,10 +658,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if loading}
|
|
||||||
<div class="fixed inset-0 bg-[var(--color-background)] flex items-center justify-center z-50">
|
|
||||||
<div class="w-8 h-8 border-2 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"></div>
|
|
||||||
</div>
|
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<div class="fixed inset-0 bg-[var(--color-background)] flex items-center justify-center z-50 p-4">
|
<div class="fixed inset-0 bg-[var(--color-background)] flex items-center justify-center z-50 p-4">
|
||||||
<div class="text-center max-w-lg">
|
<div class="text-center max-w-lg">
|
||||||
@@ -667,7 +671,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if isVideo && streamUrl}
|
{:else if loading || surface === "pending"}
|
||||||
|
<!-- "pending" = video whose stream URL has not resolved yet. Showing the
|
||||||
|
spinner keeps it out of the audio player. -->
|
||||||
|
<div class="fixed inset-0 bg-[var(--color-background)] flex items-center justify-center z-50">
|
||||||
|
<div class="w-8 h-8 border-2 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"></div>
|
||||||
|
</div>
|
||||||
|
{:else if surface === "video" && streamUrl}
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
media={currentMedia}
|
media={currentMedia}
|
||||||
{streamUrl}
|
{streamUrl}
|
||||||
|
|||||||
Reference in New Issue
Block a user