The startup hold waited for the current frame to be cached before
anything else, so a frame that can never be cached held the first frame
for ever: a zero RAM preview budget, or a single frame larger than the
budget (which the comp-size estimate misses for Extended Viewer frames).
It now gives up at once when the budget can't hold even one comp-sized
frame, and otherwise after STARTUP_WAIT (2 s from pressing play): the
sound starts from the first frame, and the preview goes on as a stalled
one does. frame_bytes() is factored out of frames_that_fit() for it.
Tests (ui_audio_scrub): with a budget of 0 or half a frame the sound
starts on the first step; with an Extended Viewer frame larger than the
whole budget (the estimate says one fits) the first frame holds for about
two seconds, then the sound starts. Both wait for ever on the previous
commit. The Extended Viewer setup is now shared by its two tests.
The start held the first frame until `ahead.min(fit)` frames were cached,
which could never happen in two cases, holding it for ever:
- the prefetch requests offsets 0..min(ahead * 3, fit - 1) from the
playhead, one frame fewer than that when the RAM preview limits it, and
with the playhead held nothing else requested the last one;
- `fit` counts comp-sized frames, but Extended Viewer frames render
larger, so fewer of them fit in the budget than it needs at once.
It now waits for startup_frames(ahead, window, budget, frame bytes):
ahead, no more than the prefetch window requests, and no more than fit
in the budget at the size the first frame really rendered at, at least
the current frame.
Tests: startup_frames unit cases (a 4-frame budget needs 3; a 160×120
comp's 320×248 Extended Viewer frames need 2); ui_audio_scrub starts from
a cold cache with budgets of 2, 3, 4, ahead - 1, ahead and ahead + 1
frames, with and without a device, and with Extended Viewer frames larger
than the comp: each leaves its startup, from frame 0 (both hold the first
frame for ever on the previous commit). Also: a device whose start fails
plays the picture; a device that consumes the sound moves the picture on
and a stall still stops it; the 30 s preview starts without caching the
whole work area; Stop after a step of preparation never opens the output.
Cause: a preview with sound and video, without Cache Before Playback,
held its sound while the playhead advanced silently through frames as
they rendered, and started the sound at the frame it had reached once the
frames ahead were cached or rendering had kept up for a quarter second.
So the opening (about the first third of a second) was never heard, and
those frames played silently. With Video off, or Cache Before Playback,
the sound started at the first frame.
Fix: starting such a preview now holds the playhead on its first frame
while the frames ahead render (the prefetch window, at most what fits in
the RAM preview: not the whole work area), then starts sound and picture
together from that frame, the sound's clock driving the picture. Without
an output device the picture plays from that frame on its own clock. A
stall later on still stops the sound and catches up silently. Stop clears
the startup state.
Tests (ui_audio_scrub): the two startup tests no longer accept silent
frames first: the playhead must hold frame 0 until the sound starts, and
the sound must start at frame 0 (also for a 30 s work area, without
caching it all). A tone lasting only the first 100 ms after a start at
0 s and at 1 s reaches the output device. Video off still starts at
once; no device plays the picture; Stop while preparing leaves nothing
playing. The three startup tests fail on the previous code (the playhead
had moved on to frame 1, silently).
The shared release workflow installs trunk, but EffectCraft's web build
runs the wasm-bindgen CLI directly (cargo xtask web), so the Web job
fails with "wasm-bindgen CLI not found". In CI, package.sh now installs
the CLI at xtask's WASM_BINDGEN version, as the per-app release.yml did
before the move to craft-common-actions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous release build failed in craft-common-actions, not in this
repo: riscv64 packaging got --formats deb,rpm,tar (fixed in
craft-common-actions#3), Linux jobs lacked the ALSA headers
(craft-common-actions#5), and Windows signing needed the AZURE_* secrets
listed in the caller.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Renderer: no cover step for adjustment layers (#686); their buffer is the comp below.
Restores a single CPU step in split adjustment stacks.
- Effect Controls rename (#953): focus the name field the frame it exists. Focusing an id
not yet drawn left AccessKit with a focus outside its tree (a panic in the consumer).
The tests double-click at 60 fps, within egui's double-click time.
- i18n: Russian, French and German entries for Copy Frame to Clipboard and the extrusion
Front/Bevel/Side/Back Color submenus added by today's other PRs.
- Tests: "fr" is now a valid language; the marker dialog's comment is focused on open
(#922), so Enter-is-OK is tested with the focus elsewhere; the toolbar gradient tests
give the selected layer its paint kind, since the toolbar mirrors it (#732).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Footage sources: the cancellable wrapper (#784) forwards frame_with (#752 float curves)
and problem (#887); problem's default lives on the trait.
- Media: a called-off decode (#784) is not a footage problem (#887).
- Effect Controls rename (#953): Escape cancels (egui drops focus on Escape the same
frame), the Rename item is translated; regression test.
- i18n (#873): drop the /out-i18n/ ignore rule and the note about an uncommitted script.
- Tests: text range selector caps (#926) re-borrow the layer; the RAM preview test (#793)
sets the comp's own Resolution (#462).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Viewer: each composition keeps its Resolution in the project, as in After Effects (#417)
# Conflicts:
# crates/ui-egui/src/control.rs
# crates/ui-egui/src/menus.rs
# crates/ui-egui/src/panels/viewer_tools.rs