feat(player): make native Android video the default

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 <video> 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.
This commit is contained in:
2026-08-16 22:14:43 +02:00
parent 5e8efa252e
commit ab95f5013d
2 changed files with 29 additions and 21 deletions
+27 -18
View File
@@ -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 `<video>` 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();