main.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. # -*- coding: utf-8 -*-
  2. """
  3. Audio-Ducking Plugin.
  4. Reduziert die Musik-Lautstaerke wenn das Wakeword erkannt wird
  5. und stellt sie nach der Conversation wieder her.
  6. Flow:
  7. Wakeword erkannt → Musik auf 20% (duck)
  8. Conversation laeuft → Musik bleibt leise
  9. Conversation beendet → Musik zurueck auf Original (restore)
  10. """
  11. from __future__ import annotations
  12. from typing import Any
  13. from trixy_core.events.decorators import TrixyEvent
  14. from trixy_core.events.enums import EventPriority
  15. from trixy_core.plugins.trixy_plugin import TrixyPlugin
  16. from trixy_core.utils.debug import pdebug, pinfo
  17. class AudioDuckingPlugin(TrixyPlugin):
  18. """Reduziert Musik-Lautstaerke bei Wakeword/Conversation."""
  19. NAME = "audio_ducking"
  20. VERSION = "1.0.0"
  21. DESCRIPTION = "Musik leiser bei Sprachinteraktion"
  22. AUTHOR = "Trixy"
  23. def __init__(self, application, plugin_path, config=None) -> None:
  24. super().__init__(application, plugin_path, config)
  25. self._original_volume: float | None = None
  26. self._ducked = False
  27. async def on_load(self) -> None:
  28. pinfo(f"AudioDucking: Lade v{self.VERSION}")
  29. async def on_unload(self) -> None:
  30. if self._ducked:
  31. await self._restore_volume()
  32. def _get_player(self):
  33. """Gibt den MusicPlayer zurueck (mehrere Wege probieren)."""
  34. # Weg 1: Ueber ServiceContainer
  35. try:
  36. for name, service in self.application.services.services.items():
  37. if "music" in name.lower() or "Music" in type(service).__name__:
  38. player = getattr(service, "_player", None)
  39. if player:
  40. return player
  41. except Exception:
  42. pass
  43. # Weg 2: Direkt am Application-Objekt
  44. try:
  45. svc = getattr(self.application, "_music_player_service", None)
  46. if svc:
  47. return getattr(svc, "_player", None)
  48. except Exception:
  49. pass
  50. return None
  51. async def _duck_volume(self) -> None:
  52. """Reduziert die Musik-Lautstaerke."""
  53. player = self._get_player()
  54. if not player:
  55. pdebug("AudioDucking: Kein MusicPlayer gefunden")
  56. return
  57. if not player.is_playing:
  58. pdebug("AudioDucking: Keine Musik aktiv")
  59. return
  60. if self._ducked:
  61. return
  62. duck_pct = self.get_config_value("duck_volume_percent", 20)
  63. self._original_volume = player.volume
  64. duck_vol = duck_pct / 100.0
  65. await player.set_volume(duck_vol)
  66. self._ducked = True
  67. pinfo(f"AudioDucking: {self._original_volume:.0%} → {duck_vol:.0%}")
  68. async def _restore_volume(self) -> None:
  69. """Stellt die urspruengliche Lautstaerke wieder her."""
  70. if not self._ducked or self._original_volume is None:
  71. return
  72. player = self._get_player()
  73. if player:
  74. await player.set_volume(self._original_volume)
  75. pinfo(f"AudioDucking: Wiederhergestellt auf {self._original_volume:.0%}")
  76. self._original_volume = None
  77. self._ducked = False
  78. # === Events ===
  79. @TrixyEvent(["wakeword_detected"], priority=EventPriority.HIGHEST)
  80. async def on_wakeword(self, event_name: str, event_data) -> None:
  81. """Wakeword erkannt → Musik leiser."""
  82. pinfo("AudioDucking: Wakeword erkannt")
  83. await self._duck_volume()
  84. @TrixyEvent(["wakeword_manual_trigger"], priority=EventPriority.HIGHEST)
  85. async def on_manual_trigger(self, event_name: str, event_data) -> None:
  86. """Manueller Trigger → Musik leiser."""
  87. pinfo("AudioDucking: Manueller Trigger")
  88. await self._duck_volume()
  89. @TrixyEvent(["recording_complete"])
  90. async def on_recording_complete(self, event_name: str, event_data) -> None:
  91. """Aufnahme beendet → Musik zurueck auf normal."""
  92. pinfo("AudioDucking: Recording complete")
  93. await self._restore_volume()
  94. @TrixyEvent(["conversation_started"])
  95. async def on_conversation_started(self, event_name: str, event_data) -> None:
  96. """Conversation gestartet → sicherstellen dass Musik leise."""
  97. await self._duck_volume()