fix: ignore bot audio in sound callback, self-mute support
- _on_sound_received skips audio from our own bots entirely, preventing self-ducking and STT transcribing bot TTS/music - New self_mute config: mute on connect, unmute before stream_audio, re-mute 3s after audio finishes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,8 @@ class MumbleBot:
|
||||
self._connect_count: int = 0
|
||||
self._sound_listeners: list = []
|
||||
self._receive_sound: bool = mu_cfg.get("receive_sound", True)
|
||||
self._self_mute: bool = mu_cfg.get("self_mute", False)
|
||||
self._mute_task: asyncio.Task | None = None
|
||||
self._only_plugins: set[str] | None = (
|
||||
set(mu_cfg["only_plugins"]) if "only_plugins" in mu_cfg else None
|
||||
)
|
||||
@@ -225,6 +227,11 @@ class MumbleBot:
|
||||
session = getattr(self._mumble.users, "myself_session", "?")
|
||||
log.info("mumble: %s as %s on %s:%d (session=%s)",
|
||||
kind, self._username, self._host, self._port, session)
|
||||
if self._self_mute:
|
||||
try:
|
||||
self._mumble.users.myself.mute()
|
||||
except Exception:
|
||||
log.exception("mumble: failed to self-mute on connect")
|
||||
if self._loop:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self._notify_plugins_connected(), self._loop,
|
||||
@@ -277,16 +284,18 @@ class MumbleBot:
|
||||
def _on_sound_received(self, user, sound_chunk) -> None:
|
||||
"""Callback from pymumble thread: voice audio received.
|
||||
|
||||
Updates the timestamp used by the music plugin's duck monitor.
|
||||
When this callback is registered, pymumble passes decoded PCM
|
||||
directly and does not queue it -- no memory buildup.
|
||||
Ignores audio from our own bots entirely -- prevents self-ducking
|
||||
and avoids STT transcribing bot TTS/music.
|
||||
"""
|
||||
name = user["name"] if isinstance(user, dict) else None
|
||||
bots = getattr(self.registry, "_bots", {})
|
||||
if name and name in bots:
|
||||
return
|
||||
prev = self._last_voice_ts
|
||||
self._last_voice_ts = time.monotonic()
|
||||
self.registry._voice_ts = self._last_voice_ts
|
||||
if prev == 0.0:
|
||||
name = user["name"] if isinstance(user, dict) else "?"
|
||||
log.info("mumble: first voice packet from %s", name)
|
||||
log.info("mumble: first voice packet from %s", name or "?")
|
||||
for fn in self._sound_listeners:
|
||||
try:
|
||||
fn(user, sound_chunk)
|
||||
@@ -604,6 +613,16 @@ class MumbleBot:
|
||||
if self._mumble is None:
|
||||
return
|
||||
|
||||
# Unmute before streaming if self_mute is enabled
|
||||
if self._self_mute:
|
||||
if self._mute_task and not self._mute_task.done():
|
||||
self._mute_task.cancel()
|
||||
self._mute_task = None
|
||||
try:
|
||||
self._mumble.users.myself.unmute()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_get_vol = volume if callable(volume) else lambda: volume
|
||||
log.info("stream_audio: starting pipeline for %s (vol=%.0f%%, seek=%.1fs)",
|
||||
url, _get_vol() * 100, seek)
|
||||
@@ -822,6 +841,19 @@ class MumbleBot:
|
||||
stderr_out.decode(errors="replace")[:500])
|
||||
if on_done is not None:
|
||||
on_done.set()
|
||||
# Re-mute after audio finishes
|
||||
if self._self_mute:
|
||||
self._mute_task = self._spawn(
|
||||
self._delayed_mute(3.0), name="self-mute",
|
||||
)
|
||||
|
||||
async def _delayed_mute(self, delay: float) -> None:
|
||||
"""Re-mute after a delay (lets the audio buffer drain fully)."""
|
||||
await asyncio.sleep(delay)
|
||||
try:
|
||||
self._mumble.users.myself.mute()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def shorten_url(self, url: str) -> str:
|
||||
"""Shorten a URL via FlaskPaste. Returns original on failure."""
|
||||
|
||||
Reference in New Issue
Block a user