diff --git a/main.py b/main.py index c5aa35c..33a402b 100755 --- a/main.py +++ b/main.py @@ -4508,6 +4508,9 @@ class AudioEditorDialog(QDialog): if not checked: self._stop_play() return + self._play_from_playhead() + + def _play_from_playhead(self) -> None: play_t = self._playhead_secs() se = self._selection_secs() if se is not None and se[0] <= play_t <= se[1]: @@ -4539,7 +4542,7 @@ class AudioEditorDialog(QDialog): end: float | None = None, loop: bool = False) -> None: from PyQt6.QtCore import QProcess - self._stop_play() + self._stop_play(reset_button=False) self._play_proc = QProcess(self) self._play_proc.finished.connect(self._on_play_finished) self._play_proc.errorOccurred.connect(self._on_play_error) @@ -4570,7 +4573,7 @@ class AudioEditorDialog(QDialog): def _on_wave_playhead_changed(self, t: float) -> None: if self._play_proc is not None: - self._stop_play() + self._play_from_playhead() def _playhead_secs(self) -> float: t = self._wave.playhead() @@ -4623,7 +4626,7 @@ class AudioEditorDialog(QDialog): self._btn_play.setChecked(False) self._btn_play.blockSignals(False) - def _stop_play(self) -> None: + def _stop_play(self, reset_button: bool = True) -> None: proc = self._play_proc self._play_proc = None self._playhead_timer.stop() @@ -4636,8 +4639,9 @@ class AudioEditorDialog(QDialog): proc.kill() proc.waitForFinished(100) proc.deleteLater() - self._btn_play.setText("▶ Play") - if self._btn_play.isChecked(): + if reset_button: + self._btn_play.setText("▶ Play") + if reset_button and self._btn_play.isChecked(): self._btn_play.blockSignals(True) self._btn_play.setChecked(False) self._btn_play.blockSignals(False) diff --git a/tests/test_ui_structure.py b/tests/test_ui_structure.py index d4ad6a0..9af840c 100644 --- a/tests/test_ui_structure.py +++ b/tests/test_ui_structure.py @@ -1076,6 +1076,51 @@ def test_editor_play_button_loops_selection_when_playhead_inside(win, tmp_path, assert seen == {"start": 1.25, "end": 2.75, "loop": True} +def test_editor_playhead_change_keeps_playback_running(win, tmp_path, monkeypatch): + import main as m + src = tmp_path / "v0.wav"; src.write_bytes(b"") + dlg = m.AudioEditorDialog(str(src), parent=win) + dlg._clip_dur = 5.0 + dlg._wave.set_view(0.0, 5.0) + dlg._wave.set_selection(1.25, 2.75) + dlg._wave.set_playhead(4.0) + dlg._play_proc = object() + dlg._btn_play.setChecked(True) + seen = {} + stopped = {"n": 0} + monkeypatch.setattr( + dlg, "_play_current", + lambda start=None, end=None, loop=False: + seen.update(start=start, end=end, loop=loop)) + monkeypatch.setattr(dlg, "_stop_play", lambda: stopped.__setitem__("n", stopped["n"] + 1)) + dlg._on_wave_playhead_changed(4.0) + assert seen == {"start": 4.0, "end": 5.0, "loop": False} + assert stopped["n"] == 0 + assert dlg._btn_play.isChecked() + + +def test_editor_playhead_change_keeps_looping_when_inside_selection(win, tmp_path, monkeypatch): + import main as m + src = tmp_path / "v0.wav"; src.write_bytes(b"") + dlg = m.AudioEditorDialog(str(src), parent=win) + dlg._clip_dur = 5.0 + dlg._wave.set_view(0.0, 5.0) + dlg._wave.set_selection(1.25, 2.75) + dlg._wave.set_playhead(2.0) + dlg._play_proc = object() + dlg._btn_play.setChecked(True) + seen = {} + stopped = {"n": 0} + monkeypatch.setattr( + dlg, "_play_current", + lambda start=None, end=None, loop=False: + seen.update(start=start, end=end, loop=loop)) + monkeypatch.setattr(dlg, "_stop_play", lambda: stopped.__setitem__("n", stopped["n"] + 1)) + dlg._on_wave_playhead_changed(2.0) + assert seen == {"start": 1.25, "end": 2.75, "loop": True} + assert stopped["n"] == 0 + + def test_editor_has_local_speed_controls(win, tmp_path): import main as m from PyQt6.QtWidgets import QPushButton