Add bounded session playback, synced audio and frame thumbnails
This commit is contained in:
@@ -262,3 +262,99 @@ active, it exits immediately. It stops discovering new games once closed,
|
||||
so there is no indefinitely running invisible app. Reopening the window and
|
||||
persistent background discovery need the future tray integration. Explicit
|
||||
Quit/Ctrl+Q stops and finalises the current recording immediately.
|
||||
|
||||
## Playback and thumbnails
|
||||
|
||||
Playback uses the shared `clipbin-media` crate (`ffmpeg-next` 9) without GPUI.
|
||||
One background decoder opens only completed, available segments. Session time
|
||||
comes from `Metadata::locate`; each file's timestamps are normalised independently.
|
||||
Seeking goes backwards to a keyframe, then decodes forward to the selected frame.
|
||||
A one-frame mailbox and coalesced seeks bound work; overdue frames are decoded
|
||||
but discarded before swscale. Playback threads have lower scheduling priority
|
||||
than recording. Leaving the editor closes its decoder and audio stream.
|
||||
|
||||
The editor uploads packed BGRA at its actual pixel size, maintains a 16:9
|
||||
preview, and releases old GPUI images. Space/K toggle playback; arrows/J/L step
|
||||
one frame. The timeline seeks and drags across files and gaps; completed footage
|
||||
from active sessions becomes browsable on the next 250 ms metadata refresh
|
||||
within the recorder's segment completion delay. Missing/removed footage advances
|
||||
on the wall clock and shows “No footage (not captured / cleaned up)”. There is
|
||||
no live recording preview.
|
||||
|
||||
Audio uses cpal and swresample at the output device's rate/channel count, with
|
||||
sample conversion in the device callback. OBS's combined third audio track is
|
||||
selected when three tracks exist; otherwise the first track is used. The queue
|
||||
holds at most 250 ms plus one decoded block. Callback timestamps, including DAC
|
||||
latency, drive the master clock; gaps and mute use the wall clock. Callbacks do
|
||||
no decoding and never wait for the queue lock. Mute and volume are in the editor;
|
||||
an unavailable device is shown explicitly while video remains usable. The Linux
|
||||
rig uses its existing null sink and ALSA's PipeWire plugin as a fallback if cpal's
|
||||
native PulseAudio backend refuses the device. Install/configure `pipewire-alsa`
|
||||
or an ALSA default routed to PulseAudio on systems without a physical ALSA card.
|
||||
|
||||
One low-priority worker produces 320×180 PNG thumbnails beside the first playable
|
||||
session segment and exported MP4s. Its request queue holds 16 paths and duplicate
|
||||
requests coalesce; card discovery and all filesystem work run off the UI thread.
|
||||
`clipbin_media::thumbnail::{generate,cache_path,Thumbnails}` accept any MP4/MKV.
|
||||
The gradient remains while a thumbnail is pending or footage is unavailable.
|
||||
|
||||
`scripts/validate/playback-fixture.sh` generates independent 1080p60 H.264/AAC
|
||||
segments with one-second keyframes and a three-tone audio fixture. It passed on
|
||||
2026-10-08: exact frame pixels at frame indices 0/1/59/60/61/119 in every file,
|
||||
real-time boundary crossing, gaps, superseded seeks, pause/seek clock maths,
|
||||
bounded frame placement, resampling 48 kHz to 44.1 kHz and combined-track selection.
|
||||
The ordinary workspace tests skip the generated-media assertions unless
|
||||
`CLIPBIN_TEST_MEDIA_DIR` points to the fixture; use the script to exercise them.
|
||||
|
||||
Windows: the cpal WASAPI dependency cross-check passed, as did the dedicated
|
||||
Windows thread-priority bridge. Core/OBS/probe strict MSVC cross-lint passed.
|
||||
The full media cross-lint is excluded on this Linux machine because the FFmpeg
|
||||
Windows development import libraries are unavailable. On Windows set `FFMPEG_DIR`
|
||||
to an FFmpeg **7.1 shared development** package with MSVC import libraries matching
|
||||
the OBS-bundled avcodec-61/avformat-61/avutil-59/swscale-8/swresample-5 DLLs. Linux
|
||||
links system FFmpeg 8. Actual Windows decoding, WASAPI device latency/seek behaviour,
|
||||
GPUI image presentation and thumbnail caching need a Windows machine. Hardware
|
||||
video decode is not used; HEVC/AV1 software performance and real output devices
|
||||
other than the rig null sink still need measurement.
|
||||
|
||||
Final playback validation passed on 2026-10-08, serially with other rigs stopped:
|
||||
|
||||
- `cargo fmt --all -- --check`, strict locked workspace clippy, and locked
|
||||
workspace tests (with `CLIPBIN_TEST_MEDIA_DIR=target/playback-fixture`).
|
||||
- `env PATH=/usr/bin:/bin:$HOME/.cargo/bin scripts/validate/linux-rig.sh`:
|
||||
all nine scenarios. An earlier concurrent run lost its H.264 probe to SIGKILL;
|
||||
the serial rerun passed without timeout or sandbox changes.
|
||||
- The same PATH with `scripts/validate/linux-session.sh`: automatic recording,
|
||||
exact segment placement, close-window survival, game exit, library reload and
|
||||
clean Quit. Repeated after the final keyboard integration, also passed.
|
||||
- `scripts/validate/linux-playback.sh target/playback-e2e-final-6`: moving preview
|
||||
(11.0% changed pixels inside the preview), active recording with zero reported
|
||||
recording drops, completed-segment availability, real session thumbnail,
|
||||
cross-file drag, exact post-boundary frame/time, cleaned-up gap, zoom, arrow
|
||||
stepping, device audio, half-volume and mute. The audio monitor RMS was 0.08044;
|
||||
the player asserts video time stays within 20 ms of the audio master clock.
|
||||
Screenshots were inspected: `sessions-thumbnails.png`, `editor-playing-1.png`,
|
||||
`editor-playing-2.png`, `editor-seek.png`, and `editor-gap.png` in that directory.
|
||||
|
||||
The rig runs on an Intel i5-12500 (12 logical CPUs). These are development builds
|
||||
with dependencies at opt-level 2, software video decoding, 1080p60 H.264 source
|
||||
and an 800×450 target (the editor used 740×416). Five serial repetitions of
|
||||
`target/debug/examples/playback-check <sessions-root> <id>` with
|
||||
`scripts/validate/measure-cpu.py`, inside the null-sink rig, measured process CPU
|
||||
at **16.7% median / 17.7% p95 of one core** (12.2–17.7% range). The filesystem cache
|
||||
was warm; each repetition reopened its decoders. Across presented frames,
|
||||
decode/scale time was **2.39 ms median / 8.80 ms p95**; maximum **101.5 ms** included
|
||||
opening files and decoding forward on seeks. This is a test pattern with gaps,
|
||||
not a worst-case game or a release-build benchmark. Per-run logs and CPU reports
|
||||
are in `target/playback-timing-final`; `report.json` stores the distributions.
|
||||
|
||||
During the active-session e2e, the player presented **59.2 frames/s** while
|
||||
recording remained at zero reported drops. GPUI upload/construction time was
|
||||
**0.22 ms median / 0.48 ms p95**. UI frame intervals (including vsync, not CPU work)
|
||||
were 16.2 ms median / 32.5 ms p95; the one-frame mailbox drops presentation work
|
||||
rather than allowing delay to accumulate. `CLIPBIN_TRACE_FRAMES=1` logs decoded
|
||||
video time, audio time, decode/scale work, uploads and actual timeline bounds.
|
||||
The headless e2e restores keyboard focus after virtual-pointer commands and
|
||||
holds virtual keys briefly: instantaneous virtual input can disappear before
|
||||
GPUI/Wayland dispatch. Keyboard actions update the editor entity directly,
|
||||
avoiding a nested window borrow during event dispatch.
|
||||
|
||||
Reference in New Issue
Block a user