add a hook for when playback begins

This commit is contained in:
Damien Elmes 2020-01-22 14:39:18 +10:00
parent 6af7933084
commit cfa0f65311
5 changed files with 62 additions and 18 deletions

View file

@ -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:

View file

@ -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():

View file

@ -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.

View file

@ -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):

View file

@ -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(