8bbc97a204
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.9 KiB
4.9 KiB
Mikrofon-Monitor – Design-Spec
Datum: 2026-05-14
Status: Genehmigt
Ziel
Die Anwendung soll erkennen, wenn Mikrofone im System hinzugefügt oder entfernt werden (USB, Bluetooth). Wenn das konfigurierte Mikrofon nicht mehr vorhanden ist, fällt die App automatisch auf das Standard-Mikrofon zurück und benachrichtigt den Nutzer.
Verhalten
- Konfiguriertes Mikrofon verschwindet: Automatischer Fallback auf Standard-Mikrofon (
device = None), Toast-Benachrichtigung + Tray-Tooltip-Warnung. - Konfiguriertes Mikrofon kehrt zurück: Stellt das konfigurierte Gerät wieder her, Toast „Mikrofon wieder verbunden", Tray-Tooltip-Warnung entfernen.
- Konfiguriertes Mikrofon fehlt bereits beim Start:
on_configured_missingsofort auslösen, nicht erst nach dem ersten Poll-Intervall. - Kein konfiguriertes Mikrofon (
device = None): Monitor läuft trotzdem, meldet nuron_device_added/on_device_removed(für zukünftige Erweiterungen), keine Fehlerbehandlung nötig.
Neue Dateien
whisper_local/
microphone/
__init__.py # Protocol + create_monitor() Factory
_poll.py # Polling-Implementierung (Linux + Fallback)
_win32.py # IMMNotificationClient-Implementierung (Windows)
tray/
_notification.py # notify-py Toast-Wrapper
Protocol
class MicrophoneMonitor(Protocol):
on_device_added: Callable[[str], Awaitable[None]] | None
on_device_removed: Callable[[str], Awaitable[None]] | None
on_configured_missing: Callable[[], Awaitable[None]] | None
async def start(self) -> None: ...
def stop(self) -> None: ...
Factory
def create_monitor(configured_device: str | None) -> MicrophoneMonitor:
...
- Windows: versucht
Win32Monitor; fällt bei COM-Fehler aufPollMonitorzurück. - Alle anderen Plattformen:
PollMonitor.
Erkennungsmechanismus
Windows: Win32Monitor (_win32.py)
- Registriert
IMMNotificationClientbeiIMMDeviceEnumeratorviacomtypes. - Empfängt
OnDeviceAdded,OnDeviceRemoved,OnDeviceStateChangedals COM-Callbacks. - Übergibt Events per
loop.call_soon_threadsafein den asyncio-Loop (identisches Muster wie pynput-Hotkey-Callbacks). - Wenn COM-Initialisierung fehlschlägt: transparenter Fallback auf
PollMonitor.
Cross-Platform: PollMonitor (_poll.py)
- Asyncio-Task mit
asyncio.sleep(2.5)zwischen Prüfungen. - Vergleicht aktuelle
sd.query_devices()-Liste mit letztem Snapshot (Set aus Gerätenamen). - Erkennt hinzugekommene und verschwundene Geräte, löst entsprechende Callbacks aus.
- Exceptions in
sd.query_devices()werden geloggt und übersprungen — Loop läuft weiter.
Benachrichtigung (_notification.py)
- Nutzt
notify-py(cross-platform, aktiv gepflegt). - Funktion
notify(title: str, message: str) -> None— bei Fehler wird geloggt, nie blockierend. - Neue Abhängigkeit:
notify-pyinpyproject.tomleintragen.
Tray-Integration
PystrayApp erhält neue Methode:
def set_warning(self, msg: str | None) -> None: ...
msg = None: Icon-Titel zurück auf"whisper-local".msg = "...": Icon-Titel auf"whisper-local ⚠ <msg>".
App-Integration (__main__.py)
App.__init__: erstelltself.monitor = create_monitor(config.microphone).- Callbacks registrieren:
on_configured_missing→recorder.device = None+notify(...)+tray.set_warning(...)on_device_added→ prüft ob konfiguriertes Gerät zurückgekehrt →recorder.device = config.microphone+notify(...)+tray.set_warning(None)
App.run(): startetmonitor.start()als asyncio-Task parallel zum Hotkey-Listener._on_config_reload: stoppt alten Monitor, erstellt neuen mit aktualisierten Gerätedaten.
Fehlerbehandlung
| Situation | Verhalten |
|---|---|
| COM-Init schlägt fehl (Windows) | Transparenter Fallback auf PollMonitor |
sd.query_devices() wirft Exception |
Loggen + überspringen, Loop läuft weiter |
notify-py wirft Exception |
Loggen + ignorieren, nie blockierend |
| Konfiguriertes Mikrofon fehlt beim Start | Sofort on_configured_missing auslösen |
Tests
tests/test_microphone_monitor.py:PollMonitormit gemocktemsd.query_devices().- Prüft:
on_device_addedwird ausgelöst wenn Gerät erscheint. - Prüft:
on_device_removedwird ausgelöst wenn Gerät verschwindet. - Prüft:
on_configured_missingwird sofort beim Start ausgelöst wenn Gerät fehlt.
Win32Monitor-Tests nur auf Windows (@pytest.mark.skipif(sys.platform != "win32", ...)), COM-Interface gemockt._notification.py: kein eigener Test (trivialer Wrapper).
Abhängigkeiten
| Paket | Zweck | Plattform |
|---|---|---|
notify-py |
Desktop-Toast-Benachrichtigungen | alle |
comtypes |
COM-Interface für IMMNotificationClient | Windows (bereits transitiv vorhanden via pywinrt) |