diff --git a/drb-edge-node/app/internal/call_recorder.py b/drb-edge-node/app/internal/call_recorder.py
index 33300f4..8523fc8 100644
--- a/drb-edge-node/app/internal/call_recorder.py
+++ b/drb-edge-node/app/internal/call_recorder.py
@@ -27,12 +27,15 @@ class CallRecorder:
self._current_file = self._recordings_dir / f"{ts}_{call_id}.mp3"
self._current_call_id = call_id
- # Read directly from the PulseAudio monitor source (zero-delay, no Icecast burst buffer).
- # PULSE_SERVER env var is set in the container environment.
+ # Read from the Icecast stream. burst-size=0 in icecast config ensures
+ # each new connection starts at the live position with no buffered data.
+ stream_url = f"http://{settings.icecast_host}:{settings.icecast_port}{settings.icecast_mount}"
cmd = [
"ffmpeg", "-y",
- "-f", "pulse",
- "-i", "default",
+ "-reconnect", "1",
+ "-reconnect_streamed", "1",
+ "-reconnect_delay_max", "5",
+ "-i", stream_url,
"-acodec", "libmp3lame",
"-ar", "22050",
"-b:a", "32k",
diff --git a/drb-edge-node/app/internal/discord_radio.py b/drb-edge-node/app/internal/discord_radio.py
index 202d1d2..7313500 100644
--- a/drb-edge-node/app/internal/discord_radio.py
+++ b/drb-edge-node/app/internal/discord_radio.py
@@ -46,10 +46,8 @@ class RadioBot:
# Remember where we are so the watchdog can rejoin if we drop
self._guild_id = guild_id
self._channel_id = channel_id
- if call_active:
- self._was_streaming = True
- self._play_stream()
- logger.info(f"Joined #{channel.name} in {guild.name} (streaming={'yes' if call_active else 'waiting for call'})")
+ self._play_stream()
+ logger.info(f"Joined #{channel.name} in {guild.name}")
return True
except Exception as e:
logger.error(f"Failed to join voice channel: {e}")
@@ -73,19 +71,12 @@ class RadioBot:
return False
def start_stream(self):
- """Called when an OP25 call starts — begin transmitting audio and light the ring."""
- self._was_streaming = True
- if self._voice_client and self._voice_client.is_connected():
- if not self._voice_client.is_playing():
- self._play_stream()
- logger.debug("Stream started (call active).")
+ """Called when an OP25 call starts — stream plays continuously, nothing to do."""
+ pass
def stop_stream(self):
- """Called when an OP25 call ends — stop transmitting so the ring goes dark."""
- self._was_streaming = False
- if self._voice_client and self._voice_client.is_connected():
- self._stop_stream()
- logger.debug("Stream stopped (call ended).")
+ """Called when an OP25 call ends — stream plays continuously, nothing to do."""
+ pass
async def stop(self):
self._guild_id = None
@@ -110,17 +101,23 @@ class RadioBot:
def _play_stream(self):
if not self._voice_client:
return
- # Read directly from PulseAudio monitor (zero-delay, no Icecast buffer).
- # PULSE_SERVER is set in the container environment to the shared socket.
+ from app.config import settings
+ stream_url = f"http://{settings.icecast_host}:{settings.icecast_port}{settings.icecast_mount}"
source = discord.FFmpegPCMAudio(
- "default",
- before_options="-f pulse",
+ stream_url,
+ before_options="-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 -probesize 32 -analyzeduration 0 -fflags nobuffer",
)
self._voice_client.play(
discord.PCMVolumeTransformer(source, volume=1.0),
- after=lambda e: logger.error(f"Stream ended unexpectedly: {e}") if e else None,
+ after=self._on_stream_end,
)
+ def _on_stream_end(self, error):
+ if error:
+ logger.error(f"Stream ended: {error}")
+ if self._voice_client and self._voice_client.is_connected() and not self._voice_client.is_playing():
+ asyncio.get_event_loop().call_soon_threadsafe(self._play_stream)
+
def _stop_stream(self):
if self._voice_client and self._voice_client.is_playing():
self._voice_client.stop()
@@ -144,7 +141,6 @@ class RadioBot:
self._guild_id,
self._channel_id,
self._current_token,
- call_active=self._was_streaming,
)
if rejoined:
logger.info("Watchdog: successfully rejoined voice channel.")
diff --git a/icecast/icecast.xml.template b/icecast/icecast.xml.template
index 1c4dfd5..a4433fc 100644
--- a/icecast/icecast.xml.template
+++ b/icecast/icecast.xml.template
@@ -10,8 +10,8 @@
30
15
10
- 1
- 65535
+ 0
+ 0