| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- # -*- coding: utf-8 -*-
- """
- Audio-Ducking Plugin.
- Reduziert die Musik-Lautstaerke wenn das Wakeword erkannt wird
- und stellt sie nach der Conversation wieder her.
- Flow:
- Wakeword erkannt → Musik auf 20% (duck)
- Conversation laeuft → Musik bleibt leise
- Conversation beendet → Musik zurueck auf Original (restore)
- """
- from __future__ import annotations
- from typing import Any
- from trixy_core.events.decorators import TrixyEvent
- from trixy_core.events.enums import EventPriority
- from trixy_core.plugins.trixy_plugin import TrixyPlugin
- from trixy_core.utils.debug import pdebug, pinfo
- class AudioDuckingPlugin(TrixyPlugin):
- """Reduziert Musik-Lautstaerke bei Wakeword/Conversation."""
- NAME = "audio_ducking"
- VERSION = "1.0.0"
- DESCRIPTION = "Musik leiser bei Sprachinteraktion"
- AUTHOR = "Trixy"
- def __init__(self, application, plugin_path, config=None) -> None:
- super().__init__(application, plugin_path, config)
- self._original_volume: float | None = None
- self._ducked = False
- async def on_load(self) -> None:
- pinfo(f"AudioDucking: Lade v{self.VERSION}")
- async def on_unload(self) -> None:
- if self._ducked:
- await self._restore_volume()
- def _get_player(self):
- """Gibt den MusicPlayer zurueck (mehrere Wege probieren)."""
- # Weg 1: Ueber ServiceContainer
- try:
- for name, service in self.application.services.services.items():
- if "music" in name.lower() or "Music" in type(service).__name__:
- player = getattr(service, "_player", None)
- if player:
- return player
- except Exception:
- pass
- # Weg 2: Direkt am Application-Objekt
- try:
- svc = getattr(self.application, "_music_player_service", None)
- if svc:
- return getattr(svc, "_player", None)
- except Exception:
- pass
- return None
- async def _duck_volume(self) -> None:
- """Reduziert die Musik-Lautstaerke."""
- player = self._get_player()
- if not player:
- pdebug("AudioDucking: Kein MusicPlayer gefunden")
- return
- if not player.is_playing:
- pdebug("AudioDucking: Keine Musik aktiv")
- return
- if self._ducked:
- return
- duck_pct = self.get_config_value("duck_volume_percent", 20)
- self._original_volume = player.volume
- duck_vol = duck_pct / 100.0
- await player.set_volume(duck_vol)
- self._ducked = True
- pinfo(f"AudioDucking: {self._original_volume:.0%} → {duck_vol:.0%}")
- async def _restore_volume(self) -> None:
- """Stellt die urspruengliche Lautstaerke wieder her."""
- if not self._ducked or self._original_volume is None:
- return
- player = self._get_player()
- if player:
- await player.set_volume(self._original_volume)
- pinfo(f"AudioDucking: Wiederhergestellt auf {self._original_volume:.0%}")
- self._original_volume = None
- self._ducked = False
- # === Events ===
- @TrixyEvent(["wakeword_detected"], priority=EventPriority.HIGHEST)
- async def on_wakeword(self, event_name: str, event_data) -> None:
- """Wakeword erkannt → Musik leiser."""
- pinfo("AudioDucking: Wakeword erkannt")
- await self._duck_volume()
- @TrixyEvent(["wakeword_manual_trigger"], priority=EventPriority.HIGHEST)
- async def on_manual_trigger(self, event_name: str, event_data) -> None:
- """Manueller Trigger → Musik leiser."""
- pinfo("AudioDucking: Manueller Trigger")
- await self._duck_volume()
- @TrixyEvent(["recording_complete"])
- async def on_recording_complete(self, event_name: str, event_data) -> None:
- """Aufnahme beendet → Musik zurueck auf normal."""
- pinfo("AudioDucking: Recording complete")
- await self._restore_volume()
- @TrixyEvent(["conversation_started"])
- async def on_conversation_started(self, event_name: str, event_data) -> None:
- """Conversation gestartet → sicherstellen dass Musik leise."""
- await self._duck_volume()
|