Give the seek bar's interpolation interval one owner #165
No files matched your search
@@ -515,6 +515,75 @@ Four things about it, each of which costs an hour if met cold:
|
|||||||
script. Plug in over USB for anything longer than a couple of probes.
|
script. Plug in over USB for anything longer than a couple of probes.
|
||||||
- **The socket name carries the pid**, which changes on every launch, so
|
- **The socket name carries the pid**, which changes on every launch, so
|
||||||
it is resolved rather than remembered.
|
it is resolved rather than remembered.
|
||||||
|
- **A reinstall resets the runtime permissions**, and the grant dialog
|
||||||
|
is a separate activity that takes focus — so the app is up, `am start`
|
||||||
|
reports "delivered to currently running top-most instance", and
|
||||||
|
`pidof` is empty because it never got to the foreground.
|
||||||
|
`dumpsys window | grep mCurrentFocus` naming
|
||||||
|
`GrantPermissionsActivity` is the tell. `adb shell pm grant
|
||||||
|
app.yellowjacket.dev android.permission.READ_MEDIA_AUDIO` (and
|
||||||
|
`POST_NOTIFICATIONS`) ahead of the launch skips it.
|
||||||
|
|
||||||
|
### Calling a binding on the device
|
||||||
|
|
||||||
|
**The runtime call does not go over HTTP on Android**, and this is worth
|
||||||
|
knowing before an hour is spent on it. The WebView cannot deliver a
|
||||||
|
`fetch()` POST body to `shouldInterceptRequest`, so v3 routes runtime
|
||||||
|
calls through the `addJavascriptInterface` bridge instead: the
|
||||||
|
@wailsio/runtime installs a `customTransport` that calls
|
||||||
|
`window.wails.invokeAsync(id, payload)` and receives the answer on
|
||||||
|
`window._wailsAndroidCallback`. Two consequences:
|
||||||
|
|
||||||
|
- **`.playwright/init-events.js` does not transfer to the device.** Its
|
||||||
|
outbound half hooks `fetch`, which sees nothing here, and its
|
||||||
|
`call()` posts to `/wails/runtime`, which answers
|
||||||
|
`Invalid runtime call: missing object value` — the interceptor got the
|
||||||
|
URL with no body. Its *inbound* half is still right, because
|
||||||
|
`dispatchWailsEvent` is the entry point in every mode.
|
||||||
|
- **Hooking `fetch` from an eval is too late anyway**, on any platform:
|
||||||
|
the bundle captured its reference at module scope, so a wrapper
|
||||||
|
installed afterwards records nothing. That is why the harness is an
|
||||||
|
`initScript` and not a step in a spec.
|
||||||
|
|
||||||
|
What works is to borrow the bridge, chaining the runtime's own callback
|
||||||
|
so its pending calls still resolve:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const pending = new Map();
|
||||||
|
const prev = window._wailsAndroidCallback;
|
||||||
|
window._wailsAndroidCallback = (id, response, error) => {
|
||||||
|
if (!pending.has(id)) return prev && prev(id, response, error);
|
||||||
|
const p = pending.get(id); pending.delete(id);
|
||||||
|
const env = JSON.parse(response || "{}");
|
||||||
|
return env.ok ? p.resolve(env.data ?? env.text) : p.reject(new Error(env.error));
|
||||||
|
};
|
||||||
|
window.__yj = { call(name, args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const id = "yj" + Math.random().toString(36).slice(2);
|
||||||
|
pending.set(id, { resolve, reject });
|
||||||
|
window.wails.invokeAsync(id, JSON.stringify({
|
||||||
|
object: 0, method: 0, windowName: "",
|
||||||
|
args: { "call-id": id, methodName: "yellowjacket/backend/" + name, args: args || [] },
|
||||||
|
clientId: window._wails.clientId,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
} };
|
||||||
|
```
|
||||||
|
|
||||||
|
That turns the device into a tier that can be *driven* rather than only
|
||||||
|
looked at — `__yj.call("player.Player.LoadFile", [path])` and
|
||||||
|
`__yj.call("library.Library.AddLibrary", ["/sdcard/Music/..."])` are how
|
||||||
|
#53 was measured. Names are the Go ones (`GetTracks`, not
|
||||||
|
`GetAllTracks`); an unknown one comes back as a plain
|
||||||
|
`unknown bound method name`, so a wrong guess is loud.
|
||||||
|
|
||||||
|
**Getting audio onto the phone**: `adb push` into
|
||||||
|
`/sdcard/Android/data/<pkg>/files/` looks like it works and then the
|
||||||
|
files are not there — scoped storage. `/sdcard/Music/...` plus
|
||||||
|
`pm grant … READ_MEDIA_AUDIO` does work, and `AddLibrary` takes the
|
||||||
|
plain path. The generated fixtures are **~2 seconds** each, which is
|
||||||
|
fine for a scan and useless for watching a seek bar, so synthesise a
|
||||||
|
long one: `ffmpeg -f lavfi -i sine=frequency=440:duration=240`.
|
||||||
|
|
||||||
**And the reason to bother: the phone is an engine, not a screen.** The
|
**And the reason to bother: the phone is an engine, not a screen.** The
|
||||||
first device here renders in **Chrome 113** at 424x439 CSS px. Every
|
first device here renders in **Chrome 113** at 424x439 CSS px. Every
|
||||||
|
|||||||
@@ -4242,3 +4242,63 @@ took another ~10 s to come up.
|
|||||||
Harmless here (the emulator was up before anything used it) and a
|
Harmless here (the emulator was up before anything used it) and a
|
||||||
straightforward race otherwise: `start` should wait for a device that
|
straightforward race otherwise: `start` should wait for a device that
|
||||||
is an emulator, not for whatever `pick_device` returns. Filed as #162.
|
is an emulator, not for whatever `pick_device` returns. Filed as #162.
|
||||||
|
|
||||||
|
## The Android runtime transport is not HTTP (measured 2026-08-20)
|
||||||
|
|
||||||
|
Found while trying to drive the phone for #53. `wails3` routes runtime
|
||||||
|
calls through `addJavascriptInterface` on Android, not through
|
||||||
|
`/wails/runtime` — the WebView cannot deliver a `fetch()` POST body to
|
||||||
|
`shouldInterceptRequest`, which the v3 source says in as many words
|
||||||
|
(`application_android.go`, "The Android transport"). The runtime
|
||||||
|
installs a `customTransport` over `window.wails.invokeAsync(id,
|
||||||
|
payload)` and takes the answer on `window._wailsAndroidCallback`.
|
||||||
|
|
||||||
|
Two things follow, and both cost time before the source was read:
|
||||||
|
|
||||||
|
- **`.playwright/init-events.js` does not transfer to the device.** Its
|
||||||
|
outbound half hooks `fetch`; a POST to `/wails/runtime` answers
|
||||||
|
`Invalid runtime call: missing object value`, which reads like a
|
||||||
|
wrong payload shape and is actually the interceptor receiving a URL
|
||||||
|
with no body at all. The payload shape was right the whole time. Its
|
||||||
|
*inbound* half is still correct, because `dispatchWailsEvent` is the
|
||||||
|
entry point in every mode.
|
||||||
|
- **Hooking `fetch` from an `eval` is too late on any platform.** The
|
||||||
|
bundle captured its reference at module scope, so a wrapper installed
|
||||||
|
afterwards records nothing — which is exactly why the harness is an
|
||||||
|
`initScript`. Measured: zero calls captured while the app was
|
||||||
|
demonstrably making them.
|
||||||
|
|
||||||
|
The working recipe is in `android-tier.md`; it chains the runtime's own
|
||||||
|
callback rather than replacing it, so its pending calls still resolve.
|
||||||
|
This is what makes the device a tier that can be *driven*.
|
||||||
|
|
||||||
|
## #53's frontend is byte-identical to the build it was reported against (2026-08-20)
|
||||||
|
|
||||||
|
`git diff v0.3.1 HEAD -- frontend/src/components/audio-player/seekbar/
|
||||||
|
frontend/src/store/player-store.ts` is **empty**; the whole diff in that
|
||||||
|
area is `backend/player/`. The phone carries the released `v0.3.1`, so
|
||||||
|
whatever #53 saw, the component was not what changed — and v0.4.0 is
|
||||||
|
where the player audit (#122–#127) landed.
|
||||||
|
|
||||||
|
Measured on that phone, current `main`, with a synthesised 4-minute
|
||||||
|
track: the Now Playing seek bar tracks correctly when mounted
|
||||||
|
mid-playback (`seekValue` 28 of 240), when the view is opened before
|
||||||
|
playback starts, after a tap on the track, and across an activity
|
||||||
|
recreation (same pid, bar resumes at 30 → 35). The issue's stated
|
||||||
|
symptom did not reproduce in any of them.
|
||||||
|
|
||||||
|
Reverting **only** `backend/player/` to v0.3.1 — the frontend and
|
||||||
|
everything else at HEAD — does reproduce a real position defect on the
|
||||||
|
same device: six seconds into a 20-second file with no database row,
|
||||||
|
played after a 240-second one, the bar read **01:27 of 240**. That is
|
||||||
|
#125's stale `trackLengthMs` ("cleared only by UnloadTrack, so a file
|
||||||
|
with no row inherited the previous track's duration"), and it is fixed
|
||||||
|
at HEAD. Note the *shape* of it: the fraction is roughly right and the
|
||||||
|
absolute numbers are wrong, so it presents as a clock that lies rather
|
||||||
|
than as a handle that will not move.
|
||||||
|
|
||||||
|
The one-line experiment is worth remembering: v0.3.1's `backend/player`
|
||||||
|
compiles against HEAD with a single shim
|
||||||
|
(`SetPlaybackFinishedHandler` gained a `srcErr error` parameter), which
|
||||||
|
makes "did the backend fix cause this" a ten-minute question instead of
|
||||||
|
a full checkout.
|
||||||
Reference in new issue
Block a user