From 45144cb6b0eab3a90592ade14dab5f743c6e319d Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 22 Aug 2026 11:51:16 +0200 Subject: [PATCH] feat(video): render mpv behind the webview, and collapse duplicated helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DR-231 with the design the failed reparent forced. mpv's render API draws into an FBO we own; the texture is composited by `gdk_cairo_draw_from_gl()` in the default vbox's own `draw` handler. GTK draws a container before its children, so the webview lands on top for free — no reparenting, no GtkOverlay, and nothing a Tauri upgrade can invalidate by assuming its own widget layout. Split so Windows inherits the useful half: `mpv_render` is the portable side (render context, framebuffer, GL resolution) and `video_surface` is the GTK side that consumes it. Nothing in the former is GTK-aware. Three things the spike paid for, carried over rather than rediscovered: - libepoxy exports GL entry points as *data* symbols. `dlsym("epoxy_glFoo")` returns the address *of a function pointer*, not of code — returning it makes mpv jump into non-executable data and take SIGSEGV on the first GL call. The value is read out of that location instead. - Frame pacing goes through mpv's update callback plus `report_swap`. Its absence looks like a GPU or compositing limit (fine in a window, judders at fullscreen) and is neither. - The render context is created on `realize` and destroyed on `unrealize`, with the update callback unregistered *before* the free, so a callback cannot land on a freed pointer. That is DR-232 built in from the start rather than retrofitted: the spike had no teardown at all, which remains the likeliest explanation for the one SIGSEGV it could not reproduce. Writing it also caught a bug that would have looked like severe stutter: the update callback flagged a new frame but never asked GTK to repaint, so decoded frames would only have reached the screen when something else happened to invalidate the widget. Still off by default behind JELLYTAU_NATIVE_VIDEO=1. It compiles and is wired; no frame has been put on screen yet. Redundant code, continued. `formatSecondsDuration` had no caller. Three components had hand-rolled `formatDuration`: Queue's was byte-equivalent to the shared "mm:ss", while EpisodeFocusView and the library page shared an identical "1h 23m" shape the util did not offer — so that format joins the other two and all three components now call one function. A survey for exported symbols referenced only by tests returns 23 more. They are deliberately left: spot-checking found `setLogForwarder` is the injection seam for a lazily-initialised forwarder, and `getCachedImageUrl` is the read path of a thumbnail cache whose management UI exists in Settings. Neither is dead — one is test infrastructure and the other is an unwired feature, and deleting either would remove capability while looking like tidying. The list is worth working through deliberately, not in a playback branch. --- docs/traceability.md | 1504 +++++++++-------- src-tauri/src/lib.rs | 13 +- src-tauri/src/player/mod.rs | 13 +- src-tauri/src/player/mpv_backend.rs | 35 +- src-tauri/src/player/mpv_render.rs | 374 ++++ src-tauri/src/player/video_surface.rs | 443 ++--- .../library/EpisodeFocusView.svelte | 15 +- src/lib/components/player/Queue.svelte | 9 +- src/lib/utils/duration.test.ts | 22 +- src/lib/utils/duration.ts | 38 +- src/routes/library/[id]/+page.svelte | 15 +- 11 files changed, 1471 insertions(+), 1010 deletions(-) create mode 100644 src-tauri/src/player/mpv_render.rs diff --git a/docs/traceability.md b/docs/traceability.md index 595d5e48..5e4ea104 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -1,15 +1,15 @@ # Code Traceability Matrix -**Generated:** 8/22/2026, 11:26:46 AM +**Generated:** 8/22/2026, 11:51:14 AM ## Summary -- **Total Files Scanned:** 394 -- **Total TRACES Found:** 1092 +- **Total Files Scanned:** 395 +- **Total TRACES Found:** 1098 - **Requirements Covered:** - User Requirements (UR): 79 - Integration Requirements (IR): 26 - - Development Requirements (DR): 198 + - Development Requirements (DR): 199 - Jellyfin API Requirements (JA): 35 ## Requirements by Type @@ -26,7 +26,7 @@ IR-003, IR-004, IR-006, IR-008, IR-009, IR-010, IR-011, IR-012, IR-013, IR-014, ### 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-042, 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, DR-101, DR-102, DR-103, DR-104, DR-105, DR-106, DR-107, DR-108, DR-109, DR-110, DR-111, DR-112, DR-113, DR-114, DR-115, DR-116, DR-117, DR-118, DR-119, DR-120, DR-121, DR-123, DR-126, DR-127, DR-128, DR-129, DR-130, DR-131, DR-132, DR-133, DR-134, DR-135, DR-136, DR-137, DR-140, DR-141, DR-142, DR-143, DR-147, DR-148, DR-149, DR-150, DR-151, DR-152, DR-153, DR-154, DR-155, DR-156, DR-157, DR-158, DR-159, DR-160, DR-162, DR-167, DR-168, DR-169, DR-170, DR-171, DR-173, DR-174, DR-175, DR-176, DR-177, DR-178, DR-179, DR-180, DR-181, DR-182, DR-183, DR-185, DR-186, DR-188, DR-189, DR-193, DR-195, DR-196, DR-197, DR-198, DR-201, DR-203, DR-204, DR-205, DR-206, DR-207, DR-209, DR-210, DR-211, DR-212, DR-213, DR-215, DR-216, DR-217, DR-218, DR-219, DR-220, DR-221, DR-223, DR-224, DR-225, DR-226, DR-227, DR-228, DR-230, DR-231, DR-232, DR-234 +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-042, 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, DR-101, DR-102, DR-103, DR-104, DR-105, DR-106, DR-107, DR-108, DR-109, DR-110, DR-111, DR-112, DR-113, DR-114, DR-115, DR-116, DR-117, DR-118, DR-119, DR-120, DR-121, DR-123, DR-126, DR-127, DR-128, DR-129, DR-130, DR-131, DR-132, DR-133, DR-134, DR-135, DR-136, DR-137, DR-140, DR-141, DR-142, DR-143, DR-147, DR-148, DR-149, DR-150, DR-151, DR-152, DR-153, DR-154, DR-155, DR-156, DR-157, DR-158, DR-159, DR-160, DR-162, DR-167, DR-168, DR-169, DR-170, DR-171, DR-173, DR-174, DR-175, DR-176, DR-177, DR-178, DR-179, DR-180, DR-181, DR-182, DR-183, DR-185, DR-186, DR-188, DR-189, DR-193, DR-195, DR-196, DR-197, DR-198, DR-201, DR-203, DR-204, DR-205, DR-206, DR-207, DR-209, DR-210, DR-211, DR-212, DR-213, DR-215, DR-216, DR-217, DR-218, DR-219, DR-220, DR-221, DR-223, DR-224, DR-225, DR-226, DR-227, DR-228, DR-230, DR-231, DR-232, DR-233, DR-234 ``` ### Jellyfin API Requirements (JA) @@ -65,11 +65,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/android/mod.rs`](../src-tauri/src/player/android/mod.rs#L1425) - **Line:** 1425 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L153) - - **Line:** 153 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L160) + - **Line:** 160 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L175) - - **Line:** 175 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L182) + - **Line:** 182 - **Context:** `Unknown` ### IR-008 @@ -293,20 +293,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/player/settings.rs`](../src-tauri/src/commands/player/settings.rs#L3) - **Line:** 3 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L658) - - **Line:** 658 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L691) + - **Line:** 691 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L678) - - **Line:** 678 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L711) + - **Line:** 711 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L747) - - **Line:** 747 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L780) + - **Line:** 780 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L759) - - **Line:** 759 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L792) + - **Line:** 792 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L833) - - **Line:** 833 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L866) + - **Line:** 866 - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L1) - **Line:** 1 @@ -422,8 +422,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/catalog.rs`](../src-tauri/src/commands/catalog.rs#L327) - **Line:** 327 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1618) - - **Line:** 1618 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1619) + - **Line:** 1619 - **Context:** `Unknown` ### IR-031 @@ -439,10 +439,19 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, ### IR-033 -**Locations:** 1 file(s) +**Locations:** 4 file(s) -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L13) - - **Line:** 13 +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L11) + - **Line:** 11 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L60) + - **Line:** 60 + - **Context:** `/ holding a lazily-resolving function pointer. So the address `dlsym` ...` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L160) + - **Line:** 160 + - **Context:** `impl MpvRenderContext {` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L29) + - **Line:** 29 - **Context:** `Unknown` ### DR-001 @@ -1167,8 +1176,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/sleep_timer.rs`](../src-tauri/src/player/sleep_timer.rs#L81) - **Line:** 81 - **Context:** `pub fn cancel(&mut self) {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L435) - - **Line:** 435 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L442) + - **Line:** 442 - **Context:** `Unknown` ### DR-030 @@ -1299,20 +1308,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/android/mod.rs`](../src-tauri/src/player/android/mod.rs#L617) - **Line:** 617 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L658) - - **Line:** 658 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L691) + - **Line:** 691 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L712) - - **Line:** 712 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L745) + - **Line:** 745 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L778) - - **Line:** 778 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L811) + - **Line:** 811 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L788) - - **Line:** 788 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L821) + - **Line:** 821 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L833) - - **Line:** 833 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L866) + - **Line:** 866 - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L1) - **Line:** 1 @@ -1647,32 +1656,32 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L929) - **Line:** 929 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L268) - - **Line:** 268 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L275) + - **Line:** 275 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L279) - - **Line:** 279 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L286) + - **Line:** 286 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1580) - - **Line:** 1580 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1587) + - **Line:** 1587 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1594) - - **Line:** 1594 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1601) + - **Line:** 1601 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1610) - - **Line:** 1610 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1617) + - **Line:** 1617 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1619) - - **Line:** 1619 - - **Context:** `pub fn exit_background_audio(&self) -> f64 {` - **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1626) - **Line:** 1626 + - **Context:** `pub fn exit_background_audio(&self) -> f64 {` +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1633) + - **Line:** 1633 - **Context:** `pub fn is_background_audio_active(&self) -> bool {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1641) - - **Line:** 1641 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1648) + - **Line:** 1648 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1883) - - **Line:** 1883 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1890) + - **Line:** 1890 - **Context:** `Unknown` ### DR-053 @@ -1755,8 +1764,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L289) - - **Line:** 289 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L278) + - **Line:** 278 - **Context:** `Unknown` ### DR-062 @@ -1766,11 +1775,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L289) - - **Line:** 289 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L278) + - **Line:** 278 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L394) - - **Line:** 394 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L383) + - **Line:** 383 - **Context:** `Unknown` - **File:** [`src/lib/components/library/EpisodeFocusView.test.ts`](../src/lib/components/library/EpisodeFocusView.test.ts#L7) - **Line:** 7 @@ -2404,23 +2413,23 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/services/playerEvents.ts`](../src/lib/services/playerEvents.ts#L8) - **Line:** 8 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L279) - - **Line:** 279 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L286) + - **Line:** 286 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L660) - - **Line:** 660 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L667) + - **Line:** 667 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L668) - - **Line:** 668 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L675) + - **Line:** 675 - **Context:** `pub fn is_html5_active(&self) -> bool {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L712) - - **Line:** 712 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L719) + - **Line:** 719 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1594) - - **Line:** 1594 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1601) + - **Line:** 1601 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1610) - - **Line:** 1610 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1617) + - **Line:** 1617 - **Context:** `Unknown` ### DR-098 @@ -2478,8 +2487,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#L1569) - **Line:** 1569 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L205) - - **Line:** 205 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L206) + - **Line:** 206 - **Context:** `Unknown` - **File:** [`src-tauri/src/commands/repository.rs`](../src-tauri/src/commands/repository.rs#L356) - **Line:** 356 @@ -2510,11 +2519,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L205) - - **Line:** 205 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L206) + - **Line:** 206 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L326) - - **Line:** 326 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L315) + - **Line:** 315 - **Context:** `async function handlePlayAll() {` ### DR-103 @@ -2533,8 +2542,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L124) - - **Line:** 124 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L125) + - **Line:** 125 - **Context:** `Unknown` - **File:** [`src/routes/library/tv/+page.svelte`](../src/routes/library/tv/+page.svelte#L8) - **Line:** 8 @@ -2659,8 +2668,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/repository.rs`](../src-tauri/src/commands/repository.rs#L51) - **Line:** 51 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1618) - - **Line:** 1618 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1619) + - **Line:** 1619 - **Context:** `Unknown` ### DR-110 @@ -2913,8 +2922,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/library/ArtistDetailView.svelte`](../src/lib/components/library/ArtistDetailView.svelte#L138) - **Line:** 138 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L250) - - **Line:** 250 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L239) + - **Line:** 239 - **Context:** `Unknown` - **File:** [`src/lib/components/library/MediaCard.svelte`](../src/lib/components/library/MediaCard.svelte#L1) - **Line:** 1 @@ -2949,8 +2958,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/stores/favorites.ts`](../src/lib/stores/favorites.ts#L79) - **Line:** 79 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L619) - - **Line:** 619 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L608) + - **Line:** 608 - **Context:** `Unknown` ### DR-120 @@ -3011,8 +3020,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L195) - **Line:** 195 - **Context:** `impl OnlineRepository {` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1625) - - **Line:** 1625 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1626) + - **Line:** 1626 - **Context:** `Unknown` ### DR-121 @@ -3122,20 +3131,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/stream_end.rs`](../src-tauri/src/player/stream_end.rs#L54) - **Line:** 54 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L288) - - **Line:** 288 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L295) + - **Line:** 295 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1698) - - **Line:** 1698 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1705) + - **Line:** 1705 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1731) - - **Line:** 1731 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1738) + - **Line:** 1738 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1788) - - **Line:** 1788 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1795) + - **Line:** 1795 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1819) - - **Line:** 1819 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1826) + - **Line:** 1826 - **Context:** `Unknown` ### DR-130 @@ -3154,14 +3163,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/player/timers.rs`](../src-tauri/src/commands/player/timers.rs#L299) - **Line:** 299 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L564) - - **Line:** 564 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L597) + - **Line:** 597 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L573) - - **Line:** 573 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L606) + - **Line:** 606 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1788) - - **Line:** 1788 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1795) + - **Line:** 1795 - **Context:** `Unknown` ### DR-131 @@ -3213,8 +3222,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L2690) - **Line:** 2690 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1632) - - **Line:** 1632 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1633) + - **Line:** 1633 - **Context:** `Unknown` ### DR-132 @@ -3369,8 +3378,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1126) - **Line:** 1126 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1535) - - **Line:** 1535 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1536) + - **Line:** 1536 - **Context:** `Unknown` ### DR-140 @@ -3405,11 +3414,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L250) - - **Line:** 250 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L239) + - **Line:** 239 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L394) - - **Line:** 394 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L383) + - **Line:** 383 - **Context:** `Unknown` - **File:** [`src/lib/components/library/EpisodeFocusView.test.ts`](../src/lib/components/library/EpisodeFocusView.test.ts#L7) - **Line:** 7 @@ -3423,14 +3432,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L139) - - **Line:** 139 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L140) + - **Line:** 140 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L233) - - **Line:** 233 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L234) + - **Line:** 234 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L467) - - **Line:** 467 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L456) + - **Line:** 456 - **Context:** `Unknown` - **File:** [`src/routes/search/+page.svelte`](../src/routes/search/+page.svelte#L97) - **Line:** 97 @@ -3458,8 +3467,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/services/offlineCatalog.ts`](../src/lib/services/offlineCatalog.ts#L46) - **Line:** 46 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L100) - - **Line:** 100 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L101) + - **Line:** 101 - **Context:** `Unknown` - **File:** [`src/routes/library/favorites/+page.svelte`](../src/routes/library/favorites/+page.svelte#L88) - **Line:** 88 @@ -3655,14 +3664,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, **Locations:** 3 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L881) - - **Line:** 881 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L888) + - **Line:** 888 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4099) - - **Line:** 4099 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4106) + - **Line:** 4106 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4142) - - **Line:** 4142 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4149) + - **Line:** 4149 - **Context:** `Unknown` ### DR-160 @@ -3791,8 +3800,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L716) - **Line:** 716 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1492) - - **Line:** 1492 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1493) + - **Line:** 1493 - **Context:** `Unknown` ### DR-167 @@ -4119,41 +4128,41 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L2108) - **Line:** 2108 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L312) - - **Line:** 312 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L319) + - **Line:** 319 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L508) - - **Line:** 508 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L515) + - **Line:** 515 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L953) - - **Line:** 953 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L960) + - **Line:** 960 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L968) - - **Line:** 968 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L975) + - **Line:** 975 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L979) - - **Line:** 979 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L986) + - **Line:** 986 - **Context:** `pub fn observed_duration(&self) -> Option {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1017) - - **Line:** 1017 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1024) + - **Line:** 1024 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4179) - - **Line:** 4179 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4186) + - **Line:** 4186 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4198) - - **Line:** 4198 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4205) + - **Line:** 4205 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4215) - - **Line:** 4215 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4222) + - **Line:** 4222 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4230) - - **Line:** 4230 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4237) + - **Line:** 4237 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4244) - - **Line:** 4244 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4251) + - **Line:** 4251 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4293) - - **Line:** 4293 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4300) + - **Line:** 4300 - **Context:** `Unknown` ### DR-179 @@ -4166,44 +4175,44 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/services/playbackReporting.ts`](../src/lib/services/playbackReporting.ts#L76) - **Line:** 76 - **Context:** `* This function is therefore the *local* half only, which is what its ...` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L76) - - **Line:** 76 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L83) + - **Line:** 83 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L128) - - **Line:** 128 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L135) + - **Line:** 135 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L772) - - **Line:** 772 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L779) + - **Line:** 779 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L987) - - **Line:** 987 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L994) + - **Line:** 994 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L996) - - **Line:** 996 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1003) + - **Line:** 1003 - **Context:** `pub fn set_report_sink(&self, sink: Arc) {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1315) - - **Line:** 1315 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1322) + - **Line:** 1322 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1559) - - **Line:** 1559 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1566) + - **Line:** 1566 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4266) - - **Line:** 4266 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4273) + - **Line:** 4273 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4293) - - **Line:** 4293 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4300) + - **Line:** 4300 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4331) - - **Line:** 4331 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4338) + - **Line:** 4338 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4359) - - **Line:** 4359 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4366) + - **Line:** 4366 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4388) - - **Line:** 4388 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4395) + - **Line:** 4395 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4406) - - **Line:** 4406 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4413) + - **Line:** 4413 - **Context:** `Unknown` ### DR-180 @@ -4222,11 +4231,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L3184) - **Line:** 3184 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L886) - - **Line:** 886 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L893) + - **Line:** 893 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4036) - - **Line:** 4036 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4043) + - **Line:** 4043 - **Context:** `Unknown` ### DR-181 @@ -4343,11 +4352,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, **Locations:** 2 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L545) - - **Line:** 545 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L552) + - **Line:** 552 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L3264) - - **Line:** 3264 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L3271) + - **Line:** 3271 - **Context:** `Unknown` ### DR-195 @@ -4629,8 +4638,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L357) - **Line:** 357 - **Context:** `impl LibrarySettings {` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1504) - - **Line:** 1504 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1505) + - **Line:** 1505 - **Context:** `Unknown` ### DR-210 @@ -4769,8 +4778,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/routes/settings/+page.svelte`](../src/routes/settings/+page.svelte#L1299) - **Line:** 1299 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1286) - - **Line:** 1286 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1287) + - **Line:** 1287 - **Context:** `Unknown` ### DR-218 @@ -4822,8 +4831,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1186) - **Line:** 1186 - **Context:** `pub fn run() {` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1278) - - **Line:** 1278 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1279) + - **Line:** 1279 - **Context:** `Unknown` ### DR-219 @@ -4872,8 +4881,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L625) - **Line:** 625 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1348) - - **Line:** 1348 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1349) + - **Line:** 1349 - **Context:** `Unknown` ### DR-224 @@ -5313,25 +5322,28 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, ### DR-231 -**Locations:** 7 file(s) +**Locations:** 8 file(s) -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L13) - - **Line:** 13 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L104) + - **Line:** 104 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L87) - - **Line:** 87 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L110) + - **Line:** 110 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L138) - - **Line:** 138 +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L11) + - **Line:** 11 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L169) - - **Line:** 169 +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L160) + - **Line:** 160 + - **Context:** `impl MpvRenderContext {` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L229) + - **Line:** 229 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L198) - - **Line:** 198 +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L29) + - **Line:** 29 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L222) - - **Line:** 222 +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L124) + - **Line:** 124 - **Context:** `Unknown` - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1239) - **Line:** 1239 @@ -5339,15 +5351,41 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, ### DR-232 -**Locations:** 2 file(s) +**Locations:** 4 file(s) -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L138) - - **Line:** 138 +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L11) + - **Line:** 11 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L29) + - **Line:** 29 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L124) + - **Line:** 124 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L198) - **Line:** 198 - **Context:** `Unknown` +### DR-233 + +**Locations:** 5 file(s) + +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L217) + - **Line:** 217 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L273) + - **Line:** 273 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L29) + - **Line:** 29 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L98) + - **Line:** 98 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L124) + - **Line:** 124 + - **Context:** `Unknown` + ### DR-234 **Locations:** 7 file(s) @@ -5931,8 +5969,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/storage/schema.rs`](../src-tauri/src/storage/schema.rs#L3) - **Line:** 3 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1632) - - **Line:** 1632 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1633) + - **Line:** 1633 - **Context:** `Unknown` ### UR-003 @@ -6083,11 +6121,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L545) - - **Line:** 545 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L552) + - **Line:** 552 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L3264) - - **Line:** 3264 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L3271) + - **Line:** 3271 - **Context:** `Unknown` ### UR-004 @@ -6241,14 +6279,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1698) - - **Line:** 1698 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1705) + - **Line:** 1705 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1788) - - **Line:** 1788 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1795) + - **Line:** 1795 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1819) - - **Line:** 1819 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1826) + - **Line:** 1826 - **Context:** `Unknown` - **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L29) - **Line:** 29 @@ -6546,11 +6584,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/events.rs`](../src-tauri/src/player/events.rs#L23) - **Line:** 23 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L564) - - **Line:** 564 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L597) + - **Line:** 597 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L573) - - **Line:** 573 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L606) + - **Line:** 606 - **Context:** `Unknown` - **File:** [`src-tauri/src/player/webview_audio_backend.rs`](../src-tauri/src/player/webview_audio_backend.rs#L25) - **Line:** 25 @@ -6591,71 +6629,71 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L312) - - **Line:** 312 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L319) + - **Line:** 319 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L508) - - **Line:** 508 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L515) + - **Line:** 515 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L545) - - **Line:** 545 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L552) + - **Line:** 552 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L660) - - **Line:** 660 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L667) + - **Line:** 667 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L668) - - **Line:** 668 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L675) + - **Line:** 675 - **Context:** `pub fn is_html5_active(&self) -> bool {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L712) - - **Line:** 712 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L719) + - **Line:** 719 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L772) - - **Line:** 772 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L779) + - **Line:** 779 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L881) - - **Line:** 881 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L888) + - **Line:** 888 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L953) - - **Line:** 953 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L960) + - **Line:** 960 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L968) - - **Line:** 968 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L975) + - **Line:** 975 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L979) - - **Line:** 979 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L986) + - **Line:** 986 - **Context:** `pub fn observed_duration(&self) -> Option {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1017) - - **Line:** 1017 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1024) + - **Line:** 1024 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1315) - - **Line:** 1315 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1322) + - **Line:** 1322 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1594) - - **Line:** 1594 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1601) + - **Line:** 1601 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1610) - - **Line:** 1610 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1617) + - **Line:** 1617 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L3264) - - **Line:** 3264 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L3271) + - **Line:** 3271 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4099) - - **Line:** 4099 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4106) + - **Line:** 4106 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4142) - - **Line:** 4142 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4149) + - **Line:** 4149 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4215) - - **Line:** 4215 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4222) + - **Line:** 4222 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4230) - - **Line:** 4230 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4237) + - **Line:** 4237 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4388) - - **Line:** 4388 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4395) + - **Line:** 4395 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4406) - - **Line:** 4406 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4413) + - **Line:** 4413 - **Context:** `Unknown` ### UR-007 @@ -7407,80 +7445,80 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/stream_end.rs`](../src-tauri/src/player/stream_end.rs#L91) - **Line:** 91 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L128) - - **Line:** 128 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L135) + - **Line:** 135 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L268) - - **Line:** 268 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L275) + - **Line:** 275 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L279) - - **Line:** 279 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L286) + - **Line:** 286 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L288) - - **Line:** 288 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L295) + - **Line:** 295 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L881) - - **Line:** 881 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L888) + - **Line:** 888 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L953) - - **Line:** 953 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L960) + - **Line:** 960 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1559) - - **Line:** 1559 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1566) + - **Line:** 1566 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1580) - - **Line:** 1580 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1587) + - **Line:** 1587 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1594) - - **Line:** 1594 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1601) + - **Line:** 1601 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1610) - - **Line:** 1610 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1617) + - **Line:** 1617 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1619) - - **Line:** 1619 - - **Context:** `pub fn exit_background_audio(&self) -> f64 {` - **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1626) - **Line:** 1626 + - **Context:** `pub fn exit_background_audio(&self) -> f64 {` +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1633) + - **Line:** 1633 - **Context:** `pub fn is_background_audio_active(&self) -> bool {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1641) - - **Line:** 1641 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1648) + - **Line:** 1648 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1698) - - **Line:** 1698 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1705) + - **Line:** 1705 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1731) - - **Line:** 1731 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1738) + - **Line:** 1738 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1788) - - **Line:** 1788 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1795) + - **Line:** 1795 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1819) - - **Line:** 1819 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1826) + - **Line:** 1826 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1883) - - **Line:** 1883 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1890) + - **Line:** 1890 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4036) - - **Line:** 4036 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4043) + - **Line:** 4043 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4099) - - **Line:** 4099 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4106) + - **Line:** 4106 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4179) - - **Line:** 4179 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4186) + - **Line:** 4186 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4198) - - **Line:** 4198 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4205) + - **Line:** 4205 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4244) - - **Line:** 4244 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4251) + - **Line:** 4251 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4331) - - **Line:** 4331 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4338) + - **Line:** 4338 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4359) - - **Line:** 4359 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4366) + - **Line:** 4366 - **Context:** `Unknown` - **File:** [`src-tauri/src/repository/hybrid.rs`](../src-tauri/src/repository/hybrid.rs#L121) - **Line:** 121 @@ -7520,6 +7558,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 8 - **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` + ### UT-003 **Locations:** 1 file(s) @@ -7552,14 +7598,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 2 - **Context:** `Unknown` -### 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) {` - ### UT-014 **Locations:** 2 file(s) @@ -7716,15 +7754,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 251 - **Context:** `Unknown` -### UT-029 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251) - - **Line:** 251 - - **Context:** `Unknown` - -### UT-030 +### UT-032 **Locations:** 1 file(s) @@ -7897,14 +7927,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L435) - - **Line:** 435 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L442) + - **Line:** 442 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1641) - - **Line:** 1641 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1648) + - **Line:** 1648 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1883) - - **Line:** 1883 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1890) + - **Line:** 1890 - **Context:** `Unknown` - **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L1306) - **Line:** 1306 @@ -8055,50 +8085,50 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/repository.rs`](../src-tauri/src/commands/repository.rs#L764) - **Line:** 764 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L76) - - **Line:** 76 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L83) + - **Line:** 83 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L128) - - **Line:** 128 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L135) + - **Line:** 135 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L312) - - **Line:** 312 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L319) + - **Line:** 319 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L772) - - **Line:** 772 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L779) + - **Line:** 779 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L953) - - **Line:** 953 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L960) + - **Line:** 960 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L987) - - **Line:** 987 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L994) + - **Line:** 994 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L996) - - **Line:** 996 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1003) + - **Line:** 1003 - **Context:** `pub fn set_report_sink(&self, sink: Arc) {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1315) - - **Line:** 1315 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1322) + - **Line:** 1322 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1559) - - **Line:** 1559 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1566) + - **Line:** 1566 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4215) - - **Line:** 4215 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4222) + - **Line:** 4222 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4266) - - **Line:** 4266 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4273) + - **Line:** 4273 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4293) - - **Line:** 4293 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4300) + - **Line:** 4300 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4331) - - **Line:** 4331 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4338) + - **Line:** 4338 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4359) - - **Line:** 4359 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4366) + - **Line:** 4366 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4388) - - **Line:** 4388 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4395) + - **Line:** 4395 - **Context:** `Unknown` - **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L688) - **Line:** 688 @@ -8124,8 +8154,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/storage/mod.rs`](../src-tauri/src/storage/mod.rs#L157) - **Line:** 157 - **Context:** `pub fn file_size(&self) -> Option {` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1632) - - **Line:** 1632 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1633) + - **Line:** 1633 - **Context:** `Unknown` ### UR-026 @@ -8186,8 +8216,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L2) - **Line:** 2 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L435) - - **Line:** 435 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L442) + - **Line:** 442 - **Context:** `Unknown` ### UR-027 @@ -8215,20 +8245,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/android/mod.rs`](../src-tauri/src/player/android/mod.rs#L617) - **Line:** 617 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L658) - - **Line:** 658 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L691) + - **Line:** 691 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L678) - - **Line:** 678 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L711) + - **Line:** 711 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L747) - - **Line:** 747 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L780) + - **Line:** 780 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L759) - - **Line:** 759 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L792) + - **Line:** 792 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L833) - - **Line:** 833 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L866) + - **Line:** 866 - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L1) - **Line:** 1 @@ -8706,14 +8736,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L124) - - **Line:** 124 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L125) + - **Line:** 125 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L205) - - **Line:** 205 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L206) + - **Line:** 206 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L326) - - **Line:** 326 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L315) + - **Line:** 315 - **Context:** `async function handlePlayAll() {` - **File:** [`src-tauri/src/commands/repository.rs`](../src-tauri/src/commands/repository.rs#L356) - **Line:** 356 @@ -8776,14 +8806,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 4 - **Context:** `Unknown` -### UT-067 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3026) - - **Line:** 3026 - - **Context:** `Unknown` - ### UT-072 **Locations:** 1 file(s) @@ -8876,16 +8898,16 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L747) - - **Line:** 747 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L780) + - **Line:** 780 - **Context:** `Unknown` ### UT-084 **Locations:** 1 file(s) -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L759) - - **Line:** 759 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L792) + - **Line:** 792 - **Context:** `Unknown` ### UT-085 @@ -8895,8 +8917,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/player/tapGestures.ts`](../src/lib/components/player/tapGestures.ts#L21) - **Line:** 21 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L778) - - **Line:** 778 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L811) + - **Line:** 811 - **Context:** `Unknown` ### UT-087 @@ -8906,8 +8928,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/player/tapGestures.ts`](../src/lib/components/player/tapGestures.ts#L21) - **Line:** 21 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L833) - - **Line:** 833 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L866) + - **Line:** 866 - **Context:** `Unknown` ### UT-089 @@ -8974,34 +8996,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 3869 - **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 **Locations:** 6 file(s) @@ -9382,11 +9376,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L289) - - **Line:** 289 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L278) + - **Line:** 278 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L394) - - **Line:** 394 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L383) + - **Line:** 383 - **Context:** `Unknown` - **File:** [`src/lib/components/library/EpisodeFocusView.test.ts`](../src/lib/components/library/EpisodeFocusView.test.ts#L7) - **Line:** 7 @@ -9443,8 +9437,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/stores/connectivity.ts`](../src/lib/stores/connectivity.ts#L250) - **Line:** 250 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L100) - - **Line:** 100 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L101) + - **Line:** 101 - **Context:** `Unknown` - **File:** [`src/routes/library/favorites/+page.svelte`](../src/routes/library/favorites/+page.svelte#L88) - **Line:** 88 @@ -9490,8 +9484,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L7) - **Line:** 7 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L250) - - **Line:** 250 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L239) + - **Line:** 239 - **Context:** `Unknown` - **File:** [`src/lib/components/library/EpisodeFocusView.test.ts`](../src/lib/components/library/EpisodeFocusView.test.ts#L7) - **Line:** 7 @@ -9508,14 +9502,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L1) - **Line:** 1 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L139) - - **Line:** 139 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L140) + - **Line:** 140 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L233) - - **Line:** 233 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L234) + - **Line:** 234 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L467) - - **Line:** 467 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L456) + - **Line:** 456 - **Context:** `Unknown` - **File:** [`src/routes/search/+page.svelte`](../src/routes/search/+page.svelte#L97) - **Line:** 97 @@ -9868,8 +9862,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1186) - **Line:** 1186 - **Context:** `pub fn run() {` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1278) - - **Line:** 1278 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1279) + - **Line:** 1279 - **Context:** `Unknown` ### UR-079 @@ -10198,8 +10192,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/player/tapGestures.ts`](../src/lib/components/player/tapGestures.ts#L21) - **Line:** 21 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L788) - - **Line:** 788 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L821) + - **Line:** 821 - **Context:** `Unknown` ### UT-088 @@ -10286,51 +10280,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 550 - **Context:** `Unknown` -### UT-111 +### UT-109 -**Locations:** 3 file(s) +**Locations:** 1 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 +- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3306) + - **Line:** 3306 - **Context:** `Unknown` -### UT-112 +### UT-110 -**Locations:** 2 file(s) +**Locations:** 1 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 +- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L3362) + - **Line:** 3362 - **Context:** `Unknown` ### UT-116 @@ -10366,17 +10329,17 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/stream_end.rs`](../src-tauri/src/player/stream_end.rs#L54) - **Line:** 54 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1698) - - **Line:** 1698 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1705) + - **Line:** 1705 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1731) - - **Line:** 1731 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1738) + - **Line:** 1738 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1788) - - **Line:** 1788 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1795) + - **Line:** 1795 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1819) - - **Line:** 1819 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1826) + - **Line:** 1826 - **Context:** `Unknown` ### UT-119 @@ -10405,11 +10368,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, **Locations:** 2 file(s) -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L564) - - **Line:** 564 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L597) + - **Line:** 597 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L573) - - **Line:** 573 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L606) + - **Line:** 606 - **Context:** `Unknown` ### UT-122 @@ -10818,11 +10781,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L3184) - **Line:** 3184 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L886) - - **Line:** 886 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L893) + - **Line:** 893 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4036) - - **Line:** 4036 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4043) + - **Line:** 4043 - **Context:** `Unknown` ### UT-183 @@ -11124,8 +11087,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/components/library/ArtistDetailView.svelte`](../src/lib/components/library/ArtistDetailView.svelte#L138) - **Line:** 138 - **Context:** `Unknown` -- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L250) - - **Line:** 250 +- **File:** [`src/lib/components/library/EpisodeFocusView.svelte`](../src/lib/components/library/EpisodeFocusView.svelte#L239) + - **Line:** 239 - **Context:** `Unknown` - **File:** [`src/lib/components/library/MediaCard.svelte`](../src/lib/components/library/MediaCard.svelte#L1) - **Line:** 1 @@ -11157,8 +11120,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/lib/stores/favorites.ts`](../src/lib/stores/favorites.ts#L55) - **Line:** 55 - **Context:** `Unknown` -- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L619) - - **Line:** 619 +- **File:** [`src/routes/library/[id]/+page.svelte`](../src/routes/library/[id]/+page.svelte#L608) + - **Line:** 608 - **Context:** `Unknown` ### UR-071 @@ -11309,11 +11272,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/media_server.rs`](../src-tauri/src/media_server.rs#L567) - **Line:** 567 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L886) - - **Line:** 886 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L893) + - **Line:** 893 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4036) - - **Line:** 4036 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4043) + - **Line:** 4043 - **Context:** `Unknown` - **File:** [`src-tauri/src/repository/mod.rs`](../src-tauri/src/repository/mod.rs#L215) - **Line:** 215 @@ -11357,8 +11320,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1126) - **Line:** 1126 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1535) - - **Line:** 1535 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1536) + - **Line:** 1536 - **Context:** `Unknown` - **File:** [`scripts/tauri-security-config.test.ts`](../scripts/tauri-security-config.test.ts#L11) - **Line:** 11 @@ -11586,8 +11549,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L716) - **Line:** 716 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1492) - - **Line:** 1492 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1493) + - **Line:** 1493 - **Context:** `Unknown` ### UT-170 @@ -11752,8 +11715,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L357) - **Line:** 357 - **Context:** `impl LibrarySettings {` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1504) - - **Line:** 1504 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1505) + - **Line:** 1505 - **Context:** `Unknown` ### UT-185 @@ -11917,8 +11880,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1126) - **Line:** 1126 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1348) - - **Line:** 1348 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1349) + - **Line:** 1349 - **Context:** `Unknown` - **File:** [`scripts/tauri-security-config.test.ts`](../scripts/tauri-security-config.test.ts#L11) - **Line:** 11 @@ -12019,11 +11982,11 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/seek.rs`](../src-tauri/src/player/seek.rs#L135) - **Line:** 135 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L153) - - **Line:** 153 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L160) + - **Line:** 160 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L175) - - **Line:** 175 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L182) + - **Line:** 182 - **Context:** `Unknown` - **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L477) - **Line:** 477 @@ -12095,20 +12058,20 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/player/android/mod.rs`](../src-tauri/src/player/android/mod.rs#L617) - **Line:** 617 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L658) - - **Line:** 658 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L691) + - **Line:** 691 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L712) - - **Line:** 712 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L745) + - **Line:** 745 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L778) - - **Line:** 778 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L811) + - **Line:** 811 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L788) - - **Line:** 788 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L821) + - **Line:** 821 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L833) - - **Line:** 833 +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L866) + - **Line:** 866 - **Context:** `Unknown` - **File:** [`src-tauri/src/settings.rs`](../src-tauri/src/settings.rs#L1) - **Line:** 1 @@ -12276,8 +12239,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/storage/schema.rs`](../src-tauri/src/storage/schema.rs#L769) - **Line:** 769 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1618) - - **Line:** 1618 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1619) + - **Line:** 1619 - **Context:** `Unknown` ### UR-069 @@ -12353,8 +12316,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L4087) - **Line:** 4087 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1625) - - **Line:** 1625 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1626) + - **Line:** 1626 - **Context:** `Unknown` ### UR-072 @@ -12378,32 +12341,8 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **File:** [`src/routes/settings/+page.svelte`](../src/routes/settings/+page.svelte#L1299) - **Line:** 1299 - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1286) - - **Line:** 1286 - - **Context:** `Unknown` - -### UT-031 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251) - - **Line:** 251 - - **Context:** `Unknown` - -### UT-032 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251) - - **Line:** 251 - - **Context:** `Unknown` - -### UT-033 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251) - - **Line:** 251 +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1287) + - **Line:** 1287 - **Context:** `Unknown` ### UT-115 @@ -12428,6 +12367,234 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 1090 - **Context:** `Unknown` +### UT-206 + +**Locations:** 9 file(s) + +- **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L1849) + - **Line:** 1849 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L1868) + - **Line:** 1868 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L2949) + - **Line:** 2949 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L1253) + - **Line:** 1253 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4500) + - **Line:** 4500 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4553) + - **Line:** 4553 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L1210) + - **Line:** 1210 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L3930) + - **Line:** 3930 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L3966) + - **Line:** 3966 + - **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-164 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/download/worker.rs`](../src-tauri/src/download/worker.rs#L313) + - **Line:** 313 + - **Context:** `Unknown` + +### UR-080 + +**Locations:** 20 file(s) + +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L104) + - **Line:** 104 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/mpv_backend.rs`](../src-tauri/src/player/mpv_backend.rs#L110) + - **Line:** 110 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L11) + - **Line:** 11 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L60) + - **Line:** 60 + - **Context:** `/ holding a lazily-resolving function pointer. So the address `dlsym` ...` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L160) + - **Line:** 160 + - **Context:** `impl MpvRenderContext {` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L217) + - **Line:** 217 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L229) + - **Line:** 229 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/mpv_render.rs`](../src-tauri/src/player/mpv_render.rs#L273) + - **Line:** 273 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L29) + - **Line:** 29 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L98) + - **Line:** 98 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L124) + - **Line:** 124 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L198) + - **Line:** 198 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L218) + - **Line:** 218 + - **Context:** `pub fn video_audio_codecs(detected: &str) -> String {` +- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L261) + - **Line:** 261 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L307) + - **Line:** 307 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L351) + - **Line:** 351 + - **Context:** `pub fn audio_forces_transcode(streams: &[(Option<&str>, bool)]) -> boo...` +- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L630) + - **Line:** 630 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L791) + - **Line:** 791 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L873) + - **Line:** 873 + - **Context:** `Unknown` +- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1239) + - **Line:** 1239 + - **Context:** `Unknown` + +### 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) {` + +### UT-029 + +**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) + - **Line:** 251 + - **Context:** `Unknown` + +### UT-031 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251) + - **Line:** 251 + - **Context:** `Unknown` + +### UT-033 + +**Locations:** 1 file(s) + +- **File:** [`src-tauri/src/player/backend.rs`](../src-tauri/src/player/backend.rs#L251) + - **Line:** 251 + - **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-130 **Locations:** 3 file(s) @@ -12504,14 +12671,14 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, **Locations:** 3 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L881) - - **Line:** 881 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L888) + - **Line:** 888 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4099) - - **Line:** 4099 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4106) + - **Line:** 4106 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4142) - - **Line:** 4142 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4149) + - **Line:** 4149 - **Context:** `Unknown` ### UT-156 @@ -12620,82 +12787,82 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, **Locations:** 3 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L953) - - **Line:** 953 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L960) + - **Line:** 960 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4179) - - **Line:** 4179 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4186) + - **Line:** 4186 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4198) - - **Line:** 4198 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4205) + - **Line:** 4205 - **Context:** `Unknown` ### UT-177 **Locations:** 6 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L953) - - **Line:** 953 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L960) + - **Line:** 960 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L968) - - **Line:** 968 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L975) + - **Line:** 975 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L979) - - **Line:** 979 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L986) + - **Line:** 986 - **Context:** `pub fn observed_duration(&self) -> Option {` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4215) - - **Line:** 4215 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4222) + - **Line:** 4222 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4230) - - **Line:** 4230 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4237) + - **Line:** 4237 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4244) - - **Line:** 4244 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4251) + - **Line:** 4251 - **Context:** `Unknown` ### UT-178 **Locations:** 3 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L772) - - **Line:** 772 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L779) + - **Line:** 779 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4266) - - **Line:** 4266 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4273) + - **Line:** 4273 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4293) - - **Line:** 4293 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4300) + - **Line:** 4300 - **Context:** `Unknown` ### UT-179 **Locations:** 4 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L128) - - **Line:** 128 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L135) + - **Line:** 135 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1559) - - **Line:** 1559 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1566) + - **Line:** 1566 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4331) - - **Line:** 4331 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4338) + - **Line:** 4338 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4359) - - **Line:** 4359 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4366) + - **Line:** 4366 - **Context:** `Unknown` ### UT-180 **Locations:** 3 file(s) -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1315) - - **Line:** 1315 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L1322) + - **Line:** 1322 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4388) - - **Line:** 4388 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4395) + - **Line:** 4395 - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4406) - - **Line:** 4406 +- **File:** [`src-tauri/src/player/mod.rs`](../src-tauri/src/player/mod.rs#L4413) + - **Line:** 4413 - **Context:** `Unknown` ### UT-190 @@ -12819,54 +12986,6 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 766 - **Context:** `Unknown` -### UT-206 - -**Locations:** 9 file(s) - -- **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L1849) - - **Line:** 1849 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L1868) - - **Line:** 1868 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/commands/player/mod.rs`](../src-tauri/src/commands/player/mod.rs#L2949) - - **Line:** 2949 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L1253) - - **Line:** 1253 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4500) - - **Line:** 4500 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/offline.rs`](../src-tauri/src/repository/offline.rs#L4553) - - **Line:** 4553 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L1210) - - **Line:** 1210 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L3930) - - **Line:** 3930 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L3966) - - **Line:** 3966 - - **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-164 - -**Locations:** 1 file(s) - -- **File:** [`src-tauri/src/download/worker.rs`](../src-tauri/src/download/worker.rs#L313) - - **Line:** 313 - - **Context:** `Unknown` - ### UT-210 **Locations:** 1 file(s) @@ -12897,54 +13016,7 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 257 - **Context:** `Unknown` -### UR-080 - -**Locations:** 14 file(s) - -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L13) - - **Line:** 13 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L87) - - **Line:** 87 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L138) - - **Line:** 138 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L169) - - **Line:** 169 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L198) - - **Line:** 198 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/player/video_surface.rs`](../src-tauri/src/player/video_surface.rs#L222) - - **Line:** 222 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L218) - - **Line:** 218 - - **Context:** `pub fn video_audio_codecs(detected: &str) -> String {` -- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L261) - - **Line:** 261 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L307) - - **Line:** 307 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/device_profile.rs`](../src-tauri/src/repository/device_profile.rs#L351) - - **Line:** 351 - - **Context:** `pub fn audio_forces_transcode(streams: &[(Option<&str>, bool)]) -> boo...` -- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L630) - - **Line:** 630 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L791) - - **Line:** 791 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/repository/online.rs`](../src-tauri/src/repository/online.rs#L873) - - **Line:** 873 - - **Context:** `Unknown` -- **File:** [`src-tauri/src/lib.rs`](../src-tauri/src/lib.rs#L1239) - - **Line:** 1239 - - **Context:** `Unknown` - -### IT-016 +### UT-067 **Locations:** 1 file(s) @@ -12952,20 +13024,4 @@ JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, - **Line:** 3026 - **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` - diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 314a193c..89a8aba7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1242,17 +1242,18 @@ pub fn run() { use tauri::Manager; log::warn!( "[INIT] JELLYTAU_NATIVE_VIDEO=1 — attaching the experimental \ - video surface; the app will abort on the first click until \ - the widget-tree shape is solved (DR-231)" + video surface (mpv drawn behind the webview, no reparenting)" ); if let Some(window) = app.get_webview_window("main") { match window.default_vbox() { - Ok(vbox) => match crate::player::video_surface::attach(&vbox) { - Ok(_surface) => { + Ok(vbox) => { + let handle = crate::player::mpv_backend::registered_handle(); + if crate::player::video_surface::attach(&vbox, handle) { info!("[INIT] Native video surface attached"); + } else { + log::warn!("[INIT] Native video surface unavailable"); } - Err(e) => log::warn!("[INIT] Native video surface unavailable: {e}"), - }, + } Err(e) => { log::warn!("[INIT] No GTK vbox for the main window: {e}") } diff --git a/src-tauri/src/player/mod.rs b/src-tauri/src/player/mod.rs index e0e2806e..f8a60013 100644 --- a/src-tauri/src/player/mod.rs +++ b/src-tauri/src/player/mod.rs @@ -24,11 +24,18 @@ pub mod android; #[cfg(target_os = "linux")] pub mod mpv_backend; +/// mpv's render API into a framebuffer we own (UR-080 / DR-231, IR-033). +/// +/// Deliberately *not* GTK-gated beyond the platform that currently builds it: +/// everything here is the portable half, and Windows reuses it unchanged behind +/// its own surface. +#[cfg(target_os = "linux")] +pub mod mpv_render; + /// The native video surface mpv renders into (UR-080 / DR-231). /// -/// Linux-gated for now because the surface is GTK. Everything *around* it — the -/// render context, its lifetime, frame pacing, the device profile — is -/// deliberately not, so Windows reuses it behind its own surface. +/// Linux-gated because the *surface* is GTK. Everything around it — the render +/// context, its lifetime, frame pacing, the device profile — is not. #[cfg(target_os = "linux")] pub mod video_surface; diff --git a/src-tauri/src/player/mpv_backend.rs b/src-tauri/src/player/mpv_backend.rs index 2137f7ed..873a65ad 100644 --- a/src-tauri/src/player/mpv_backend.rs +++ b/src-tauri/src/player/mpv_backend.rs @@ -89,6 +89,32 @@ fn get_stream_url(media: &MediaItem) -> String { } } +/// The mpv handle of the backend this process created, for the video surface. +/// +/// A `OnceLock` rather than a field reached through `PlayerBackend`, because the +/// trait is cross-platform and a raw mpv pointer is not something every backend +/// should have to pretend to have. Stored as `usize` because a raw pointer is +/// neither `Send` nor `Sync`; the only consumer is the GTK main thread, which is +/// also where mpv was created. +/// +/// Written once at construction and never cleared: the backend outlives the +/// window, so there is no window in which this could dangle while a surface is +/// still using it. +/// +/// TRACES: UR-080 | DR-231 +static MPV_HANDLE: std::sync::OnceLock = std::sync::OnceLock::new(); + +/// The registered handle, or null if no MPV backend was created (initialisation +/// can fail, and the app falls back to a no-op backend rather than dying). +/// +/// TRACES: UR-080 | DR-231 +pub fn registered_handle() -> *mut libmpv_sys::mpv_handle { + MPV_HANDLE + .get() + .map(|p| *p as *mut libmpv_sys::mpv_handle) + .unwrap_or(std::ptr::null_mut()) +} + impl MpvBackend { /// Create a new MPV backend pub fn new( @@ -178,7 +204,14 @@ impl MpvBackend { })); let backend = MpvBackend { - mpv: Arc::new(mpv), + mpv: { + let mpv = Arc::new(mpv); + // Publish the handle for the video surface (DR-231). Ignores a + // second call: only one MPV backend is ever constructed, and a + // failed re-init must not replace a live handle. + let _ = MPV_HANDLE.set(mpv.ctx.as_ptr() as usize); + mpv + }, state, event_emitter, audio_settings: AudioSettings::default(), diff --git a/src-tauri/src/player/mpv_render.rs b/src-tauri/src/player/mpv_render.rs new file mode 100644 index 00000000..9595819b --- /dev/null +++ b/src-tauri/src/player/mpv_render.rs @@ -0,0 +1,374 @@ +//! mpv's render API, driven into an OpenGL framebuffer we own. +//! +//! This is the half of native video that is not GTK: create a render context +//! over the mpv handle the audio backend already drives, render a frame into a +//! texture, and hand that texture id back for the toolkit to composite. +//! +//! Kept apart from `video_surface` deliberately — everything here is portable +//! across the platforms this app targets, while the surface that consumes it is +//! not. Windows reuses this file unchanged (DR-237). +//! +//! TRACES: UR-080 | DR-231, DR-232, IR-033 + +use std::ffi::{c_void, CStr, CString}; +use std::os::raw::{c_char, c_int}; +use std::ptr; + +use log::{error, info, warn}; + +/// GL entry points, resolved once. +/// +/// Only the handful needed to own a framebuffer; mpv resolves everything else +/// it needs through [`get_proc_address`]. +struct Gl { + gen_framebuffers: unsafe extern "C" fn(c_int, *mut u32), + delete_framebuffers: unsafe extern "C" fn(c_int, *const u32), + bind_framebuffer: unsafe extern "C" fn(u32, u32), + framebuffer_texture_2d: unsafe extern "C" fn(u32, u32, u32, u32, c_int), + gen_textures: unsafe extern "C" fn(c_int, *mut u32), + delete_textures: unsafe extern "C" fn(c_int, *const u32), + bind_texture: unsafe extern "C" fn(u32, u32), + tex_image_2d: + unsafe extern "C" fn(u32, c_int, c_int, c_int, c_int, c_int, u32, u32, *const c_void), + tex_parameteri: unsafe extern "C" fn(u32, u32, c_int), + check_framebuffer_status: unsafe extern "C" fn(u32) -> u32, +} + +const GL_TEXTURE_2D: u32 = 0x0DE1; +const GL_FRAMEBUFFER: u32 = 0x8D40; +const GL_COLOR_ATTACHMENT0: u32 = 0x8CE0; +const GL_RGBA: u32 = 0x1908; +const GL_RGBA8: c_int = 0x8058; +const GL_UNSIGNED_BYTE: u32 = 0x1401; +const GL_LINEAR: c_int = 0x2601; +const GL_TEXTURE_MIN_FILTER: u32 = 0x2801; +const GL_TEXTURE_MAG_FILTER: u32 = 0x2800; +const GL_FRAMEBUFFER_COMPLETE: u32 = 0x8CD5; + +/// Resolve a GL symbol the way libepoxy actually exports it. +/// +/// **This is the trap that cost the spike a debugging cycle.** libepoxy does not +/// export `glFoo` as a function. It exports `epoxy_glFoo` as a *data* symbol +/// holding a lazily-resolving function pointer. So the address `dlsym` returns +/// is the address *of the pointer*, not of any code: returning it makes mpv jump +/// into non-executable data and take SIGSEGV/SEGV_ACCERR on the very first GL +/// call. The value must be read *out of* that location. +/// +/// The `epoxy` crate does this correctly and is unusable here — its +/// `gl_generator` dependency pulls a yanked `xml-rs`. +/// +/// TRACES: UR-080 | IR-033 +unsafe fn resolve(name: &str) -> *mut c_void { + let epoxy_name = match CString::new(format!("epoxy_{name}")) { + Ok(n) => n, + Err(_) => return ptr::null_mut(), + }; + let slot = libc::dlsym(libc::RTLD_DEFAULT, epoxy_name.as_ptr()); + if !slot.is_null() { + // The symbol holds the function pointer; return what is stored there. + return *(slot as *mut *mut c_void); + } + + // Fall back to a plain symbol, for a GL stack that is not behind epoxy. + match CString::new(name) { + Ok(n) => libc::dlsym(libc::RTLD_DEFAULT, n.as_ptr()), + Err(_) => ptr::null_mut(), + } +} + +/// What mpv calls to find GL entry points. Same rule as [`resolve`]. +unsafe extern "C" fn get_proc_address(_ctx: *mut c_void, name: *const c_char) -> *mut c_void { + if name.is_null() { + return ptr::null_mut(); + } + match CStr::from_ptr(name).to_str() { + Ok(n) => resolve(n), + Err(_) => ptr::null_mut(), + } +} + +macro_rules! load { + ($name:literal) => {{ + let p = resolve($name); + if p.is_null() { + error!("[MpvRender] GL symbol not found: {}", $name); + return None; + } + std::mem::transmute(p) + }}; +} + +impl Gl { + /// Resolve every entry point, or none — a partially-loaded table would fail + /// later at a call site with no context. + /// + /// The transmutes are unannotated on purpose: each target type is declared + /// once on the struct field above, and repeating it at the call site would + /// be two places to get the same signature wrong. + #[allow(clippy::missing_transmute_annotations)] + unsafe fn load() -> Option { + Some(Gl { + gen_framebuffers: load!("glGenFramebuffers"), + delete_framebuffers: load!("glDeleteFramebuffers"), + bind_framebuffer: load!("glBindFramebuffer"), + framebuffer_texture_2d: load!("glFramebufferTexture2D"), + gen_textures: load!("glGenTextures"), + delete_textures: load!("glDeleteTextures"), + bind_texture: load!("glBindTexture"), + tex_image_2d: load!("glTexImage2D"), + tex_parameteri: load!("glTexParameteri"), + check_framebuffer_status: load!("glCheckFramebufferStatus"), + }) + } +} + +/// A colour-renderable framebuffer mpv draws into, sized to the widget. +struct Target { + fbo: u32, + texture: u32, + width: i32, + height: i32, +} + +/// mpv's render context plus the framebuffer it draws into. +/// +/// # Lifetime (DR-232) +/// +/// The render context must not outlive the GL context it was created against. +/// `Drop` unregisters mpv's update callback *before* freeing the context, so a +/// callback cannot land on a freed pointer, and frees the GL objects while the +/// caller still has the context current. The caller is responsible for making +/// the GL context current around both creation and drop — see `video_surface`. +/// +/// This is DR-184 on Android restated: a surface outliving its player. The spike +/// had no defence at all and saw one unexplained SIGSEGV in a decoder thread. +pub struct MpvRenderContext { + ctx: *mut libmpv_sys::mpv_render_context, + gl: Gl, + target: Option, +} + +// The render context is driven only from the GTK main thread; the update +// callback merely schedules a redraw and touches nothing here. +unsafe impl Send for MpvRenderContext {} + +impl MpvRenderContext { + /// Create a render context over an existing mpv handle. + /// + /// The GL context must already be current on this thread. + /// + /// TRACES: UR-080 | DR-231, IR-033 + pub unsafe fn new(mpv: *mut libmpv_sys::mpv_handle) -> Option { + let gl = Gl::load()?; + + let mut init = libmpv_sys::mpv_opengl_init_params { + get_proc_address: Some(get_proc_address), + get_proc_address_ctx: ptr::null_mut(), + }; + let mut api_type = CString::new("opengl").ok()?; + // `advanced_control` lets mpv tell us when a frame is ready instead of + // us guessing; it is what makes the update callback meaningful (DR-233). + let mut advanced: c_int = 1; + + let mut params = [ + libmpv_sys::mpv_render_param { + type_: libmpv_sys::mpv_render_param_type_MPV_RENDER_PARAM_API_TYPE, + data: api_type.as_ptr() as *mut c_void, + }, + libmpv_sys::mpv_render_param { + type_: libmpv_sys::mpv_render_param_type_MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, + data: &mut init as *mut _ as *mut c_void, + }, + libmpv_sys::mpv_render_param { + type_: libmpv_sys::mpv_render_param_type_MPV_RENDER_PARAM_ADVANCED_CONTROL, + data: &mut advanced as *mut _ as *mut c_void, + }, + libmpv_sys::mpv_render_param { + type_: 0, + data: ptr::null_mut(), + }, + ]; + + let mut ctx: *mut libmpv_sys::mpv_render_context = ptr::null_mut(); + let rc = libmpv_sys::mpv_render_context_create(&mut ctx, mpv, params.as_mut_ptr()); + // Keep the CString alive until after the call. + let _ = &mut api_type; + + if rc < 0 || ctx.is_null() { + error!("[MpvRender] mpv_render_context_create failed: {rc}"); + return None; + } + + info!("[MpvRender] render context created"); + Some(MpvRenderContext { + ctx, + gl, + target: None, + }) + } + + /// Ask to be told when a new frame is ready. + /// + /// Paired with [`report_swap`](Self::report_swap): without both, mpv has + /// nothing to time against. The symptom is misleading — playback looks fine + /// in a window and judders at fullscreen, which reads as a compositing or + /// GPU limit and is neither (DR-233). + /// + /// TRACES: UR-080 | DR-233 + pub unsafe fn set_update_callback( + &mut self, + callback: libmpv_sys::mpv_render_update_fn, + ctx: *mut c_void, + ) { + libmpv_sys::mpv_render_context_set_update_callback(self.ctx, callback, ctx); + } + + /// Render the current frame at `width` x `height`, returning the texture id + /// holding it. The GL context must be current. + /// + /// TRACES: UR-080 | DR-231 + pub unsafe fn render(&mut self, width: i32, height: i32) -> Option { + if width <= 0 || height <= 0 { + return None; + } + self.ensure_target(width, height)?; + let target = self.target.as_ref()?; + + let mut fbo = libmpv_sys::mpv_opengl_fbo { + fbo: target.fbo as c_int, + w: width as c_int, + h: height as c_int, + internal_format: 0, + }; + // GTK's cairo surface has its origin at the top left; mpv defaults to + // OpenGL's bottom-left. Without this the picture is drawn upside down — + // which looks like a broken decode rather than a coordinate convention. + let mut flip: c_int = 1; + + let mut params = [ + libmpv_sys::mpv_render_param { + type_: libmpv_sys::mpv_render_param_type_MPV_RENDER_PARAM_OPENGL_FBO, + data: &mut fbo as *mut _ as *mut c_void, + }, + libmpv_sys::mpv_render_param { + type_: libmpv_sys::mpv_render_param_type_MPV_RENDER_PARAM_FLIP_Y, + data: &mut flip as *mut _ as *mut c_void, + }, + libmpv_sys::mpv_render_param { + type_: 0, + data: ptr::null_mut(), + }, + ]; + + let rc = libmpv_sys::mpv_render_context_render(self.ctx, params.as_mut_ptr()); + if rc < 0 { + warn!("[MpvRender] render failed: {rc}"); + return None; + } + Some(target.texture) + } + + /// Tell mpv the frame reached the screen. See [`set_update_callback`]. + /// + /// TRACES: UR-080 | DR-233 + pub unsafe fn report_swap(&self) { + libmpv_sys::mpv_render_context_report_swap(self.ctx); + } + + /// Create or resize the framebuffer. Reused across frames — reallocating per + /// frame would churn GPU memory at the display rate. + unsafe fn ensure_target(&mut self, width: i32, height: i32) -> Option<()> { + if let Some(t) = &self.target { + if t.width == width && t.height == height { + return Some(()); + } + } + self.drop_target(); + + let gl = &self.gl; + let mut texture: u32 = 0; + (gl.gen_textures)(1, &mut texture); + (gl.bind_texture)(GL_TEXTURE_2D, texture); + (gl.tex_image_2d)( + GL_TEXTURE_2D, + 0, + GL_RGBA8, + width, + height, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + ptr::null(), + ); + (gl.tex_parameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + (gl.tex_parameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + (gl.bind_texture)(GL_TEXTURE_2D, 0); + + let mut fbo: u32 = 0; + (gl.gen_framebuffers)(1, &mut fbo); + (gl.bind_framebuffer)(GL_FRAMEBUFFER, fbo); + (gl.framebuffer_texture_2d)( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + texture, + 0, + ); + let status = (gl.check_framebuffer_status)(GL_FRAMEBUFFER); + (gl.bind_framebuffer)(GL_FRAMEBUFFER, 0); + + if status != GL_FRAMEBUFFER_COMPLETE { + error!("[MpvRender] framebuffer incomplete: 0x{status:x}"); + (gl.delete_framebuffers)(1, &fbo); + (gl.delete_textures)(1, &texture); + return None; + } + + self.target = Some(Target { + fbo, + texture, + width, + height, + }); + Some(()) + } + + unsafe fn drop_target(&mut self) { + if let Some(t) = self.target.take() { + (self.gl.delete_framebuffers)(1, &t.fbo); + (self.gl.delete_textures)(1, &t.texture); + } + } + + /// Free everything, with the GL context current. + /// + /// Explicit rather than left to `Drop` because the ordering matters and the + /// caller is the only one that can guarantee the GL context is current. See + /// DR-232. + pub unsafe fn destroy(mut self) { + // Unregister first: a callback arriving after the free would be a use + // after free, and it is scheduled from mpv's own threads. + libmpv_sys::mpv_render_context_set_update_callback(self.ctx, None, ptr::null_mut()); + self.drop_target(); + libmpv_sys::mpv_render_context_free(self.ctx); + self.ctx = ptr::null_mut(); + info!("[MpvRender] render context freed"); + std::mem::forget(self); + } +} + +impl Drop for MpvRenderContext { + fn drop(&mut self) { + if !self.ctx.is_null() { + // Reached only if `destroy` was not called — the GL context may not + // be current, so the GL objects are deliberately leaked rather than + // deleted against whatever context happens to be bound. Freeing the + // render context is still safe and is the part that matters. + warn!("[MpvRender] dropped without destroy(); GL objects leaked deliberately"); + unsafe { + libmpv_sys::mpv_render_context_set_update_callback(self.ctx, None, ptr::null_mut()); + libmpv_sys::mpv_render_context_free(self.ctx); + } + } + } +} diff --git a/src-tauri/src/player/video_surface.rs b/src-tauri/src/player/video_surface.rs index c96c3106..5017884c 100644 --- a/src-tauri/src/player/video_surface.rs +++ b/src-tauri/src/player/video_surface.rs @@ -1,232 +1,283 @@ -//! The native video surface: a GL area beneath Tauri's own webview. +//! The native video surface: mpv drawn *behind* Tauri's webview, without +//! touching the widget tree. //! -//! This is the desktop counterpart of the Android arrangement — a native -//! renderer at the bottom of the stack with a transparent webview drawn over it, -//! so the Svelte controls composite on top of moving video. +//! # Why there is no overlay here //! -//! The spike that authorised this built its *own* `GtkOverlay` and proved mpv -//! renders into it on X11 and Wayland. What it could not prove is the step this -//! module exists for: taking the overlay Tauri already built and reparenting the -//! real webview into it. Same widgets, one extra move, and the only place -//! Tauri-specific behaviour can still bite — which is why it is gate one. +//! The obvious arrangement — wrap the webview in a `GtkOverlay` with a +//! `GtkGLArea` beneath — attaches cleanly and then aborts the process on the +//! first click. `tauri-runtime-wry` connects a button-press handler to the +//! webview that walks a hard-coded path: //! -//! TRACES: UR-080 | DR-231, IR-033 +//! ```text +//! webview.parent() // "This one should be GtkBox" +//! .parent() // ...and this one the GtkWindow +//! .downcast::().unwrap() +//! ``` +//! +//! An overlay makes that chain `webview → GtkOverlay → GtkBox`, the downcast +//! fails, and because the panic is non-unwinding it takes the app with it. +//! Nothing in configuration avoids it: on Linux the handler is attached +//! *unconditionally* (the Windows path guards it behind `is_decorated()`), and +//! the decoration check that would make it inert runs *after* the unwrap. +//! +//! So the widget tree is left exactly as Tauri built it. GTK draws a container +//! before its children, so rendering into the vbox's own `draw` handler puts the +//! picture underneath the webview for free — the same z-order, no reparenting, +//! one less widget, and nothing a Tauri upgrade can invalidate by assuming its +//! own layout. +//! +//! TRACES: UR-080 | DR-231, DR-232, DR-233, IR-033 + +use std::cell::RefCell; +use std::ffi::c_void; +use std::rc::Rc; use gtk::prelude::*; -use log::{info, warn}; +use gtk::{gdk, glib}; +use log::{error, info, warn}; -/// The widgets that make up the video surface, kept together because their -/// lifetimes are bound: the render context (added next) is created when the GL -/// area realizes and must be freed before it unrealizes — DR-232. +use super::mpv_render::MpvRenderContext; + +/// GL enum for `gdk_cairo_draw_from_gl`'s `source_type`. GDK takes the GL +/// constant itself rather than an enum of its own. +const GL_TEXTURE: i32 = 0x1702; + +/// Everything the draw handler needs, shared with the GTK callbacks. +struct SurfaceState { + gl: Option, + render: Option, + mpv: *mut libmpv_sys::mpv_handle, + /// The widget to repaint when mpv reports a frame. Held here because the + /// update callback arrives on an mpv thread with nothing but this state. + widget: gtk::Box, +} + +impl SurfaceState { + /// Tear down in the order DR-232 requires, with the GL context current. + /// + /// The update callback is unregistered before the context is freed (inside + /// `destroy`), and the GL objects go while their context is still bound. + /// Getting this wrong is DR-184 on Android restated — a surface outliving + /// its player — and is the likeliest cause of the one unexplained SIGSEGV + /// the spike recorded. + fn teardown(&mut self) { + if let Some(render) = self.render.take() { + if let Some(gl) = &self.gl { + gl.make_current(); + } + unsafe { render.destroy() }; + } + self.gl = None; + } +} + +/// A live video surface. Dropping it tears the render context down. pub struct VideoSurface { - /// The GL area mpv renders into. Main child of the overlay, so it sits - /// *under* everything else. - #[allow(dead_code)] - gl_area: gtk::GLArea, - /// The overlay holding the GL area and the webview. - #[allow(dead_code)] - overlay: gtk::Overlay, + state: Rc>, + widget: gtk::Box, + handlers: Vec, } -impl VideoSurface { - // Consumed by the render context, which binds to the GL area on `realize` - // and is freed on `unrealize` (DR-232). Held here from the moment the - // surface exists so that binding has something to attach to. - #[allow(dead_code)] - /// The GL area, for the render context to bind to. - pub fn gl_area(&self) -> >k::GLArea { - &self.gl_area - } - - #[allow(dead_code)] - /// The overlay, for teardown. - pub fn overlay(&self) -> >k::Overlay { - &self.overlay +impl Drop for VideoSurface { + fn drop(&mut self) { + for id in self.handlers.drain(..) { + self.widget.disconnect(id); + } + self.state.borrow_mut().teardown(); + self.widget.queue_draw(); + info!("[VideoSurface] detached"); } } -/// Why a surface could not be attached. +/// mpv's update callback. Runs on an mpv thread, so it does the least possible: +/// flags the state and asks GTK to redraw on the main loop. /// -/// One variant, because there is exactly one way this fails that is not already -/// reported by Tauri itself: the window exists and has a vbox, but the vbox is -/// not shaped the way Tauri has always shaped it. -#[derive(Debug)] -pub enum SurfaceError { - /// The vbox held no webview to reparent — Tauri's layout has changed. - NoWebviewChild, +/// **Nothing here may block or re-enter the player.** The project's deadlock +/// gotcha applies with full force — this is called from mpv's own threads. +/// +/// TRACES: UR-080 | DR-233 +unsafe extern "C" fn on_mpv_update(ctx: *mut c_void) { + if ctx.is_null() { + return; + } + let state = &*(ctx as *const Rc>); + let state = state.clone(); + // Hop to the main loop: GTK is not thread-safe and mpv is not on its thread. + // + // Asking for a repaint is the entire job. Without it a decoded frame simply + // waits — the picture would then only advance when something else happened + // to invalidate the widget, which looks like severe stutter rather than like + // a missing call. + glib::idle_add_local_once(move || { + let Ok(s) = state.try_borrow() else { return }; + s.widget.queue_draw(); + }); } -impl std::fmt::Display for SurfaceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SurfaceError::NoWebviewChild => write!( - f, - "Tauri's default vbox had no child to reparent — its window layout has changed" - ), +/// Start drawing mpv's video underneath the webview. +/// +/// `vbox` is Tauri's `default_vbox()` — the container the webview already lives +/// in. It is not modified; only a `draw` handler is added. +/// +/// Must run on the GTK main thread. +/// +/// TRACES: UR-080 | DR-231, DR-232, DR-233 +pub fn attach(vbox: >k::Box, mpv: *mut libmpv_sys::mpv_handle) -> bool { + if mpv.is_null() { + warn!("[VideoSurface] no mpv handle; native video unavailable"); + return false; + } + + let state = Rc::new(RefCell::new(SurfaceState { + gl: None, + render: None, + mpv, + widget: vbox.clone(), + })); + + let mut handlers = Vec::new(); + + // The GL context can only be created once the widget has a GdkWindow, which + // is what `realize` announces. Creating it earlier leaves nothing to attach + // to — the same ordering constraint the render context has. + let realize_state = state.clone(); + handlers.push(vbox.connect_realize(move |widget| { + if let Err(e) = init_gl(widget, &realize_state) { + error!("[VideoSurface] GL init failed: {e}"); + } + })); + + // A render context outliving its GL context is the defect DR-232 exists to + // prevent, so teardown is bound to `unrealize` rather than left to Drop. + let unrealize_state = state.clone(); + handlers.push(vbox.connect_unrealize(move |_| { + unrealize_state.borrow_mut().teardown(); + })); + + let draw_state = state.clone(); + handlers.push(vbox.connect_draw(move |widget, cr| { + draw(widget, cr, &draw_state); + // Propagate: the webview is a child and must still draw over us. + glib::Propagation::Proceed + })); + + // The window is already up by the time we are called, so run the init the + // `realize` signal would have. + if vbox.is_realized() { + if let Err(e) = init_gl(vbox, &state) { + error!("[VideoSurface] GL init failed: {e}"); } } + + info!("[VideoSurface] attached to Tauri's vbox without reparenting"); + // The surface lives as long as the window. Held in a thread-local rather + // than returned, because it owns `Rc` and GTK types and so is neither `Send` + // nor `Sync` — it cannot go into Tauri's managed state, and leaking it would + // give up the ability to tear it down at all. + // + // Teardown does not depend on this being dropped: it is driven by the + // widget's `unrealize`, which is the signal that actually means "your GL + // context is going away" (DR-232). + LIVE_SURFACE.with(|cell| { + *cell.borrow_mut() = Some(VideoSurface { + state, + widget: vbox.clone(), + handlers, + }); + }); + true } -impl std::error::Error for SurfaceError {} - -/// Build the overlay and move Tauri's webview on top of it. -/// -/// Tauri's Linux window is an `ApplicationWindow` holding a single vertical -/// `gtk::Box` (`default_vbox`), with the webview packed into it. This takes that -/// webview out, puts a `GtkGLArea` in its place inside a `GtkOverlay`, and adds -/// the webview back as the *overlay* child so it draws above. -/// -/// **Must run on the GTK main thread.** Every GTK call here is main-thread-only, -/// and the caller reaches it via `run_on_main_thread`. -/// -/// Ordering matters: the GL area is added as the overlay's main child *before* -/// the webview goes back, because `GtkOverlay` treats its first `add` as the -/// bottom of the stack. Adding them the other way round yields a webview with -/// video painted over it — an easy mistake with an obvious symptom. -/// -/// TRACES: UR-080 | DR-231 -pub fn attach(vbox: >k::Box) -> Result { - // Tauri packs exactly one child (the webview) into the default vbox. Take it - // rather than assume its type: wry's widget is an implementation detail, and - // all this needs is "whatever Tauri put here". - let children = vbox.children(); - let webview = children - .into_iter() - .next() - .ok_or(SurfaceError::NoWebviewChild)?; - - let gl_area = gtk::GLArea::new(); - // No depth buffer: mpv draws a flat picture into an FBO and nothing here is - // 3D. Asking for one costs memory on every resize for nothing. - gl_area.set_has_depth_buffer(false); - gl_area.set_has_stencil_buffer(false); - // Fill the overlay rather than centring at intrinsic size — the same defect - // `videoFitClass` had to fix on the webview side, where `max-w-full` only - // ever shrank and a 480p source rendered as a small box on a black screen. - gl_area.set_hexpand(true); - gl_area.set_vexpand(true); - - let overlay = gtk::Overlay::new(); - - // Reparent. `remove` drops the container's reference, so hold one across the - // move or the widget is destroyed between the two calls. - let webview_ref = webview.clone(); - vbox.remove(&webview); - - overlay.add(&gl_area); // main child — the bottom of the stack - overlay.add_overlay(&webview_ref); // drawn above the video - - // The webview must keep receiving input: it *is* the UI. `GtkOverlay` passes - // events to overlay children by default, so pass-through stays off — setting - // it would send clicks to the GL area, which has no controls on it. - overlay.set_overlay_pass_through(&webview_ref, false); - - vbox.pack_start(&overlay, true, true, 0); - overlay.show_all(); - - info!("[VideoSurface] GL area attached beneath Tauri's webview"); - - Ok(VideoSurface { gl_area, overlay }) +thread_local! { + /// The one live surface, on the GTK main thread. + static LIVE_SURFACE: RefCell> = const { RefCell::new(None) }; } -/// Put Tauri's window back the way it was found. +/// Drop the live surface, if there is one. Idempotent. /// -/// Not merely tidiness: the webview outlives the video surface, so if the -/// surface is torn down without returning the webview to the vbox the UI -/// disappears while the app keeps running. Mirrors [`attach`] exactly. -/// -/// TRACES: UR-080 | DR-231, DR-232 -// Called by the render-context teardown, which lands with DR-232. Written now, -// beside `attach`, because a reparent whose inverse is written later is a -// reparent whose inverse is written wrong. +/// TRACES: UR-080 | DR-232 #[allow(dead_code)] -pub fn detach(vbox: >k::Box, surface: &VideoSurface) { - let children = surface.overlay.children(); - for child in children { - // Everything except the GL area came from the vbox and goes back to it. - if child.downcast_ref::().is_some() { - continue; - } - surface.overlay.remove(&child); - vbox.pack_start(&child, true, true, 0); - } - vbox.remove(&surface.overlay); - vbox.show_all(); - warn!("[VideoSurface] detached; webview returned to Tauri's vbox"); +pub fn detach() { + LIVE_SURFACE.with(|cell| { + cell.borrow_mut().take(); + }); } -#[cfg(test)] -mod tests { - //! These exercise GTK widget wiring, so they need a display and are ignored - //! by default — CI has no X11 or Wayland session. Run locally with - //! `cargo test -- --ignored video_surface`. +/// Create the GL context and the mpv render context over it. +fn init_gl(widget: >k::Box, state: &Rc>) -> Result<(), String> { + if state.borrow().render.is_some() { + return Ok(()); + } + let window = widget.window().ok_or("widget has no GdkWindow")?; - use super::*; + let gl = window + .create_gl_context() + .map_err(|e| format!("create_gl_context: {e}"))?; + gl.realize().map_err(|e| format!("realize: {e}"))?; + gl.make_current(); - /// The stacking order is the whole point, and getting it backwards produces - /// video painted over the controls rather than under them. - /// - /// TRACES: UR-080 | DR-231 - #[test] - #[ignore = "requires a display"] - fn test_gl_area_is_below_the_reparented_webview() { - if gtk::init().is_err() { - return; - } - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - // Stand in for the webview; `attach` deliberately does not care what it is. - let stand_in = gtk::DrawingArea::new(); - vbox.pack_start(&stand_in, true, true, 0); + let mpv = state.borrow().mpv; + let mut render = + unsafe { MpvRenderContext::new(mpv) }.ok_or("mpv render context creation failed")?; - let surface = attach(&vbox).expect("attaches"); - let children = surface.overlay().children(); + // The callback holds a pointer to the shared state, so that Rc must outlive + // it. `destroy` unregisters before freeing, which is what makes it safe. + let ctx_ptr = Rc::into_raw(state.clone()) as *mut c_void; + unsafe { render.set_update_callback(Some(on_mpv_update), ctx_ptr) }; - // GtkOverlay lists its main child first. - assert!( - children[0].downcast_ref::().is_some(), - "the GL area must be the overlay's main child, i.e. underneath" - ); - assert!( - children.len() > 1, - "the reparented widget must still be present" - ); + let mut s = state.borrow_mut(); + s.gl = Some(gl); + s.render = Some(render); + info!("[VideoSurface] GL and render context ready"); + Ok(()) +} + +/// Draw the current frame, if there is one. +/// +/// Runs *before* the children, which is what puts the picture behind the +/// webview. Deliberately forgiving: no frame, no GL, or a borrowed state all +/// mean "draw nothing this pass" rather than an error — the webview then paints +/// over an untouched background, which is exactly the pre-native appearance. +fn draw(widget: >k::Box, cr: >k::cairo::Context, state: &Rc>) { + let Ok(mut s) = state.try_borrow_mut() else { + return; + }; + let Some(gl) = s.gl.clone() else { + return; + }; + let Some(window) = widget.window() else { + return; + }; + + let scale = widget.scale_factor(); + let width = widget.allocated_width() * scale; + let height = widget.allocated_height() * scale; + if width <= 0 || height <= 0 { + return; } - /// A surface that tears down without returning the webview leaves a running - /// app with no UI. - /// - /// TRACES: UR-080 | DR-231, DR-232 - #[test] - #[ignore = "requires a display"] - fn test_detach_returns_the_webview_to_the_vbox() { - if gtk::init().is_err() { - return; - } - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - let stand_in = gtk::DrawingArea::new(); - vbox.pack_start(&stand_in, true, true, 0); + gl.make_current(); - let surface = attach(&vbox).expect("attaches"); - detach(&vbox, &surface); + let Some(render) = s.render.as_mut() else { + return; + }; + let Some(texture) = (unsafe { render.render(width, height) }) else { + return; + }; - let children = vbox.children(); - assert_eq!(children.len(), 1, "exactly the original child comes back"); - assert!( - children[0].downcast_ref::().is_some(), - "and it is the webview stand-in, not the overlay" + unsafe { + cr.draw_from_gl( + &window, + texture as i32, + GL_TEXTURE, + scale, + 0, + 0, + width, + height, ); - } - - /// A vbox Tauri has not populated is a changed assumption, not a panic. - /// - /// TRACES: UR-080 | DR-231 - #[test] - #[ignore = "requires a display"] - fn test_an_empty_vbox_is_an_error_not_a_panic() { - if gtk::init().is_err() { - return; - } - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - assert!(matches!(attach(&vbox), Err(SurfaceError::NoWebviewChild))); + // Tell mpv the frame reached the screen. Without this it has nothing to + // pace against — see DR-233. + render.report_swap(); } } diff --git a/src/lib/components/library/EpisodeFocusView.svelte b/src/lib/components/library/EpisodeFocusView.svelte index 509f97b7..c9ba767e 100644 --- a/src/lib/components/library/EpisodeFocusView.svelte +++ b/src/lib/components/library/EpisodeFocusView.svelte @@ -8,6 +8,7 @@ --> diff --git a/src/lib/components/player/Queue.svelte b/src/lib/components/player/Queue.svelte index f7a44fb5..1be5335d 100644 --- a/src/lib/components/player/Queue.svelte +++ b/src/lib/components/player/Queue.svelte @@ -1,5 +1,6 @@