From cfa0f6531190a9df4e16e257fcb35c393a779f39 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 22 Jan 2020 14:39:18 +1000 Subject: [PATCH] add a hook for when playback begins --- qt/aqt/gui_hooks.py | 44 ++++++++++++++++++++++++++++++++-------- qt/aqt/main.py | 4 ++-- qt/aqt/sound.py | 19 +++++++++++++---- qt/aqt/tts.py | 7 ++++--- qt/tools/genhooks_gui.py | 6 +++++- 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 019956c92..ed1648373 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -75,28 +75,56 @@ class _AddCardsWillShowHistoryMenuHook: add_cards_will_show_history_menu = _AddCardsWillShowHistoryMenuHook() -class _AvPlayerDidPlayHook: - _hooks: List[Callable[[], None]] = [] +class _AvPlayerDidBeginPlayingHook: + _hooks: List[Callable[["aqt.sound.Player", "anki.sound.AVTag"], None]] = [] - def append(self, cb: Callable[[], None]) -> None: - """()""" + def append( + self, cb: Callable[["aqt.sound.Player", "anki.sound.AVTag"], None] + ) -> None: + """(player: aqt.sound.Player, tag: anki.sound.AVTag)""" self._hooks.append(cb) - def remove(self, cb: Callable[[], None]) -> None: + def remove( + self, cb: Callable[["aqt.sound.Player", "anki.sound.AVTag"], None] + ) -> None: if cb in self._hooks: self._hooks.remove(cb) - def __call__(self) -> None: + def __call__(self, player: aqt.sound.Player, tag: anki.sound.AVTag) -> None: for hook in self._hooks: try: - hook() + hook(player, tag) except: # if the hook fails, remove it self._hooks.remove(hook) raise -av_player_did_play = _AvPlayerDidPlayHook() +av_player_did_begin_playing = _AvPlayerDidBeginPlayingHook() + + +class _AvPlayerDidEndPlayingHook: + _hooks: List[Callable[["aqt.sound.Player"], None]] = [] + + def append(self, cb: Callable[["aqt.sound.Player"], None]) -> None: + """(player: aqt.sound.Player)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.sound.Player"], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__(self, player: aqt.sound.Player) -> None: + for hook in self._hooks: + try: + hook(player) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +av_player_did_end_playing = _AvPlayerDidEndPlayingHook() class _AvPlayerWillPlayHook: diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 8ed44aae6..a4e2824d2 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1180,7 +1180,7 @@ Difference to correct time: %s.""" hooks.card_odue_was_invalid.append(self.onOdueInvalid) gui_hooks.av_player_will_play.append(self.on_av_player_will_play) - gui_hooks.av_player_did_play.append(self.on_av_player_did_play) + gui_hooks.av_player_did_end_playing.append(self.on_av_player_did_end_playing) self._activeWindowOnPlay: Optional[QWidget] = None @@ -1207,7 +1207,7 @@ and if the problem comes up again, please ask on the support site.""" self._activeWindowOnPlay = self.app.activeWindow() or self._activeWindowOnPlay - def on_av_player_did_play(self) -> None: + def on_av_player_did_end_playing(self, player: Any) -> None: "Restore window focus after a video was played." w = self._activeWindowOnPlay if not self.app.activeWindow() and w and not sip.isdeleted(w) and w.isVisible(): diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index 236dd452c..c90e8862f 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -36,6 +36,12 @@ OnDoneCallback = Callable[[], None] class Player(ABC): @abstractmethod def play(self, tag: AVTag, on_done: OnDoneCallback) -> None: + """Play a file. + + When reimplementing, make sure to call + gui_hooks.av_player_did_begin_playing(self, tag) + on the main thread after playback begins. + """ pass @abstractmethod @@ -142,8 +148,8 @@ class AVPlayer: return self._enqueued.pop(0) def _on_play_finished(self) -> None: + gui_hooks.av_player_did_end_playing(self.current_player) self.current_player = None - gui_hooks.av_player_did_play() self._play_next_if_idle() def _play_next_if_idle(self) -> None: @@ -238,9 +244,13 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method def _play(self, tag: AVTag) -> None: assert isinstance(tag, SoundOrVideoTag) self._process = subprocess.Popen(self.args + [tag.filename], env=self.env) - self._wait_for_termination() + self._wait_for_termination(tag) + + def _wait_for_termination(self, tag: AVTag): + self._taskman.run_on_main( + lambda: gui_hooks.av_player_did_begin_playing(self, tag) + ) - def _wait_for_termination(self): try: while True: try: @@ -344,6 +354,7 @@ class MpvManager(MPV, SoundOrVideoPlayer): self._on_done = on_done path = os.path.join(os.getcwd(), tag.filename) self.command("loadfile", path, "append-play") + gui_hooks.av_player_did_begin_playing(self, tag) def stop(self) -> None: self.command("stop") @@ -388,7 +399,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer): self._process = subprocess.Popen( self.args + [tag.filename], env=self.env, stdin=subprocess.PIPE ) - self._wait_for_termination() + self._wait_for_termination(tag) def command(self, *args) -> None: """Send a command over the slave interface. diff --git a/qt/aqt/tts.py b/qt/aqt/tts.py index 89fd21895..36065f71c 100644 --- a/qt/aqt/tts.py +++ b/qt/aqt/tts.py @@ -34,6 +34,7 @@ from typing import Any, List, Optional, cast from anki.sound import AVTag, TTSTag from anki.utils import checksum, isWin, tmpdir +from aqt import gui_hooks from aqt.sound import OnDoneCallback, PlayerInterrupted, SimpleProcessPlayer @@ -128,8 +129,7 @@ class MacTTSPlayer(TTSProcessPlayer): # write the input text to stdin self._process.stdin.write(tag.field_text.encode("utf8")) self._process.stdin.close() - - self._wait_for_termination() + self._wait_for_termination(tag) def get_available_voices(self) -> List[TTSVoice]: cmd = subprocess.run( @@ -170,7 +170,7 @@ class MacTTSFilePlayer(MacTTSPlayer): # write the input text to stdin self._process.stdin.write(tag.field_text.encode("utf8")) self._process.stdin.close() - self._wait_for_termination() + self._wait_for_termination(tag) def _on_done(self, ret: Future, cb: OnDoneCallback) -> None: try: @@ -433,6 +433,7 @@ if isWin: native_voice = voice.handle self.speaker.Voice = native_voice self.speaker.Speak(tag.field_text, 1) + gui_hooks.av_player_did_begin_playing(self, tag) # wait 100ms while not self.speaker.WaitUntilDone(100): diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index d1b73c499..679b71efd 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -199,7 +199,11 @@ hooks = [ # Sound/video ################### Hook(name="av_player_will_play", args=["tag: anki.sound.AVTag"]), - Hook(name="av_player_did_play"), + Hook( + name="av_player_did_begin_playing", + args=["player: aqt.sound.Player", "tag: anki.sound.AVTag"], + ), + Hook(name="av_player_did_end_playing", args=["player: aqt.sound.Player"]), # Other ################### Hook(