fix audio getting stuck (1/2)

The problem was caused by stop() doing a spin loop on the main
thread waiting for the completion signal. This prevented Qt's run
loop from executing, and so the completion signal was never delivered,
meaning longer files would time out.

Fixed by reworking the code so that stop() does not block at all -
instead it just sets the termination flag, and AVPlayer does not
unset current_player. Then when the completion callback fires, it
can advance to the next file.

TTS code still needs updating, and the lock should be safe to remove
as the start/stop logic is all on the main thread.
This commit is contained in:
Damien Elmes 2020-03-15 09:26:31 +10:00
parent e7452300a2
commit f30853f5ed

View file

@ -129,7 +129,6 @@ class AVPlayer:
def _stop_if_playing(self) -> None: def _stop_if_playing(self) -> None:
if self.current_player: if self.current_player:
self.current_player.stop() self.current_player.stop()
self.current_player = None
def _pop_next(self) -> Optional[AVTag]: def _pop_next(self) -> Optional[AVTag]:
if not self._enqueued: if not self._enqueued:
@ -235,17 +234,15 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
self._lock = threading.Lock() self._lock = threading.Lock()
def play(self, tag: AVTag, on_done: OnDoneCallback) -> None: def play(self, tag: AVTag, on_done: OnDoneCallback) -> None:
self._terminate_flag = False
self._taskman.run_in_background( self._taskman.run_in_background(
lambda: self._play(tag), lambda res: self._on_done(res, on_done) lambda: self._play(tag), lambda res: self._on_done(res, on_done)
) )
def stop(self) -> None: def stop(self) -> None:
self._terminate_flag = True self._terminate_flag = True
# block until stopped
t = time.time()
while self._terminate_flag and time.time() - t < 3:
time.sleep(0.1)
# note: mplayer implementation overrides this
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._process = subprocess.Popen(
@ -264,19 +261,11 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
while True: while True:
with self._lock: with self._lock:
# if .stop() timed out, another thread may run when
# there is no process
if not self._process:
self._process = None
self._terminate_flag = False
return
# should we abort playing? # should we abort playing?
if self._terminate_flag: if self._terminate_flag:
self._process.terminate() self._process.terminate()
self._process = None self._process = None
self._terminate_flag = False return
raise PlayerInterrupted()
# wait for completion # wait for completion
try: try:
@ -284,17 +273,14 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
if self._process.returncode != 0: if self._process.returncode != 0:
print(f"player got return code: {self._process.returncode}") print(f"player got return code: {self._process.returncode}")
self._process = None self._process = None
self._terminate_flag = False
return return
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
# process still running, repeat loop
pass pass
def _on_done(self, ret: Future, cb: OnDoneCallback) -> None: def _on_done(self, ret: Future, cb: OnDoneCallback) -> None:
try: try:
ret.result() ret.result()
except PlayerInterrupted:
# don't fire done callback when interrupted
return
except FileNotFoundError: except FileNotFoundError:
showWarning( showWarning(
_( _(