Give each tab its own full timeline and transport (PlayerPanel refactor)

Extracts a new PlayerPanel widget (video + its own seek slider/marker
strip + its own centered frame-step/play/mute transport row) into
player_panel.py. Both Source and Preview tabs now use one, so
switching tabs switches a complete, independent monitor -- its own
scrubber, its own play state, its own frame-stepping -- not just the
video frame with a single shared play button underneath.

main.py's global keyboard shortcuts (,/./space/left/right) now
dispatch to whichever panel is active via a small _active_panel()
helper, so they follow the same 'whichever monitor you're looking at'
rule as the on-screen controls.

MarkerStrip (clap detection) and the Mark Audio/Video/offset/export
row stay outside the tabs, at the MainWindow level -- marking and
exporting are inherently about the source file, not the preview clip,
so only the timeline+transport moved, not the whole marking workflow.

Updated every existing smoke test for the new win.source_panel.* /
win.preview_panel.* structure (previously flat win.player,
win.seek_slider, etc.), and added a new check to
smoke_test_preview.py confirming the Preview tab's slider is a
genuinely separate, functional widget -- dragging it seeks the preview
player without moving the source player, which is the actual thing
this change was for.

Verified the full suite on the virtual Xvfb display, plus a live
click-through (mark both events, build preview, confirm the Preview
tab shows its own populated timeline/transport, not the source's).
This commit is contained in:
ThePendulum
2026-07-26 03:58:23 +02:00
parent 0a8f9f45bd
commit 8c34a0f0c2
10 changed files with 309 additions and 258 deletions
+7 -6
View File
@@ -47,7 +47,7 @@ def main():
ok = wait_for(lambda: win.detect_all_action.isEnabled(), qapp)
assert ok, "detection did not finish in time"
markers = win.marker_strip.markers()
markers = win.source_panel.marker_strip.markers()
print(f"detected markers: {markers}")
assert len(markers) >= 1, "expected at least one clap marker"
# ground truth beep is at t=5.350s in this fixture; onset detection has
@@ -59,12 +59,12 @@ def main():
# Simulate a real mouse click on the marker to prove click-to-seek works
# end to end, not just that the signal is wired.
target_time = min(markers, key=lambda m: abs(m - 5.350))
strip = win.marker_strip
strip.resize(win.seek_slider.width(), strip.height())
strip = win.source_panel.marker_strip
strip.resize(win.source_panel.seek_slider.width(), strip.height())
x = strip._x_for_time(target_time)
click_pos = QPointF(x, 5)
before_pos = win.player.get_position()
before_pos = win.source_panel.player.get_position()
event = QMouseEvent(
QMouseEvent.Type.MouseButtonPress, click_pos, click_pos,
Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier,
@@ -74,11 +74,12 @@ def main():
time.sleep(0.2)
qapp.processEvents()
after_pos = win.player.get_position()
after_pos = win.source_panel.player.get_position()
print(f"clicked marker at x={x:.1f} (t={target_time:.3f}s); player position before={before_pos:.3f}s after={after_pos:.3f}s")
assert abs(after_pos - target_time) < 0.1, "clicking the marker should seek the player to that timestamp"
win.player.shutdown()
win.source_panel.shutdown()
win.preview_panel.shutdown()
print("\nCLAP DETECTION SMOKE TEST PASSED")