Frame-accurate video decode via mediabunny — fixes video/pose mismatch#141
Conversation
Preview DeploymentPreview has been removed. |
This reverts commit ce83acf.
Hacky quick fix: snap-to-frame on pausePlayback now runs on the native Rather than chase perfect real-time sync on the native path, this PR adds a deliberate
This is literally automating the thing we already knew fixed it — pressing "next frame" Scope (intentionally narrow):
Known trade-off: pausing advances the frame by one (that's the "forward" in the fix). This is explicitly a quick fix, not the real solution — see the follow-up issue on the Tests: |
Frame-accurate video decode via mediabunny — fixes video/pose mismatch (#115)
Closes #115.
Summary
When stepping through frames, the pose skeleton could sit on the wrong video frame
(most visible on fast-moving nodes like the tail), making predictions look misplaced
"reprojection-like". This PR fixes it by decoding video frames through sleap-io.js's
frame-accurate
MediaBunnyVideoBackend(WebCodecs) instead of relying on HTML5<video>.currentTimeseeking, which is not frame-accurate.Confirmed fixed on the original (un-re-encoded) HEVC session on real hardware:
video and pose now match while stepping.
Root cause
The pose data was never the problem — it imports byte-for-byte correctly and exports
perfectly (verified across all three SLP backends and confirmed by round-tripping through
the SLEAP GUI). The bug is display-only:
OnDemandVideoDecoderextracts every frame by seeking the HTML5<video>element(
currentTime = frameIdx / fps). That seek is not frame-accurate — for a long-GOPvideo it can return a frame a whole GOP behind the one requested (
currentTimereadsback exactly right, but the displayed/captured frame is stale). The correct skeleton
overlay then draws on top of a stale video image, so the animal has moved and the tail
misses.
(HTML5 was the only live extraction path — the class's WebCodecs branch was disabled from
the start, and its
decodeRangewas never actually exercised.)The fix
sleap-io.js already ships
MediaBunnyVideoBackend, which decodes exact frames viaWebCodecs (
sink.getSample(frameTimestamp)) and handles keyframe seeking correctly.mediabunny was stubbed out in LUCID; this PR vendors the real library and routes the
per-frame path through the backend.
lib/mediabunny/mediabunny.min.mjs(npmmediabunny@1.30.0browser ESM, matching sleap-io.js's
^1.30.0). Importmapmediabunnyrepointed fromthe stub to it in
index.html+tests/test-runner.html;MediaBunnyVideoBackendexposed on
window.SleapIO.loading/video.js—OnDemandVideoDecodergains a frame-accurate path:init()buildsMediaBunnyVideoBackend.fromBlob(_initMediabunny) and adopts itsauthoritative frame count / fps;
getFrame()decodes exact frames through it with atransparent HTML5 fallback on any miss/error; playback still uses the HTML5 element;
close()frees the backend.lib/sleap-io/chunk-M65RB7KH.js: sleap-io's backend calledsample.toVideoFrame()but never closed the underlyingVideoSample(
"A VideoSample was garbage collected without first being closed"), which can exhaustthe WebCodecs frame pool over a long session. Both sites now
sample.close(), marked// LUCID local patch (#115).marker); should be reported upstream.
Enabling it (currently opt-in)
Off by default (zero change to current behavior). Enable per-browser:
Confirm it engaged — the console shows
[video] … [mediabunny frame-accurate], or:Disable with
delete localStorage.LUCID_VIDEO_BACKEND+ reload.Testing
tests/test-mediabunny-backend.js(new, 6 cases) — guards the integration wiring:flag detection (
_mediabunnyEnabled),getFramerouting through the backend, HTML5fallback on null/throw, HTML5-direct when no backend, and
close()releasing it. Thesedeliberately don't assert decode frame-accuracy (not headless-testable — see above).
patch (was 1058 + 6 new). No page errors from the importmap swap.
Not needed: re-encoding
We also tried re-encoding the HEVC to short-GOP H.264, which helped the HTML5 path a
little but did not fully fix it — the mediabunny backend fixes it on the original
videos, so no re-encode is required.
Files changed
lib/mediabunny/mediabunny.min.mjs,PROVENANCE.txtlib/sleap-io/chunk-M65RB7KH.jsindex.html,tests/test-runner.htmlloading/video.jsOnDemandVideoDecodertests/test-mediabunny-backend.jsMODULES.md,CLAUDE.mdThe old
lib/sleap-io/mediabunny-stub.jsis now unused (left in place; harmless).Base branch
This branch (
eric/prediction-mismatch) is stacked on the cross-view-tracker PR (#131).Target this PR at that branch, or rebase onto
mainafter #131 lands, so the diff is onlythe files above.