feat: step to adjacent decoded video frames
This commit is contained in:
@@ -27,12 +27,15 @@ Opening video fits the window to its display aspect ratio, including non-square
|
||||
| `K` / `Space` | Toggle playback |
|
||||
| `M` | Toggle mute, restoring the previous audible level |
|
||||
| `J` / `L` | Seek backward / forward ten seconds |
|
||||
| `,` / `.` | Step to the previous / next video frame; leave playback paused |
|
||||
| `F` | Toggle fullscreen |
|
||||
| `P` | Toggle keep-on-top (windowed mode) |
|
||||
| `O` | Open the file picker to switch files |
|
||||
| `Q` | Quit through normal application shutdown |
|
||||
|
||||
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` can continue seeking. `,` / `.` frame stepping and `C` captions are unbound until supported.
|
||||
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. 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.
|
||||
@@ -43,8 +46,8 @@ Only implemented actions have controls; there are no placeholder playlist, subti
|
||||
|
||||
## Where to change things
|
||||
|
||||
- `Sources/Core/player.h`: the C interface and authoritative state snapshot. Playback commands enqueue work; poll state for completion. `seek_pending` is set synchronously when a seek is accepted and clears when all queued seeks on that timeline finish. Volume set/get is synchronous and uses a lock-free target gain. Callers own the opaque handle and must stop using it before destruction.
|
||||
- `Sources/Core/player.cpp`: single demux/decode worker, bounded buffers, timestamps, audio-master synchronization, paused previews, decoder/resampler drain, seeking, errors, and shutdown. A seek stops output and flushes all old timeline state before resuming.
|
||||
- `Sources/Core/player.h`: the C interface and authoritative state snapshot. Playback commands enqueue work; poll state for completion. `seek_pending` is set synchronously when a seek or frame step is accepted and clears when all queued seeks/steps on that timeline finish. `player_step_frame` accepts only `-1` or `+1`, requires ready video, and publishes the selected frame's timestamp as `position`. Volume set/get is synchronous and uses a lock-free target gain. Callers own the opaque handle and must stop using it before destruction.
|
||||
- `Sources/Core/player.cpp`: single demux/decode worker, bounded buffers, timestamps, audio-master synchronization, paused previews, exact frame stepping, decoder/resampler drain, seeking, errors, and shutdown. A seek stops output and flushes all old timeline state before resuming.
|
||||
- `Sources/Core/output.hpp` and `Sources/macOS/Output.mm`: native output contract, allocation-free/nonblocking Core Audio callback and bounded audio ring, latency-aware presentation clock, retained frame mailbox, and Metal YUV conversion. The view can outlive the player safely.
|
||||
- `Sources/App/PlayerApp.swift`: MainActor state polling, file panel/Finder/drop handling, video host, AppKit glass and native slider cells, floating controls, visibility, video-fitted sizing, and native window integration.
|
||||
- `Makefile`: compiler/linker commands and app bundle construction. No generated Xcode project or additional build dependency.
|
||||
@@ -55,7 +58,7 @@ 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, 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, 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.
|
||||
|
||||
`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.
|
||||
|
||||
@@ -69,4 +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. Long-GOP or unindexed files can therefore step slowly; 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.
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user