From ab95f5013d277a23c8f290b2d2c7ac0e1f4f990f Mon Sep 17 00:00:00 2001
From: Duncan Tourolle
Date: Sun, 16 Aug 2026 22:14:43 +0200
Subject: [PATCH] feat(player): make native Android video the default
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The two defects that were holding the flip back are fixed and verified on a
device, which is the standard this default has been held to since DR-161 shipped
a verified sub-path over an unverified one:
- returning from background audio restarts the renderer that is actually on
screen, instead of only ever reloading the element (DR-196)
- the letterbox bars are painted, instead of retaining whatever was last in
the framebuffer (DR-194)
Evidence: handoff to audio-only at 69:54 returning to video playing at 70:18,
and clean bars across playback, the control bar and a rotation round-trip.
An explicit stored choice still wins in both directions, so anyone who turned the
flag off keeps it off — hence the null check on the stored value rather than a
bare === "true", which would silently re-enable it for people who opted out.
The Settings copy no longer tells users to leave it off; it now describes the
toggle as the fallback to the built-in web player.
The flag keeps its "experimental" name because it remains a suppressor of Rust's
backend choice, never a promoter: turning it on cannot produce a native backend
where Rust says HTML5.
---
src/lib/stores/nativeVideo.ts | 45 +++++++++++++++++++-------------
src/routes/settings/+page.svelte | 5 ++--
2 files changed, 29 insertions(+), 21 deletions(-)
diff --git a/src/lib/stores/nativeVideo.ts b/src/lib/stores/nativeVideo.ts
index afa6f286..61bb5ea2 100644
--- a/src/lib/stores/nativeVideo.ts
+++ b/src/lib/stores/nativeVideo.ts
@@ -50,30 +50,37 @@ const NATIVE_VIDEO_ATTR = "data-native-video";
*
* The picture is genuinely fixed and device-verified — `WebView transparent =
* true` and `Marking media ready` now appear in logcat with video on screen,
- * the pair DR-172 went looking for and could not find. **The default is still
- * off**, because turning it on surfaced a different gap: the background-audio
- * handoff (UR-040) can only *return* through the HTML5 element.
- * `applyPendingForegroundSeek` bails on `!videoElement`, the HLS re-init effect
- * bails on `!useHtml5Element`, and `handleCanPlay` — the event that owns the
- * post-handoff position and play state — is an element event that never fires
- * natively. So coming back from background audio leaves playback dead.
+ * the pair DR-172 went looking for and could not find. The default nonetheless
+ * stayed **off** for a further release, because turning it on surfaced a
+ * different gap: the background-audio handoff (UR-040) could only *return*
+ * through the HTML5 element, so coming back from background audio left playback
+ * dead. That was the same shape of mistake as DR-161 — a verified sub-path
+ * shipped as a default over an unverified one — so the flip waited (DR-190).
*
- * That is the same shape of mistake as DR-161: a verified sub-path shipped as a
- * default over an unverified one. The evidence standard this branch set for the
- * picture applies to the handoff too, so the flip waits for it (DR-190).
+ * - **on** now. The two defects that were holding it back are fixed and
+ * verified on a device: the handoff return restarts the renderer that is
+ * actually on screen rather than only ever reloading the `` element
+ * (DR-196), and the letterbox bars are painted instead of retaining whatever
+ * was last in the framebuffer (DR-194). The evidence standard this default
+ * has been held to since DR-161 is met for both: audio handoff at 69:54
+ * returning to video playing at 70:18, and clean bars across playback, the
+ * control bar and a rotation round-trip.
*
* An explicit stored choice still wins in both directions, so anyone who turned
- * it on keeps it on.
+ * it off keeps it off — hence the `null` check rather than a bare `=== "true"`,
+ * which would silently re-enable it for people who opted out.
*
* TRACES: UR-003, UR-004 | DR-188
*/
function load(): boolean {
- if (typeof localStorage === "undefined") return false;
+ if (typeof localStorage === "undefined") return true;
try {
- return localStorage.getItem(STORAGE_KEY) === "true";
+ const stored = localStorage.getItem(STORAGE_KEY);
+ // Never chosen → on. Chosen → honour it, in both directions.
+ return stored === null ? true : stored === "true";
} catch {
- // Private-mode / disabled storage — default to the path whose handoff works.
- return false;
+ // Private-mode / disabled storage — same default as a fresh install.
+ return true;
}
}
@@ -101,9 +108,11 @@ function createExperimentalNativeVideoStore() {
}
/**
- * User opt-in for the native Android video path. **Defaults to off** — see
- * `load()`. The name says "experimental" because the flag remains a suppressor
- * of Rust's backend choice, not a promoter of it.
+ * User preference for the native Android video path. **Defaults to on** — see
+ * `load()`. The name still says "experimental" because the flag remains a
+ * suppressor of Rust's backend choice, not a promoter of it: turning it off
+ * forces the webview element, turning it on never produces a native backend
+ * where Rust says HTML5.
*/
export const experimentalNativeVideo = createExperimentalNativeVideoStore();
diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte
index 608698de..ac2de9c7 100644
--- a/src/routes/settings/+page.svelte
+++ b/src/routes/settings/+page.svelte
@@ -746,9 +746,8 @@
Decode video with the device's hardware decoder instead of the
built-in web player, for better performance and battery life,
and so picture-in-picture shows the video rather than the app.
- The picture works, but background audio does not come back from
- the lockscreen on this path yet — leave it off unless you are
- helping test it.
+ On by default. Turn it off to fall back to the built-in web
+ player if a video misbehaves.