feat: step to adjacent decoded video frames

This commit is contained in:
2026-10-10 17:49:04 +02:00
parent 310be5aa61
commit a99b0c1168
6 changed files with 311 additions and 29 deletions
+14
View File
@@ -52,6 +52,7 @@ final class PlayerModel: ObservableObject {
(state.status == PLAYER_PAUSED || state.status == PLAYER_PLAYING || state.status == PLAYER_ENDED)
}
var canSeek: Bool { canTogglePlayback && state.duration.isFinite && state.duration > 0 }
var canStepFrame: Bool { canTogglePlayback && state.has_video && !isSeeking }
var canOpen: Bool { handle != nil && !isBusy }
var canClose: Bool { handle != nil && !isBusy && state.status != PLAYER_EMPTY }
var canSetVolume: Bool { handle != nil && !isBusy && state.has_audio && state.status != PLAYER_ERROR }
@@ -155,6 +156,14 @@ final class PlayerModel: ObservableObject {
revealControls()
}
func stepFrame(_ direction: Int32) {
guard canStepFrame, let handle else { return }
let accepted = player_step_frame(handle, direction)
if accepted { seekPending = true }
commandError = accepted ? nil : "Could not queue the frame step. Please try again."
revealControls()
}
func setVolume(_ value: Double) {
guard let handle else { return }
if player_set_volume(handle, Float(value)) {
@@ -697,6 +706,8 @@ private struct WindowChrome: NSViewRepresentable {
switch key {
case "j": model.seekRelative(-10)
case "l": model.seekRelative(10)
case ",": model.stepFrame(-1)
case ".": model.stepFrame(1)
case "k", " ":
if !event.isARepeat { model.togglePlayback() }
case "m":
@@ -825,6 +836,9 @@ struct PlayerApp: App {
Button("Seek Back 10 Seconds") { model.seekRelative(-10) }.disabled(!model.canSeek)
Button("Seek Forward 10 Seconds") { model.seekRelative(10) }.disabled(!model.canSeek)
Divider()
Button("Previous Frame") { model.stepFrame(-1) }.disabled(!model.canStepFrame)
Button("Next Frame") { model.stepFrame(1) }.disabled(!model.canStepFrame)
Divider()
Button("Volume Up") { model.setVolume(min(1, model.volume + 0.05)) }
.keyboardShortcut(.upArrow, modifiers: []).disabled(!model.canSetVolume)
Button("Volume Down") { model.setVolume(max(0, model.volume - 0.05)) }