Skip to content

Commit

Permalink
Fix no speech after removed USB audio device (PR #11978)
Browse files Browse the repository at this point in the history
  • Loading branch information
feerrenrut authored Jan 4, 2021
1 parent ef0c86c commit 80bd360
Showing 1 changed file with 42 additions and 28 deletions.
70 changes: 42 additions & 28 deletions source/nvwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

import threading
import typing
from typing import (
Optional,
Callable,
)
from ctypes import (
windll,
POINTER,
Expand Down Expand Up @@ -123,18 +127,6 @@ def _isDebugForNvWave():
return config.conf["debugLog"]["nvwave"]


def safe_winmm_waveOutReset(_waveout) -> bool:
""" Wrap waveOutReset in try block and log exceptions,
it seems to fail randomly on some systems.
@return True on success.
"""
try:
winmm.waveOutReset(_waveout)
return True
except WindowsError:
log.debug("Exception while resetting wave out device.", exc_info=True)
return False

class WavePlayer(garbageHandler.TrackedObject):
"""Synchronously play a stream of audio.
To use, construct an instance and feed it waveform audio using L{feed}.
Expand Down Expand Up @@ -311,13 +303,9 @@ def open(self):
CALLBACK_EVENT
)
except WindowsError:
if _isDebugForNvWave():
log.debug(
f"Error opening"
f" outputDeviceName: {self._outputDeviceName}"
f" with id: {self._outputDeviceID}"
)
if self._outputDeviceID != WAVE_MAPPER:
lastOutputDeviceID = self._outputDeviceID
self._handleWinmmError(message="Error opening")
if lastOutputDeviceID != WAVE_MAPPER:
if _isDebugForNvWave():
log.debug(f"Falling back to WAVE_MAPPER")
self._setCurrentDevice(WAVE_MAPPER)
Expand All @@ -326,7 +314,7 @@ def open(self):
log.warning(f"Unable to open WAVE_MAPPER device, there may be no audio devices.")
raise # can't open the default device.
return
self._waveout = waveout.value
self._waveout: typing.Optional[int] = waveout.value
self._prev_whdr = None

def feed(
Expand Down Expand Up @@ -444,12 +432,11 @@ def pause(self, switch):
with self._waveout_lock:
if not self._waveout:
return
if switch:
with self._global_waveout_lock:
winmm.waveOutPause(self._waveout)
else:
with self._global_waveout_lock:
winmm.waveOutRestart(self._waveout)
with self._global_waveout_lock:
if switch:
self._safe_winmm_call(winmm.waveOutPause, "Pause")
else:
self._safe_winmm_call(winmm.waveOutRestart, "Restart")

def idle(self):
"""Indicate that this player is now idle; i.e. the current continuous segment of audio is complete.
Expand Down Expand Up @@ -498,8 +485,10 @@ def stop(self):
self._prevOnDone = self.STOPPING
with self._global_waveout_lock:
# Pausing first seems to make waveOutReset respond faster on some systems.
winmm.waveOutPause(self._waveout)
safe_winmm_waveOutReset(self._waveout)
self._safe_winmm_call(winmm.waveOutPause, "Pause")
self._safe_winmm_call(winmm.waveOutReset, "Reset")
# Allow fall through to idleUnbuffered if either pause or reset fail.

# The documentation is not explicit about whether waveOutReset will signal the event,
# so trigger it to be sure that sync isn't blocking on 'waitForSingleObject'.
windll.kernel32.SetEvent(self._waveout_event)
Expand All @@ -524,6 +513,7 @@ def _close(self):
if not self._waveout:
return
try:
# don't use '_safe_winmm_call' here, on error it would re-enter _close infinitely
winmm.waveOutClose(self._waveout)
except WindowsError:
log.debug("Error closing the device, it may have been removed.", exc_info=True)
Expand All @@ -535,6 +525,30 @@ def __del__(self):
self._waveout_event = None
super().__del__()

def _handleWinmmError(self, message: str):
if _isDebugForNvWave():
log.debug(
f"Winmm Error: {message}"
f" outputDeviceName: {self._outputDeviceName}"
f" with id: {self._outputDeviceID}",
stack_info=True
)
self._close()

def _safe_winmm_call(
self,
winmmCall: Callable[[Optional[int]], None],
messageOnFailure: str
) -> bool:
try:
winmmCall(self._waveout)
return True
except WindowsError:
# device will be closed and _waveout set to None,
# triggering re-open.
self._handleWinmmError(message=messageOnFailure)
return False


def _getOutputDevices():
"""Generator, returning device ID and device Name in device ID order.
Expand Down

0 comments on commit 80bd360

Please sign in to comment.