fix: make !volume apply immediately during playback

stream_audio now accepts a callable for volume, re-read on each PCM
frame instead of capturing a static float at track start.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-21 23:20:17 +01:00
parent eae36aa1f9
commit 67b2dc827d
4 changed files with 16 additions and 10 deletions

View File

@@ -556,8 +556,8 @@ HTML stripped on receive, escaped on send. IRC-only commands are no-ops.
!volume 75 # Set volume (0-100, default 50)
```
Requires: `yt-dlp`, `ffmpeg`, `libopus.so.0` on the host.
Max 50 tracks in queue. Volume applies on next track.
Requires: `yt-dlp`, `ffmpeg`, `libopus` on the host.
Max 50 tracks in queue. Volume changes take effect immediately.
Mumble-only: `!play` replies with error on other adapters, others silently no-op.
## Plugin Template

View File

@@ -1576,7 +1576,7 @@ and voice transmission.
```
- Queue holds up to 50 tracks
- Volume applies from the next track (default: 50%)
- Volume takes effect immediately during playback (default: 50%)
- Title resolved via `yt-dlp --get-title` before playback
- Audio pipeline: `yt-dlp | ffmpeg` subprocess, PCM fed to pymumble
- Commands are Mumble-only; `!play` on other adapters replies with an error,

View File

@@ -78,10 +78,11 @@ async def _play_loop(bot) -> None:
done = asyncio.Event()
ps["done_event"] = done
volume = ps["volume"] / 100.0
try:
await bot.stream_audio(
track.url, volume=volume, on_done=done,
track.url,
volume=lambda: ps["volume"] / 100.0,
on_done=done,
)
except asyncio.CancelledError:
raise
@@ -269,7 +270,7 @@ async def cmd_volume(bot, message):
Usage:
!volume Show current volume
!volume <0-100> Set volume (applies on next track)
!volume <0-100> Set volume (takes effect immediately)
"""
if not _is_mumble(bot):
return

View File

@@ -421,7 +421,7 @@ class MumbleBot:
self,
url: str,
*,
volume: float = 0.5,
volume=0.5,
on_done=None,
) -> None:
"""Stream audio from URL through yt-dlp|ffmpeg to voice channel.
@@ -432,12 +432,16 @@ class MumbleBot:
Feeds raw PCM to pymumble's sound_output which handles Opus
encoding, packetization, and timing.
``volume`` may be a float (static) or a callable returning float
(dynamic, re-read each frame).
"""
if self._mumble is None:
return
_get_vol = volume if callable(volume) else lambda: volume
log.info("stream_audio: starting pipeline for %s (vol=%.0f%%)",
url, volume * 100)
url, _get_vol() * 100)
proc = await asyncio.create_subprocess_exec(
"sh", "-c",
@@ -457,8 +461,9 @@ class MumbleBot:
if len(pcm) < _FRAME_BYTES:
pcm += b"\x00" * (_FRAME_BYTES - len(pcm))
if volume != 1.0:
pcm = _scale_pcm(pcm, volume)
vol = _get_vol()
if vol != 1.0:
pcm = _scale_pcm(pcm, vol)
self._mumble.sound_output.add_sound(pcm)
frames += 1