main.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # -*- coding: utf-8 -*-
  2. """
  3. Crossfade-Plugin für Trixy.
  4. Ermöglicht nahtlose Übergänge zwischen Musik-Tracks durch
  5. Mischen des Track-Endes mit dem Anfang des nächsten Tracks.
  6. """
  7. from trixy_core.plugins import TrixyPlugin
  8. from trixy_core.utils.debug import pinfo, pdebug, perror, pwarn
  9. from plugins.crossfade.processor import CrossfadeProcessor, CrossfadeConfig
  10. class CrossfadePlugin(TrixyPlugin):
  11. """
  12. Crossfade-Plugin.
  13. Fügt der Audio-Pipeline einen Crossfade-Prozessor hinzu,
  14. der automatisch Übergänge zwischen Tracks mischt.
  15. """
  16. NAME = "crossfade"
  17. VERSION = "1.0.0"
  18. DESCRIPTION = "Nahtlose Übergänge zwischen Musik-Tracks"
  19. AUTHOR = "Trixy"
  20. def __init__(self, application, plugin_path, config=None):
  21. super().__init__(application, plugin_path, config)
  22. self._processor: CrossfadeProcessor | None = None
  23. async def on_load(self) -> None:
  24. """Wird beim Laden des Plugins aufgerufen."""
  25. pinfo(f"CrossfadePlugin: Lade Plugin v{self.VERSION}")
  26. # Konfiguration laden
  27. crossfade_config = CrossfadeConfig(
  28. duration_ms=self.get_config_value("crossfade_duration_ms", 5000),
  29. fade_curve=self.get_config_value("fade_curve", "linear"),
  30. min_track_duration_ms=self.get_config_value("min_track_duration_ms", 30000),
  31. prebuffer_duration_ms=self.get_config_value("prebuffer_duration_ms", 6000),
  32. enabled=self.get_config_value("enabled", True),
  33. )
  34. # Prozessor erstellen
  35. self._processor = CrossfadeProcessor(
  36. processor_id=f"{self.NAME}_processor",
  37. name="Crossfade",
  38. config=crossfade_config,
  39. )
  40. # Decoder-Registry setzen wenn verfügbar
  41. if self.application:
  42. try:
  43. # Versuche MusicPlayer zu finden
  44. from trixy_core.music.formats import get_default_registry
  45. registry = get_default_registry()
  46. self._processor.set_decoder_registry(registry)
  47. except Exception:
  48. pass
  49. pinfo(f"CrossfadePlugin: Geladen (Dauer: {crossfade_config.duration_ms}ms)")
  50. async def on_unload(self) -> None:
  51. """Wird beim Entladen des Plugins aufgerufen."""
  52. if self._processor:
  53. self._processor.reset()
  54. self._processor = None
  55. pdebug("CrossfadePlugin: Entladen")
  56. async def on_enable(self) -> None:
  57. """Wird beim Aktivieren aufgerufen."""
  58. pinfo("CrossfadePlugin: on_enable() aufgerufen")
  59. if self._processor:
  60. self._processor.enabled = True
  61. self._processor.config.enabled = True
  62. # Prozessor zur Pipeline hinzufügen
  63. self._register_processor()
  64. else:
  65. pwarn("CrossfadePlugin: Kein Processor vorhanden!")
  66. pinfo("CrossfadePlugin: Aktiviert")
  67. async def on_disable(self) -> None:
  68. """Wird beim Deaktivieren aufgerufen."""
  69. if self._processor:
  70. self._processor.enabled = False
  71. self._processor.config.enabled = False
  72. # Prozessor aus Pipeline entfernen
  73. self._unregister_processor()
  74. pinfo("CrossfadePlugin: Deaktiviert")
  75. async def on_config_change(self, old_config: dict) -> None:
  76. """Wird bei Konfigurationsänderungen aufgerufen."""
  77. if self._processor:
  78. self._processor.update_config(
  79. duration_ms=self.get_config_value("crossfade_duration_ms", 5000),
  80. fade_curve=self.get_config_value("fade_curve", "linear"),
  81. min_track_duration_ms=self.get_config_value("min_track_duration_ms", 30000),
  82. prebuffer_duration_ms=self.get_config_value("prebuffer_duration_ms", 6000),
  83. enabled=self.get_config_value("enabled", True),
  84. )
  85. pdebug("CrossfadePlugin: Konfiguration aktualisiert")
  86. def _register_processor(self) -> None:
  87. """Registriert Prozessor bei der Audio-Pipeline."""
  88. if not self._processor:
  89. return
  90. try:
  91. # MusicPlayer finden und Pipeline abrufen
  92. if self.application:
  93. # Server-Modus
  94. if hasattr(self.application, "services"):
  95. # Debug: Alle registrierten Services anzeigen
  96. all_services = list(self.application.services.services.keys())
  97. pdebug(f"CrossfadePlugin: Verfügbare Services: {all_services}")
  98. music_service = self.application.services.get_service("music_player")
  99. pdebug(f"CrossfadePlugin: music_player Service = {music_service}")
  100. if music_service and hasattr(music_service, "player"):
  101. player = music_service.player
  102. pdebug(f"CrossfadePlugin: Player = {player}")
  103. if player:
  104. pipeline = player.processing_pipeline
  105. pipeline.add(self._processor)
  106. pinfo("CrossfadePlugin: Prozessor zur Pipeline hinzugefügt")
  107. return
  108. else:
  109. pdebug("CrossfadePlugin: Player ist None")
  110. else:
  111. pdebug(f"CrossfadePlugin: Service hat kein 'player' Attribut")
  112. pdebug("CrossfadePlugin: Keine Pipeline gefunden, Prozessor nicht registriert")
  113. except Exception as e:
  114. perror(f"CrossfadePlugin: Fehler beim Registrieren: {e}")
  115. import traceback
  116. pdebug(traceback.format_exc())
  117. def _unregister_processor(self) -> None:
  118. """Entfernt Prozessor aus der Audio-Pipeline."""
  119. if not self._processor:
  120. return
  121. try:
  122. if self.application:
  123. if hasattr(self.application, "services"):
  124. music_service = self.application.services.get_service("music_player")
  125. if music_service and hasattr(music_service, "player"):
  126. pipeline = music_service.player.processing_pipeline
  127. pipeline.remove(self._processor.id)
  128. pdebug("CrossfadePlugin: Prozessor aus Pipeline entfernt")
  129. except Exception as e:
  130. perror(f"CrossfadePlugin: Fehler beim Entfernen: {e}")
  131. @property
  132. def processor(self) -> CrossfadeProcessor | None:
  133. """Zugriff auf den Crossfade-Prozessor."""
  134. return self._processor
  135. def get_stats(self) -> dict:
  136. """Liefert Statistiken."""
  137. if self._processor:
  138. return self._processor.get_config()
  139. return {}