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() add_cards_will_show_history_menu = _AddCardsWillShowHistoryMenuHook()
class _AvPlayerDidPlayHook: class _AvPlayerDidBeginPlayingHook:
_hooks: List[Callable[[], None]] = [] _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) 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: if cb in self._hooks:
self._hooks.remove(cb) 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: for hook in self._hooks:
try: try:
hook() hook(player, tag)
except: except:
# if the hook fails, remove it # if the hook fails, remove it
self._hooks.remove(hook) self._hooks.remove(hook)
raise 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: class _AvPlayerWillPlayHook:

View file

@ -1180,7 +1180,7 @@ Difference to correct time: %s."""
hooks.card_odue_was_invalid.append(self.onOdueInvalid) 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_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 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 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." "Restore window focus after a video was played."
w = self._activeWindowOnPlay w = self._activeWindowOnPlay
if not self.app.activeWindow() and w and not sip.isdeleted(w) and w.isVisible(): 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): class Player(ABC):
@abstractmethod @abstractmethod
def play(self, tag: AVTag, on_done: OnDoneCallback) -> None: 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 pass
@abstractmethod @abstractmethod
@ -142,8 +148,8 @@ class AVPlayer:
return self._enqueued.pop(0) return self._enqueued.pop(0)
def _on_play_finished(self) -> None: def _on_play_finished(self) -> None:
gui_hooks.av_player_did_end_playing(self.current_player)
self.current_player = None self.current_player = None
gui_hooks.av_player_did_play()
self._play_next_if_idle() self._play_next_if_idle()
def _play_next_if_idle(self) -> None: def _play_next_if_idle(self) -> None:
@ -238,9 +244,13 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
def _play(self, tag: AVTag) -> None: def _play(self, tag: AVTag) -> None:
assert isinstance(tag, SoundOrVideoTag) assert isinstance(tag, SoundOrVideoTag)
self._process = subprocess.Popen(self.args + [tag.filename], env=self.env) 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: try:
while True: while True:
try: try:
@ -344,6 +354,7 @@ class MpvManager(MPV, SoundOrVideoPlayer):
self._on_done = on_done self._on_done = on_done
path = os.path.join(os.getcwd(), tag.filename) path = os.path.join(os.getcwd(), tag.filename)
self.command("loadfile", path, "append-play") self.command("loadfile", path, "append-play")
gui_hooks.av_player_did_begin_playing(self, tag)
def stop(self) -> None: def stop(self) -> None:
self.command("stop") self.command("stop")
@ -388,7 +399,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer):
self._process = subprocess.Popen( self._process = subprocess.Popen(
self.args + [tag.filename], env=self.env, stdin=subprocess.PIPE self.args + [tag.filename], env=self.env, stdin=subprocess.PIPE
) )
self._wait_for_termination() self._wait_for_termination(tag)
def command(self, *args) -> None: def command(self, *args) -> None:
"""Send a command over the slave interface. """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.sound import AVTag, TTSTag
from anki.utils import checksum, isWin, tmpdir from anki.utils import checksum, isWin, tmpdir
from aqt import gui_hooks
from aqt.sound import OnDoneCallback, PlayerInterrupted, SimpleProcessPlayer from aqt.sound import OnDoneCallback, PlayerInterrupted, SimpleProcessPlayer
@ -128,8 +129,7 @@ class MacTTSPlayer(TTSProcessPlayer):
# write the input text to stdin # write the input text to stdin
self._process.stdin.write(tag.field_text.encode("utf8")) self._process.stdin.write(tag.field_text.encode("utf8"))
self._process.stdin.close() self._process.stdin.close()
self._wait_for_termination(tag)
self._wait_for_termination()
def get_available_voices(self) -> List[TTSVoice]: def get_available_voices(self) -> List[TTSVoice]:
cmd = subprocess.run( cmd = subprocess.run(
@ -170,7 +170,7 @@ class MacTTSFilePlayer(MacTTSPlayer):
# write the input text to stdin # write the input text to stdin
self._process.stdin.write(tag.field_text.encode("utf8")) self._process.stdin.write(tag.field_text.encode("utf8"))
self._process.stdin.close() self._process.stdin.close()
self._wait_for_termination() self._wait_for_termination(tag)
def _on_done(self, ret: Future, cb: OnDoneCallback) -> None: def _on_done(self, ret: Future, cb: OnDoneCallback) -> None:
try: try:
@ -433,6 +433,7 @@ if isWin:
native_voice = voice.handle native_voice = voice.handle
self.speaker.Voice = native_voice self.speaker.Voice = native_voice
self.speaker.Speak(tag.field_text, 1) self.speaker.Speak(tag.field_text, 1)
gui_hooks.av_player_did_begin_playing(self, tag)
# wait 100ms # wait 100ms
while not self.speaker.WaitUntilDone(100): while not self.speaker.WaitUntilDone(100):

View file

@ -199,7 +199,11 @@ hooks = [
# Sound/video # Sound/video
################### ###################
Hook(name="av_player_will_play", args=["tag: anki.sound.AVTag"]), 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 # Other
################### ###################
Hook( Hook(