Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e98e1c37a | ||
|
|
440d7a01a9 |
@@ -9,6 +9,65 @@ generated trace matrix lives in [docs/traceability.md](docs/traceability.md).
|
||||
For how long each fixed defect had been shipping before it was found, see
|
||||
[docs/defect-windows.md](docs/defect-windows.md).
|
||||
|
||||
## v0.6.0
|
||||
|
||||
### 🐛 Fixes
|
||||
|
||||
- **Android native video actually shows a picture.** It shipped once as *audio
|
||||
with no picture* and was reverted with the compositing named as the suspect
|
||||
(DR-172). The compositing was not at fault; five independent defects sat
|
||||
between ExoPlayer and the screen, each able to produce that symptom alone. The
|
||||
app shell painted over the video surface through a CSS rule targeting
|
||||
`[data-app-shell]`, an attribute no component had ever set in any commit
|
||||
(DR-185). The poster/title card had no way to lift on a path that renders no
|
||||
`<video>` element, so a black card covered the surface for the whole session
|
||||
(DR-182). The JavaScript bridges were installed by a 500 ms tree walk that
|
||||
raced the page load — and lost permanently when it lost, because the
|
||||
re-injection guard then declined to retry — so `setTransparent(true)` could
|
||||
never arrive (DR-183). The `SurfaceView` was never detached, leaking one per
|
||||
video and leaving picture-in-picture's gate stuck open (DR-184). Verified on a
|
||||
device: logcat now carries `WebView transparent = true` and
|
||||
`Marking media ready` with video on screen, the pair the original
|
||||
investigation went looking for and could not find.
|
||||
(UR-003, UR-004, UR-041 → DR-182, DR-183, DR-184, DR-185)
|
||||
|
||||
- **Play and pause reach the player that is actually rendering.** Transport did
|
||||
nothing on the native video path — from the on-screen tap, from the control
|
||||
bar, and from a direct command invocation — while seek and skip kept working,
|
||||
because those decide elsewhere. Rust routes play/pause to the webview `<video>`
|
||||
whenever it believes one is active, and the player route mirrored element state
|
||||
into that belief unconditionally, including from a ten-second progress
|
||||
interval. So on the native path the frontend re-declared every ten seconds that
|
||||
an element was playing when none existed, and every intent was emitted at
|
||||
something that was not there. The mirror now lives where `useHtml5Element` is
|
||||
known. This also explains the flashing transport controls, since they key off
|
||||
the play state that was being contradicted on every tick.
|
||||
(UR-005, UR-003 → DR-193, DR-195)
|
||||
|
||||
- **The player's controls hide themselves on a touchscreen.** The auto-hide timer
|
||||
was armed only from `mousemove`, which a touch device never fires, so the
|
||||
control bar stayed over the video for the whole film. It is now armed on entry
|
||||
and on every touch, and pinned open while paused, seeking, or with a menu open.
|
||||
(UR-003, UR-066 → DR-189)
|
||||
|
||||
- **The system bars go away with the player.** Immersive mode had exactly one
|
||||
caller — the fullscreen button — so opening a video left the status and
|
||||
navigation bars painted over it until the user pressed a control most never
|
||||
press. (UR-066, UR-003 → DR-187)
|
||||
|
||||
### 🔬 Internal
|
||||
|
||||
- Native video presents through a `TextureView` rather than a `SurfaceView`. A
|
||||
SurfaceView renders on its own layer outside the app window and punches a
|
||||
transparent region through it, and Android's own graphics documentation warns
|
||||
that overlays do not composite reliably above one. (UR-003, UR-004 → DR-192)
|
||||
|
||||
- Native Android video remains **opt-in**, and is not yet the default. Turning it
|
||||
on surfaced a further unverified path: returning from background audio is
|
||||
implemented only for the webview element, so playback stays dead on the native
|
||||
path (DR-190, proposed). Rotation still needs device confirmation (DR-194).
|
||||
(UR-003 → DR-188)
|
||||
|
||||
## v0.5.5
|
||||
|
||||
### ✨ Features
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jellytau",
|
||||
"version": "0.5.5",
|
||||
"version": "0.6.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.5",
|
||||
|
||||
Generated
+1
-1
@@ -2018,7 +2018,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jellytau"
|
||||
version = "0.5.5"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "jellytau"
|
||||
version = "0.5.5"
|
||||
version = "0.6.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "jellytau",
|
||||
"version": "0.5.5",
|
||||
"version": "0.6.0",
|
||||
"identifier": "com.dtourolle.jellytau",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render } from "@testing-library/svelte";
|
||||
import { tick } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import VideoPlayer from "./VideoPlayer.svelte";
|
||||
import { SEEK_FORWARD_SECONDS } from "./tapGestures";
|
||||
|
||||
@@ -128,6 +129,23 @@ function renderPlayer() {
|
||||
describe("VideoPlayer tap surface (real component)", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// This file deliberately does NOT mock `$lib/api/bindings` — it renders the
|
||||
// real component against the real bindings, which bottom out in the globally
|
||||
// mocked `invoke`. That mock resolves `undefined` for every command, so the
|
||||
// commands whose results are *rendered* have to be answered here: the
|
||||
// quality picker assigns the result straight to state and then does
|
||||
// `streamingQualities.length` in the template, which throws (asynchronously,
|
||||
// outside any test) on undefined and fails the run with an unhandled error.
|
||||
vi.mocked(invoke).mockImplementation(async (cmd: string) => {
|
||||
switch (cmd) {
|
||||
case "player_get_streaming_qualities":
|
||||
return [];
|
||||
case "player_get_video_settings":
|
||||
return { streamingQuality: "original" };
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("a single tap on the video toggles play/pause exactly once", async () => {
|
||||
|
||||
Reference in New Issue
Block a user