fix: count repeated frame steps and retain the forward decoder cursor

This commit is contained in:
2026-10-10 18:35:13 +02:00
parent 2bb7d9b4ee
commit 131acbe41b
6 changed files with 130 additions and 21 deletions
+4 -4
View File
@@ -35,7 +35,7 @@ Opening video fits the window to its display aspect ratio, including non-square
Single-key shortcuts apply only to the playback window and do not intercept text editing, file-picker input, or Command/Control/Option combinations. Held toggle keys do not repeatedly toggle; held `J`/`L` continue seeking and held `,`/`.` continue stepping. `C` captions remains unbound.
Frame stepping uses decoded presentation timestamps, not an average-FPS time offset, including variable-frame-rate video and reordered B-frames. Steps start from the displayed frame, stop at the first/last frame, and keep the timeline paused. The last presented image remains visible during decoder preroll and queued steps; opening/closing media still clears the old image. Audio-only files disable stepping; normal playback resumes with audio synchronized to the selected frame.
Frame stepping uses decoded presentation timestamps, not an average-FPS time offset, including variable-frame-rate video and reordered B-frames. Steps start from the displayed frame, stop at the first/last frame, and keep the timeline paused. Consecutive same-direction repeats share one counted queue entry without skipping frames or reordering seeks/direction changes. The last presented image remains visible during decoder preroll and queued steps; the progress thumb follows each authoritative stepped position. Opening/closing media still clears the old image. Audio-only files disable stepping; normal playback resumes with audio synchronized to the selected frame.
- Existing shortcuts remain: **⌘O** opens files, **←/→** seeks five seconds, **↑/↓** adjusts volume by five percent, **⌃⌘F** toggles fullscreen, and **Esc** leaves fullscreen. Double-clicking video also toggles fullscreen.
- The timeline slider seeks on release and holds the requested position until queued seeking finishes. Both slider tracks span the native thumb-center travel range; the orange fill ends at the current volume thumb center. The volume thumb is opaque light gray.
@@ -58,9 +58,9 @@ Only implemented actions have controls; there are no placeholder playlist, subti
make check
```
Generates short fixtures and exercises the real playback interface and native outputs: audio/video, video-only, audio-only, non-square-pixel display aspect, pause, paused/playing and queued seek acknowledgement, exact bidirectional VFR/B-frame stepping across keyframes, queued/mixed steps, seek-to-step transitions, first/last-frame boundaries, stepping pause/resume, completion, replay, close, HDR rejection, failed-open recovery, and destruction while playing. It initializes the actual Metal shaders and uses an offscreen native window. It requires a working audio device and graphical macOS session, and plays short sine-wave audio. It is a runtime smoke check, not a mocked clock/device test.
Generates short fixtures and exercises the real playback interface and native outputs: audio/video, video-only, audio-only, non-square-pixel display aspect, pause, paused/playing and queued seek acknowledgement, exact bidirectional VFR/B-frame stepping across keyframes, repeated-step bursts beyond the ordinary command capacity, mixed seek/direction ordering, replacement media superseding a long pending run, first/last-frame boundaries, stepping pause/resume, completion, replay, close, HDR rejection, failed-open recovery, and destruction while playing. It initializes the actual Metal shaders and uses an offscreen native window. It requires a working audio device and graphical macOS session, and plays short sine-wave audio. It is a runtime smoke check, not a mocked clock/device test.
`make check-audio` runs just the audio-quality check. A check-only hook captures actual Core Audio callback PCM into preallocated storage and compares every sample against FFmpeg's reference output for PCM/AAC, 44.1/48 kHz, and coarse container timestamps. It checks fractional gain, silent muted startup, smooth live volume changes, and unchanged playback completion. A separate gapped fixture verifies that real timestamp gaps remain silent. Release builds contain no capture hook.
`make check-audio` runs just the audio-quality check. A check-only hook captures actual Core Audio callback PCM into preallocated storage and compares every sample against FFmpeg's reference output for PCM/AAC, 44.1/48 kHz, and coarse container timestamps. It verifies that repeated forward/backward stepping stays silent and that resumed audio matches the exact selected-frame PCM suffix. It also checks fractional gain, silent muted startup, smooth live volume changes, and unchanged playback completion. A separate gapped fixture verifies that real timestamp gaps remain silent. Release builds contain no capture hook.
Continuous audio uses resampled sample counts for its output timeline; packet timestamp quantization and rounded resampler delay must not insert silence or drop samples between blocks.
@@ -72,6 +72,6 @@ Visual correctness, perceived A/V synchronization, and control usability still n
Local regular files, automatically selected best audio/video tracks, software decoding, and SDR output. PQ/HLG HDR is rejected rather than rendered incorrectly. No subtitles, selectable tracks, VideoToolbox decoding, rotation/deinterlacing, or network playback yet. Mid-track audio format changes report an error.
Frame stepping re-decodes from an earlier keyframe and rebuilds audio at the selected timestamp. Intermediate decoded frames are discarded, but inter-frame dependencies still require decoding them. Every accepted repeated step counts; commands are not coalesced or skipped. Long-GOP or unindexed files can therefore step slowly even though the image no longer blanks; frames are not cached across the entire file. Stepping requires usable decoded presentation timestamps and reports an error rather than inventing frame times when they are absent.
Forward frame stepping retains the live video decoder cursor instead of seeking and re-decoding the same keyframe group for each repeat. Backward stepping still scans from an earlier keyframe; long-GOP or unindexed files can therefore step backward slowly, without blanking the image or allocating a command per key repeat. Audio is skipped while stepping and rebuilt once on resume from the verified preroll anchor; resuming after a long forward run can also require a longer preroll. Intermediate frames needed by inter-frame dependencies are decoded and discarded; frames are not cached across the entire file. Every accepted step counts. Stepping requires usable decoded presentation timestamps and reports an error rather than inventing frame times when they are absent.
Audio buffering is bounded to one second plus one decoded block (up to four seconds); video is bounded to 64 frames/128 MiB. Extremely interleaved files can drop far-future video frames to avoid starving audio; the ceiling and upgrade path are marked at `queueVideo()`. Device/stream latency is accounted for, but device changes during playback are not handled yet.