From 6f76b3f15a9fc401f5e645a6149406eaf76a0d79 Mon Sep 17 00:00:00 2001 From: ThePendulum Date: Sun, 26 Jul 2026 02:44:37 +0200 Subject: [PATCH] Arrow-key skip preserves play/pause state instead of always pausing _seek_relative() was unconditionally calling player.pause() before seeking. mpv's seek command doesn't itself change pause state, so simply not forcing it is enough: paused stays paused, playing keeps playing through the skip. (Frame-step still always pauses, correctly -- that's for precisely inspecting a single frame, a different use case from a coarse 5s nudge.) Verified by calling _seek_relative() directly rather than simulating the QShortcut keypress: a debug script isolated that PlayerWidget.seek() itself works fine while playing, and QTest.keyClick against this shortcut turned out to be unreliable in this headless setup even with Qt's active-window state forced -- consistent with the flakiness already noted in smoke_test_arrow_seek.py. The real key binding itself was already confirmed with live xdotool keypresses in an earlier session; this only needed to test the new state-preservation logic. --- main.py | 7 ++- scripts/smoke_test_preserve_play_state.py | 75 +++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 scripts/smoke_test_preserve_play_state.py diff --git a/main.py b/main.py index df88370..0da589b 100644 --- a/main.py +++ b/main.py @@ -303,10 +303,13 @@ class MainWindow(QMainWindow): self.player.mpv.mute = checked def _seek_relative(self, delta_seconds: float): + # Unlike frame-stepping (which always pauses -- you're precisely + # inspecting a frame), a 5s skip is a coarse nudge while scrubbing + # around; seeking doesn't change mpv's pause state on its own, so + # just not touching play/pause here is enough to preserve whichever + # state the user was already in. if self.input_path is None: return - self.player.pause() - self.play_button.setText("Play") duration = self.player.mpv.duration or 0.0 target = min(max(self.player.get_position() + delta_seconds, 0.0), duration) self.player.seek(target) diff --git a/scripts/smoke_test_preserve_play_state.py b/scripts/smoke_test_preserve_play_state.py new file mode 100644 index 0000000..6e8d82c --- /dev/null +++ b/scripts/smoke_test_preserve_play_state.py @@ -0,0 +1,75 @@ +"""Verifies arrow-key skips preserve whichever play/pause state the user +was already in, instead of always forcing a pause. + +Calls the real MainWindow._seek_relative() method directly rather than +simulating the QShortcut keypress: QTest.keyClick against this shortcut was +observed to be unreliable in this headless setup even with Qt's +active-window state forced (see smoke_test_arrow_seek.py's note) -- +sometimes the simulated key silently doesn't reach the shortcut at all, and +a debug script isolated that PlayerWidget.seek() itself works correctly +while playing, so the flakiness is in the synthetic-keypress dispatch, not +the app. The QShortcut -> _seek_relative wiring itself (a single line) was +already independently confirmed with real xdotool keypresses against a +live window in an earlier session. +""" +import sys +import time +from pathlib import Path + +from PyQt6.QtWidgets import QApplication + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +import main as app_main + +ROOT = Path(__file__).resolve().parent.parent +CLIP = str(ROOT / "test_media" / "sync_test_INSYNC.mp4") + + +def main(): + qapp = QApplication(sys.argv) + win = app_main.MainWindow() + win.show() + qapp.processEvents() + + win._load(CLIP) + qapp.processEvents() + time.sleep(0.2) + qapp.processEvents() + + # -- Case 1: paused before the skip -> stays paused after + assert win.player.mpv.pause is True + before_pos = win.player.get_position() + win._seek_relative(5.0) + qapp.processEvents() + time.sleep(0.2) + qapp.processEvents() + after_pos = win.player.get_position() + print(f"paused case: before={before_pos:.3f}s after={after_pos:.3f}s") + assert win.player.mpv.pause is True, "should still be paused after skip" + assert win.play_button.text() == "Play" + assert after_pos > before_pos + 4.0, "should have actually skipped forward" + print("PASS: stays paused across arrow-key skip when it started paused") + + # -- Case 2: playing before the skip -> stays playing after + win.player.seek(0.0) + win.player.play() + win.play_button.setText("Pause") + qapp.processEvents() + time.sleep(0.2) + before_pos = win.player.get_position() + win._seek_relative(5.0) + qapp.processEvents() + time.sleep(0.2) + qapp.processEvents() + assert win.player.mpv.pause is False, "should still be playing after skip" + assert win.play_button.text() == "Pause" + after_pos = win.player.get_position() + print(f"playing case: before={before_pos:.3f}s after={after_pos:.3f}s (should have jumped ~5s and kept playing)") + assert after_pos > before_pos + 4.0, "should have actually skipped forward" + + win.player.shutdown() + print("\nPRESERVE PLAY STATE SMOKE TEST PASSED") + + +if __name__ == "__main__": + main()