refactor: delete two orphans, and record why the reparent design changed
`resolveVideoSource` chose between a local file and a remote URL for video playback. Backend-owned stream selection took that decision into Rust — `media_local_selection` for a downloaded file, `get_stream_selection` for a streamed one — and its last caller went with it. What remained was the function plus sixty lines of tests exercising nothing that ships. `fittedVideoSize` computed the rendered size of a video letterboxed into its container. Nothing has ever called it: it arrived with the fix that made the video fill its viewport and was superseded by `object-fit: contain` in the same change. There is some irony in a helper that models letterboxing sitting unused beside a container that was not letterboxing at all — the bug fixed in the previous commit was CSS, and this function would not have helped. A survey for exported symbols referenced only by their own tests finds 22 more. Most are legitimate — test mocks, deliberate reset hooks, public utility APIs — and the rest are unrelated to this work, so they are left for a cleanup that can be reviewed on its own terms rather than smuggled into a playback branch. Also records in the spec why DR-231's design changed. Reparenting Tauri's webview into a GtkOverlay aborts the process on the first click: Linux calls `attach_resize_handler` unconditionally (the Windows path guards it with `is_decorated()`), and its handler walks webview -> GtkBox -> GtkWindow with an unwrap that an overlay breaks. So the webview is not moved at all — mpv draws into the default vbox's own `draw` handler via `gdk_cairo_draw_from_gl()`, and GTK's container-before-children order puts the webview on top for free. No reparent, one less widget, and nothing a Tauri upgrade can invalidate by assuming its own layout.
This commit is contained in:
@@ -143,17 +143,41 @@ differs per platform:
|
|||||||
|
|
||||||
| Platform | Surface | Status |
|
| Platform | Surface | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Linux (X11 + Wayland) | `GtkOverlay`: `GtkGLArea` main child, Tauri's webview reparented as overlay child, via `WebviewWindow::gtk_window()` / `default_vbox()` | Proven by the spike, bar the reparent |
|
| Linux (X11 + Wayland) | `gdk_cairo_draw_from_gl()` in the default vbox's `draw` handler, over a `GdkGLContext` on its `GdkWindow`. No reparenting — see below | Render path proven by the spike; the *overlay* approach it used is rejected |
|
||||||
| Windows | Native HWND child beneath a transparent WebView2 | Phase 2 |
|
| Windows | Native HWND child beneath a transparent WebView2 | Phase 2 |
|
||||||
|
|
||||||
`vo=libmpv` plus `mpv_render_context_create` with `MPV_RENDER_PARAM_OPENGL_FBO`.
|
`vo=libmpv` plus `mpv_render_context_create` with `MPV_RENDER_PARAM_OPENGL_FBO`.
|
||||||
Webview transparency via `with_transparent(true)` — no window-level transparency;
|
Webview transparency via `with_transparent(true)` — no window-level transparency;
|
||||||
the spike showed it is neither used nor needed.
|
the spike showed it is neither used nor needed.
|
||||||
|
|
||||||
**The untested half of G1 is the reparent.** The spike built its own overlay; the
|
**G1's untested half failed, and the design changed because of it.**
|
||||||
app must adopt Tauri's. Same widgets, one extra step, but it is the only place
|
|
||||||
Tauri-specific behaviour can still bite — so it is the first gate, proven before
|
Reparenting Tauri's webview into a `GtkOverlay` attaches cleanly and then aborts
|
||||||
anything is built on it.
|
the process on the first click. `tauri-runtime-wry` connects a
|
||||||
|
button-press handler to the webview that walks a hard-coded path:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
webview.parent() // "This one should be GtkBox"
|
||||||
|
.parent() // ...and this one the GtkWindow
|
||||||
|
.downcast::<gtk::Window>().unwrap()
|
||||||
|
```
|
||||||
|
|
||||||
|
An overlay makes that chain `webview → GtkOverlay → GtkBox`, the downcast fails,
|
||||||
|
and the panic is non-unwinding so it kills the app. Nothing in configuration
|
||||||
|
avoids it: on Linux `attach_resize_handler` is called **unconditionally** (the
|
||||||
|
Windows equivalent is guarded by `is_decorated()`), and the decoration check that
|
||||||
|
would make the handler inert runs *after* the unwrap.
|
||||||
|
|
||||||
|
**So the webview is not moved at all.** mpv draws into the *default vbox's own
|
||||||
|
`draw` handler* instead, via `gdk_cairo_draw_from_gl()` over a `GdkGLContext`
|
||||||
|
created on that widget's `GdkWindow`. GTK3 draws a container before its children,
|
||||||
|
so the webview composites on top for free — the same z-order the overlay was for,
|
||||||
|
without touching the widget tree Tauri walks.
|
||||||
|
|
||||||
|
That is strictly better than the overlay it replaces: no reparent, no extra
|
||||||
|
widget, and the arrangement cannot be broken by a Tauri upgrade that assumes its
|
||||||
|
own layout. It is also why "the surface attached successfully" is not the gate —
|
||||||
|
a click is.
|
||||||
|
|
||||||
Three traps from the spike, each of which cost a debugging cycle and each of
|
Three traps from the spike, each of which cost a debugging cycle and each of
|
||||||
which looks like a platform limitation and is not:
|
which looks like a platform limitation and is not:
|
||||||
|
|||||||
+180
-194
@@ -1,11 +1,11 @@
|
|||||||
# Code Traceability Matrix
|
# Code Traceability Matrix
|
||||||
|
|
||||||
**Generated:** 8/22/2026, 10:39:30 AM
|
**Generated:** 8/22/2026, 11:26:46 AM
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- **Total Files Scanned:** 394
|
- **Total Files Scanned:** 394
|
||||||
- **Total TRACES Found:** 1093
|
- **Total TRACES Found:** 1092
|
||||||
- **Requirements Covered:**
|
- **Requirements Covered:**
|
||||||
- User Requirements (UR): 79
|
- User Requirements (UR): 79
|
||||||
- Integration Requirements (IR): 26
|
- Integration Requirements (IR): 26
|
||||||
@@ -3037,14 +3037,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
|
|
||||||
### DR-123
|
### DR-123
|
||||||
|
|
||||||
**Locations:** 7 file(s)
|
**Locations:** 6 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](../src/lib/api/bindings.ts#L389)
|
- **File:** [`src/lib/api/bindings.ts`](../src/lib/api/bindings.ts#L389)
|
||||||
- **Line:** 389
|
- **Line:** 389
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L12)
|
|
||||||
- **Line:** 12
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src/routes/player/[id]/+page.svelte`](../src/routes/player/[id]/+page.svelte#L405)
|
- **File:** [`src/routes/player/[id]/+page.svelte`](../src/routes/player/[id]/+page.svelte#L405)
|
||||||
- **Line:** 405
|
- **Line:** 405
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
@@ -3265,8 +3262,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L55)
|
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L17)
|
||||||
- **Line:** 55
|
- **Line:** 17
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/routes/player/[id]/+page.svelte`](../src/routes/player/[id]/+page.svelte#L314)
|
- **File:** [`src/routes/player/[id]/+page.svelte`](../src/routes/player/[id]/+page.svelte#L314)
|
||||||
- **Line:** 314
|
- **Line:** 314
|
||||||
@@ -7523,22 +7520,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 8
|
- **Line:** 8
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### IT-016
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3026)
|
|
||||||
- **Line:** 3026
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### IT-017
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/commands/catalog.rs`](../src-tauri/src/commands/catalog.rs#L878)
|
|
||||||
- **Line:** 878
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-003
|
### UT-003
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -7739,6 +7720,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **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#L251)
|
- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251)
|
||||||
- **Line:** 251
|
- **Line:** 251
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
@@ -8779,14 +8768,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 929
|
- **Line:** 929
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-062
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src/lib/utils/backgroundAudio.test.ts`](../src/lib/utils/backgroundAudio.test.ts#L7)
|
|
||||||
- **Line:** 7
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-066
|
### UT-066
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -8795,12 +8776,12 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 4
|
- **Line:** 4
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-071
|
### UT-067
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/utils/formatBytes.test.ts`](../src/lib/utils/formatBytes.test.ts#L4)
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3026)
|
||||||
- **Line:** 4
|
- **Line:** 3026
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-072
|
### UT-072
|
||||||
@@ -8956,46 +8937,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 15
|
- **Line:** 15
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-094
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
|
||||||
- **Line:** 17
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-095
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
|
||||||
- **Line:** 17
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-096
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
|
||||||
- **Line:** 17
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-097
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
|
||||||
- **Line:** 17
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-098
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src/lib/utils/layoutShell.test.ts`](../src/lib/utils/layoutShell.test.ts#L158)
|
|
||||||
- **Line:** 158
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-099
|
### UT-099
|
||||||
|
|
||||||
**Locations:** 3 file(s)
|
**Locations:** 3 file(s)
|
||||||
@@ -9033,6 +8974,34 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 3869
|
- **Line:** 3869
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-101
|
||||||
|
|
||||||
|
**Locations:** 4 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L2014)
|
||||||
|
- **Line:** 2014
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4370)
|
||||||
|
- **Line:** 4370
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4405)
|
||||||
|
- **Line:** 4405
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/mod.rs`](../src-tauri/src/repository/mod.rs#L238)
|
||||||
|
- **Line:** 238
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-102
|
||||||
|
|
||||||
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L688)
|
||||||
|
- **Line:** 688
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4605)
|
||||||
|
- **Line:** 4605
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-103
|
### UT-103
|
||||||
|
|
||||||
**Locations:** 6 file(s)
|
**Locations:** 6 file(s)
|
||||||
@@ -9133,14 +9102,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 1106
|
- **Line:** 1106
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-118
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L12)
|
|
||||||
- **Line:** 12
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-123
|
### UT-123
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -9156,8 +9117,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L55)
|
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L17)
|
||||||
- **Line:** 55
|
- **Line:** 17
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-131
|
### UT-131
|
||||||
@@ -10189,6 +10150,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 4413
|
- **Line:** 4413
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### IT-017
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/commands/catalog.rs`](../src-tauri/src/commands/catalog.rs#L878)
|
||||||
|
- **Line:** 878
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-060
|
### UT-060
|
||||||
|
|
||||||
**Locations:** 4 file(s)
|
**Locations:** 4 file(s)
|
||||||
@@ -10206,6 +10175,22 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 110
|
- **Line:** 110
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-062
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/utils/backgroundAudio.test.ts`](../src/lib/utils/backgroundAudio.test.ts#L7)
|
||||||
|
- **Line:** 7
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-071
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/utils/formatBytes.test.ts`](../src/lib/utils/formatBytes.test.ts#L4)
|
||||||
|
- **Line:** 4
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-086
|
### UT-086
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -10250,6 +10235,46 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 8
|
- **Line:** 8
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-094
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
||||||
|
- **Line:** 17
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-095
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
||||||
|
- **Line:** 17
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-096
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
||||||
|
- **Line:** 17
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-097
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/utils/safeArea.test.ts`](../src/lib/utils/safeArea.test.ts#L17)
|
||||||
|
- **Line:** 17
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-098
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src/lib/utils/layoutShell.test.ts`](../src/lib/utils/layoutShell.test.ts#L158)
|
||||||
|
- **Line:** 158
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-108
|
### UT-108
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -10261,20 +10286,51 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 550
|
- **Line:** 550
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-109
|
### UT-111
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 3 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3306)
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L62)
|
||||||
- **Line:** 3306
|
- **Line:** 62
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L2548)
|
||||||
|
- **Line:** 2548
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L2588)
|
||||||
|
- **Line:** 2588
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-110
|
### UT-112
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3362)
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L501)
|
||||||
- **Line:** 3362
|
- **Line:** 501
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3253)
|
||||||
|
- **Line:** 3253
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-113
|
||||||
|
|
||||||
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L276)
|
||||||
|
- **Line:** 276
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3144)
|
||||||
|
- **Line:** 3144
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-114
|
||||||
|
|
||||||
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L333)
|
||||||
|
- **Line:** 333
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3087)
|
||||||
|
- **Line:** 3087
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-116
|
### UT-116
|
||||||
@@ -10702,6 +10758,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 3394
|
- **Line:** 3394
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-171
|
||||||
|
|
||||||
|
**Locations:** 2 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/commands/catalog.rs`](../src-tauri/src/commands/catalog.rs#L916)
|
||||||
|
- **Line:** 916
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
- **File:** [`src-tauri/src/commands/catalog.rs`](../src-tauri/src/commands/catalog.rs#L957)
|
||||||
|
- **Line:** 957
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-172
|
### UT-172
|
||||||
|
|
||||||
**Locations:** 3 file(s)
|
**Locations:** 3 file(s)
|
||||||
@@ -11096,7 +11163,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
|
|
||||||
### UR-071
|
### UR-071
|
||||||
|
|
||||||
**Locations:** 67 file(s)
|
**Locations:** 66 file(s)
|
||||||
|
|
||||||
- **File:** [`src/lib/api/bindings.ts`](../src/lib/api/bindings.ts#L389)
|
- **File:** [`src/lib/api/bindings.ts`](../src/lib/api/bindings.ts#L389)
|
||||||
- **Line:** 389
|
- **Line:** 389
|
||||||
@@ -11110,11 +11177,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **File:** [`src/lib/api/bindings.ts`](../src/lib/api/bindings.ts#L2085)
|
- **File:** [`src/lib/api/bindings.ts`](../src/lib/api/bindings.ts#L2085)
|
||||||
- **Line:** 2085
|
- **Line:** 2085
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L12)
|
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L17)
|
||||||
- **Line:** 12
|
- **Line:** 17
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src/lib/player/localSource.ts`](../src/lib/player/localSource.ts#L55)
|
|
||||||
- **Line:** 55
|
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
- **File:** [`src/routes/player/[id]/+page.svelte`](../src/routes/player/[id]/+page.svelte#L314)
|
- **File:** [`src/routes/player/[id]/+page.svelte`](../src/routes/player/[id]/+page.svelte#L314)
|
||||||
- **Line:** 314
|
- **Line:** 314
|
||||||
@@ -12318,14 +12382,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 1286
|
- **Line:** 1286
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-030
|
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251)
|
|
||||||
- **Line:** 251
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-031
|
### UT-031
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
@@ -12350,81 +12406,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 251
|
- **Line:** 251
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-101
|
|
||||||
|
|
||||||
**Locations:** 4 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L2014)
|
|
||||||
- **Line:** 2014
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4370)
|
|
||||||
- **Line:** 4370
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4405)
|
|
||||||
- **Line:** 4405
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/mod.rs`](../src-tauri/src/repository/mod.rs#L238)
|
|
||||||
- **Line:** 238
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-102
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L688)
|
|
||||||
- **Line:** 688
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4605)
|
|
||||||
- **Line:** 4605
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-111
|
|
||||||
|
|
||||||
**Locations:** 3 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L62)
|
|
||||||
- **Line:** 62
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L2548)
|
|
||||||
- **Line:** 2548
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L2588)
|
|
||||||
- **Line:** 2588
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-112
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L501)
|
|
||||||
- **Line:** 501
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3253)
|
|
||||||
- **Line:** 3253
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-113
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L276)
|
|
||||||
- **Line:** 276
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3144)
|
|
||||||
- **Line:** 3144
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-114
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L333)
|
|
||||||
- **Line:** 333
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3087)
|
|
||||||
- **Line:** 3087
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-115
|
### UT-115
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
**Locations:** 2 file(s)
|
||||||
@@ -12615,17 +12596,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 3788
|
- **Line:** 3788
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-171
|
|
||||||
|
|
||||||
**Locations:** 2 file(s)
|
|
||||||
|
|
||||||
- **File:** [`src-tauri/src/commands/catalog.rs`](../src-tauri/src/commands/catalog.rs#L916)
|
|
||||||
- **Line:** 916
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
- **File:** [`src-tauri/src/commands/catalog.rs`](../src-tauri/src/commands/catalog.rs#L957)
|
|
||||||
- **Line:** 957
|
|
||||||
- **Context:** `Unknown`
|
|
||||||
|
|
||||||
### UT-173
|
### UT-173
|
||||||
|
|
||||||
**Locations:** 5 file(s)
|
**Locations:** 5 file(s)
|
||||||
@@ -12974,7 +12944,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 1239
|
- **Line:** 1239
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
### UT-067
|
### IT-016
|
||||||
|
|
||||||
**Locations:** 1 file(s)
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
@@ -12982,4 +12952,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010,
|
|||||||
- **Line:** 3026
|
- **Line:** 3026
|
||||||
- **Context:** `Unknown`
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-109
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3306)
|
||||||
|
- **Line:** 3306
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
### UT-110
|
||||||
|
|
||||||
|
**Locations:** 1 file(s)
|
||||||
|
|
||||||
|
- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3362)
|
||||||
|
- **Line:** 3362
|
||||||
|
- **Context:** `Unknown`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { videoFitClass, fittedVideoSize } from "./videoFit";
|
import { videoFitClass } from "./videoFit";
|
||||||
|
|
||||||
describe("videoFitClass", () => {
|
describe("videoFitClass", () => {
|
||||||
it("fills the container instead of capping at the source's intrinsic size", () => {
|
it("fills the container instead of capping at the source's intrinsic size", () => {
|
||||||
@@ -19,39 +19,3 @@ describe("videoFitClass", () => {
|
|||||||
expect(cls).not.toContain("object-fill");
|
expect(cls).not.toContain("object-fill");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fittedVideoSize", () => {
|
|
||||||
it("scales a 480p source up to fill a larger window (the reported bug)", () => {
|
|
||||||
// Exact 16:9 480p in a 1920x1080 window -> scales up to fill, rather than
|
|
||||||
// staying a 854x480 box in the middle.
|
|
||||||
const size = fittedVideoSize(853.33, 480, 1920, 1080);
|
|
||||||
expect(size.width).toBeCloseTo(1920, 0);
|
|
||||||
expect(size.height).toBeCloseTo(1080, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fits to the constraining dimension when aspect ratios differ", () => {
|
|
||||||
// 4:3 source in a 16:9 window -> height-constrained, pillarboxed.
|
|
||||||
const size = fittedVideoSize(640, 480, 1920, 1080);
|
|
||||||
expect(size.height).toBeCloseTo(1080, 0);
|
|
||||||
expect(size.width).toBeCloseTo(1440, 0);
|
|
||||||
expect(size.width).toBeLessThan(1920);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fits to width when the source is wider than the window", () => {
|
|
||||||
// 21:9 source in a 16:9 window -> width-constrained, letterboxed.
|
|
||||||
const size = fittedVideoSize(2560, 1080, 1920, 1080);
|
|
||||||
expect(size.width).toBeCloseTo(1920, 0);
|
|
||||||
expect(size.height).toBeCloseTo(810, 0);
|
|
||||||
expect(size.height).toBeLessThan(1080);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shrinks oversized media to fit rather than overflowing", () => {
|
|
||||||
const size = fittedVideoSize(3840, 2160, 1280, 720);
|
|
||||||
expect(size.width).toBeCloseTo(1280, 0);
|
|
||||||
expect(size.height).toBeCloseTo(720, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a zero size for unknown intrinsic dimensions", () => {
|
|
||||||
expect(fittedVideoSize(0, 0, 1920, 1080)).toEqual({ width: 0, height: 0 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -15,32 +15,3 @@
|
|||||||
export function videoFitClass(): string {
|
export function videoFitClass(): string {
|
||||||
return "w-full h-full object-contain";
|
return "w-full h-full object-contain";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FittedSize {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The rendered size of a video of the given intrinsic dimensions once it has
|
|
||||||
* been fitted into the container - i.e. scaled (up or down) so that it touches
|
|
||||||
* the container on its constraining axis, with the other axis letter/pillar
|
|
||||||
* boxed. Mirrors what `object-fit: contain` on a full-size element does.
|
|
||||||
*/
|
|
||||||
export function fittedVideoSize(
|
|
||||||
intrinsicWidth: number,
|
|
||||||
intrinsicHeight: number,
|
|
||||||
containerWidth: number,
|
|
||||||
containerHeight: number,
|
|
||||||
): FittedSize {
|
|
||||||
if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
|
|
||||||
return { width: 0, height: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const scale = Math.min(containerWidth / intrinsicWidth, containerHeight / intrinsicHeight);
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: intrinsicWidth * scale,
|
|
||||||
height: intrinsicHeight * scale,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { downloadedFilePath, resolveVideoSource } from "./localSource";
|
import { downloadedFilePath } from "./localSource";
|
||||||
|
|
||||||
describe("downloadedFilePath", () => {
|
describe("downloadedFilePath", () => {
|
||||||
// The download worker rewrites `downloads.file_path` to the absolute path it
|
// The download worker rewrites `downloads.file_path` to the absolute path it
|
||||||
@@ -29,74 +29,3 @@ describe("downloadedFilePath", () => {
|
|||||||
expect(downloadedFilePath("C:\\Users\\u\\AppData\\jellytau", stored)).toBe(stored);
|
expect(downloadedFilePath("C:\\Users\\u\\AppData\\jellytau", stored)).toBe(stored);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// A stand-in for Tauri's convertFileSrc, so the module stays pure.
|
|
||||||
const toAssetUrl = (p: string) => `asset://localhost/${encodeURIComponent(p)}`;
|
|
||||||
|
|
||||||
describe("resolveVideoSource", () => {
|
|
||||||
it("plays the downloaded file when one exists", () => {
|
|
||||||
const decision = resolveVideoSource({
|
|
||||||
localPath: "/home/u/.local/share/jellytau/movie.mp4",
|
|
||||||
remoteUrl: "https://server/Videos/abc/master.m3u8",
|
|
||||||
remoteNeedsTranscoding: true,
|
|
||||||
toAssetUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(decision.isLocal).toBe(true);
|
|
||||||
expect(decision.url).toBe(toAssetUrl("/home/u/.local/share/jellytau/movie.mp4"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("never marks a local file as needing transcoding, even when the remote did", () => {
|
|
||||||
// The transcoded path re-requests a whole new stream URL on every seek.
|
|
||||||
// A local file seeks natively; sending it down that route would ask the
|
|
||||||
// server for a stream we deliberately avoided.
|
|
||||||
const decision = resolveVideoSource({
|
|
||||||
localPath: "/downloads/film.mkv",
|
|
||||||
remoteUrl: "https://server/Videos/abc/master.m3u8",
|
|
||||||
remoteNeedsTranscoding: true,
|
|
||||||
toAssetUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(decision.needsTranscoding).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("streams when nothing is downloaded, preserving the transcoding flag", () => {
|
|
||||||
const decision = resolveVideoSource({
|
|
||||||
localPath: null,
|
|
||||||
remoteUrl: "https://server/Videos/abc/master.m3u8",
|
|
||||||
remoteNeedsTranscoding: true,
|
|
||||||
toAssetUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(decision).toEqual({
|
|
||||||
url: "https://server/Videos/abc/master.m3u8",
|
|
||||||
needsTranscoding: true,
|
|
||||||
isLocal: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("streams a direct-play remote without claiming it transcodes", () => {
|
|
||||||
const decision = resolveVideoSource({
|
|
||||||
localPath: null,
|
|
||||||
remoteUrl: "https://server/Videos/abc/stream.mp4",
|
|
||||||
remoteNeedsTranscoding: false,
|
|
||||||
toAssetUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(decision.needsTranscoding).toBe(false);
|
|
||||||
expect(decision.isLocal).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("falls back to streaming for a blank path rather than building a dead asset URL", () => {
|
|
||||||
for (const localPath of ["", " "]) {
|
|
||||||
const decision = resolveVideoSource({
|
|
||||||
localPath,
|
|
||||||
remoteUrl: "https://server/stream",
|
|
||||||
remoteNeedsTranscoding: false,
|
|
||||||
toAssetUrl,
|
|
||||||
});
|
|
||||||
expect(decision.isLocal).toBe(false);
|
|
||||||
expect(decision.url).toBe("https://server/stream");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,41 +1,3 @@
|
|||||||
/**
|
|
||||||
* Choosing between a downloaded file and a server stream for video playback.
|
|
||||||
*
|
|
||||||
* Audio has preferred local files since the queue is built (the Rust queue
|
|
||||||
* resolves `MediaSource::Local`), but video asks the repository for a stream URL
|
|
||||||
* and never consults `downloads` — so a downloaded film was streamed anyway,
|
|
||||||
* spending bandwidth that had already been spent and failing outright offline.
|
|
||||||
*
|
|
||||||
* Pure so it can be unit-tested: the component only supplies the two inputs and
|
|
||||||
* the asset-URL converter.
|
|
||||||
*
|
|
||||||
* TRACES: UR-071 | DR-123 | UT-118
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface VideoSourceInputs {
|
|
||||||
/** Absolute on-disk path of a completed download, or null to stream. */
|
|
||||||
localPath: string | null;
|
|
||||||
/** Stream URL the repository resolved (already transcoded if it had to be). */
|
|
||||||
remoteUrl: string;
|
|
||||||
/** Whether the *remote* stream is a transcode. */
|
|
||||||
remoteNeedsTranscoding: boolean;
|
|
||||||
/** Usually Tauri's `convertFileSrc`; injected so this module stays pure. */
|
|
||||||
toAssetUrl: (path: string) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VideoSourceDecision {
|
|
||||||
/** What to hand the `<video>` element. */
|
|
||||||
url: string;
|
|
||||||
/**
|
|
||||||
* Local files are never transcodes, so this is always false for them. It
|
|
||||||
* matters because the transcoded path re-requests a whole new stream URL on
|
|
||||||
* every seek; a local file seeks natively and must not go down that route.
|
|
||||||
*/
|
|
||||||
needsTranscoding: boolean;
|
|
||||||
/** True when playing from disk — for logging and the offline badge. */
|
|
||||||
isLocal: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Absolute on POSIX (`/…`), Windows (`C:\…`, `C:/…`) or a UNC share (`\\…`). */
|
/** Absolute on POSIX (`/…`), Windows (`C:\…`, `C:/…`) or a UNC share (`\\…`). */
|
||||||
function isAbsolute(path: string): boolean {
|
function isAbsolute(path: string): boolean {
|
||||||
return path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:[\\/]/.test(path);
|
return path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:[\\/]/.test(path);
|
||||||
@@ -57,15 +19,3 @@ function isAbsolute(path: string): boolean {
|
|||||||
export function downloadedFilePath(storageRoot: string, filePath: string): string {
|
export function downloadedFilePath(storageRoot: string, filePath: string): string {
|
||||||
return isAbsolute(filePath) ? filePath : `${storageRoot}/${filePath}`;
|
return isAbsolute(filePath) ? filePath : `${storageRoot}/${filePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveVideoSource(inputs: VideoSourceInputs): VideoSourceDecision {
|
|
||||||
const { localPath, remoteUrl, remoteNeedsTranscoding, toAssetUrl } = inputs;
|
|
||||||
|
|
||||||
// Treat blank/whitespace paths as absent — a malformed `downloads` row must
|
|
||||||
// not produce an asset URL pointing at nothing.
|
|
||||||
if (localPath && localPath.trim() !== "") {
|
|
||||||
return { url: toAssetUrl(localPath), needsTranscoding: false, isLocal: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { url: remoteUrl, needsTranscoding: remoteNeedsTranscoding, isLocal: false };
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user