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.
This commit is contained in:
@@ -303,10 +303,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.player.mpv.mute = checked
|
self.player.mpv.mute = checked
|
||||||
|
|
||||||
def _seek_relative(self, delta_seconds: float):
|
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:
|
if self.input_path is None:
|
||||||
return
|
return
|
||||||
self.player.pause()
|
|
||||||
self.play_button.setText("Play")
|
|
||||||
duration = self.player.mpv.duration or 0.0
|
duration = self.player.mpv.duration or 0.0
|
||||||
target = min(max(self.player.get_position() + delta_seconds, 0.0), duration)
|
target = min(max(self.player.get_position() + delta_seconds, 0.0), duration)
|
||||||
self.player.seek(target)
|
self.player.seek(target)
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user