diff --git a/server/src/plugins/radio/index.ts b/server/src/plugins/radio/index.ts index 5c94d78..5e2fcf8 100644 --- a/server/src/plugins/radio/index.ts +++ b/server/src/plugins/radio/index.ts @@ -70,13 +70,20 @@ function setFavorites(favs: Favorite[]): void { } // ── Streaming ── -function stopStream(guildId: string): void { + +/** Stop only audio (ffmpeg + player), keep voice connection alive */ +function stopAudio(guildId: string): void { const state = guildRadioState.get(guildId); if (!state) return; try { state.ffmpeg.kill('SIGKILL'); } catch {} try { state.player.stop(true); } catch {} - try { getVoiceConnection(guildId, 'radio')?.destroy(); } catch {} guildRadioState.delete(guildId); +} + +/** Full stop: audio + voice connection + broadcast */ +function stopStream(guildId: string): void { + stopAudio(guildId); + try { getVoiceConnection(guildId, 'radio')?.destroy(); } catch {} connectedSince.delete(guildId); broadcastState(guildId); console.log(`[Radio] Stopped stream in guild ${guildId}`); @@ -86,12 +93,23 @@ async function startStream( ctx: PluginContext, guildId: string, voiceChannelId: string, stationId: string, stationName: string, placeName: string, country: string, ): Promise<{ ok: boolean; error?: string }> { - // Stoppe laufenden Stream in diesem Guild - stopStream(guildId); + // Check if we can reuse the existing voice connection + const prev = guildRadioState.get(guildId); + const existingConn = getVoiceConnection(guildId, 'radio'); + const sameChannel = prev && existingConn && prev.channelId === voiceChannelId + && existingConn.state.status === VoiceConnectionStatus.Ready; + + // Stop only audio (keep connection if same channel) + stopAudio(guildId); // Stream-URL auflösen const streamUrl = await resolveStreamUrl(stationId); - if (!streamUrl) return { ok: false, error: 'Stream-URL konnte nicht aufgelöst werden' }; + if (!streamUrl) { + // No stream → full disconnect if nothing playing + if (!sameChannel) try { existingConn?.destroy(); } catch {} + broadcastState(guildId); + return { ok: false, error: 'Stream-URL konnte nicht aufgelöst werden' }; + } // Guild + Channel finden const guild = ctx.client.guilds.cache.get(guildId); @@ -99,20 +117,27 @@ async function startStream( const channel = guild.channels.cache.get(voiceChannelId) as VoiceBasedChannel | undefined; if (!channel) return { ok: false, error: 'Voice Channel nicht gefunden' }; - // Voice-Channel joinen - const connection = joinVoiceChannel({ - channelId: voiceChannelId, - guildId, - adapterCreator: guild.voiceAdapterCreator, - selfDeaf: true, - group: 'radio', - }); + // Reuse or create voice connection + let connection = sameChannel ? existingConn! : null; + if (!connection) { + // Different channel or no connection → full join + try { existingConn?.destroy(); } catch {} + connectedSince.delete(guildId); - try { - await entersState(connection, VoiceConnectionStatus.Ready, 10_000); - } catch { - connection.destroy(); - return { ok: false, error: 'Voice-Verbindung fehlgeschlagen' }; + connection = joinVoiceChannel({ + channelId: voiceChannelId, + guildId, + adapterCreator: guild.voiceAdapterCreator, + selfDeaf: true, + group: 'radio', + }); + + try { + await entersState(connection, VoiceConnectionStatus.Ready, 10_000); + } catch { + connection.destroy(); + return { ok: false, error: 'Voice-Verbindung fehlgeschlagen' }; + } } // ffmpeg spawnen – Radio-Stream → raw PCM @@ -138,6 +163,7 @@ async function startStream( console.log(`[Radio] ffmpeg exited (code ${code}), cleaning up`); guildRadioState.delete(guildId); try { getVoiceConnection(guildId, 'radio')?.destroy(); } catch {} + connectedSince.delete(guildId); broadcastState(guildId); } }); @@ -169,7 +195,7 @@ async function startStream( }); broadcastState(guildId); - console.log(`[Radio] ▶ "${stationName}" (${placeName}, ${country}) → ${guild.name}/#${channelName}`); + console.log(`[Radio] ${sameChannel ? '\u{1F504}' : '\u25B6'} "${stationName}" (${placeName}, ${country}) → ${guild.name}/#${channelName}`); return { ok: true }; }