Fix: Reuse voice connection when switching radio stations
Instead of destroying and recreating the voice connection on every station change, now checks if the bot is already in the target channel. If same channel: only stops ffmpeg/player, spawns new stream, reuses the existing connection (no reconnect flicker). If different channel: full disconnect + reconnect as before. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
63afc55836
commit
b821ad9615
1 changed files with 45 additions and 19 deletions
|
|
@ -70,13 +70,20 @@ function setFavorites(favs: Favorite[]): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Streaming ──
|
// ── 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);
|
const state = guildRadioState.get(guildId);
|
||||||
if (!state) return;
|
if (!state) return;
|
||||||
try { state.ffmpeg.kill('SIGKILL'); } catch {}
|
try { state.ffmpeg.kill('SIGKILL'); } catch {}
|
||||||
try { state.player.stop(true); } catch {}
|
try { state.player.stop(true); } catch {}
|
||||||
try { getVoiceConnection(guildId, 'radio')?.destroy(); } catch {}
|
|
||||||
guildRadioState.delete(guildId);
|
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);
|
connectedSince.delete(guildId);
|
||||||
broadcastState(guildId);
|
broadcastState(guildId);
|
||||||
console.log(`[Radio] Stopped stream in guild ${guildId}`);
|
console.log(`[Radio] Stopped stream in guild ${guildId}`);
|
||||||
|
|
@ -86,12 +93,23 @@ async function startStream(
|
||||||
ctx: PluginContext, guildId: string, voiceChannelId: string,
|
ctx: PluginContext, guildId: string, voiceChannelId: string,
|
||||||
stationId: string, stationName: string, placeName: string, country: string,
|
stationId: string, stationName: string, placeName: string, country: string,
|
||||||
): Promise<{ ok: boolean; error?: string }> {
|
): Promise<{ ok: boolean; error?: string }> {
|
||||||
// Stoppe laufenden Stream in diesem Guild
|
// Check if we can reuse the existing voice connection
|
||||||
stopStream(guildId);
|
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
|
// Stream-URL auflösen
|
||||||
const streamUrl = await resolveStreamUrl(stationId);
|
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
|
// Guild + Channel finden
|
||||||
const guild = ctx.client.guilds.cache.get(guildId);
|
const guild = ctx.client.guilds.cache.get(guildId);
|
||||||
|
|
@ -99,8 +117,14 @@ async function startStream(
|
||||||
const channel = guild.channels.cache.get(voiceChannelId) as VoiceBasedChannel | undefined;
|
const channel = guild.channels.cache.get(voiceChannelId) as VoiceBasedChannel | undefined;
|
||||||
if (!channel) return { ok: false, error: 'Voice Channel nicht gefunden' };
|
if (!channel) return { ok: false, error: 'Voice Channel nicht gefunden' };
|
||||||
|
|
||||||
// Voice-Channel joinen
|
// Reuse or create voice connection
|
||||||
const connection = joinVoiceChannel({
|
let connection = sameChannel ? existingConn! : null;
|
||||||
|
if (!connection) {
|
||||||
|
// Different channel or no connection → full join
|
||||||
|
try { existingConn?.destroy(); } catch {}
|
||||||
|
connectedSince.delete(guildId);
|
||||||
|
|
||||||
|
connection = joinVoiceChannel({
|
||||||
channelId: voiceChannelId,
|
channelId: voiceChannelId,
|
||||||
guildId,
|
guildId,
|
||||||
adapterCreator: guild.voiceAdapterCreator,
|
adapterCreator: guild.voiceAdapterCreator,
|
||||||
|
|
@ -114,6 +138,7 @@ async function startStream(
|
||||||
connection.destroy();
|
connection.destroy();
|
||||||
return { ok: false, error: 'Voice-Verbindung fehlgeschlagen' };
|
return { ok: false, error: 'Voice-Verbindung fehlgeschlagen' };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ffmpeg spawnen – Radio-Stream → raw PCM
|
// ffmpeg spawnen – Radio-Stream → raw PCM
|
||||||
const ffmpeg = spawn('ffmpeg', [
|
const ffmpeg = spawn('ffmpeg', [
|
||||||
|
|
@ -138,6 +163,7 @@ async function startStream(
|
||||||
console.log(`[Radio] ffmpeg exited (code ${code}), cleaning up`);
|
console.log(`[Radio] ffmpeg exited (code ${code}), cleaning up`);
|
||||||
guildRadioState.delete(guildId);
|
guildRadioState.delete(guildId);
|
||||||
try { getVoiceConnection(guildId, 'radio')?.destroy(); } catch {}
|
try { getVoiceConnection(guildId, 'radio')?.destroy(); } catch {}
|
||||||
|
connectedSince.delete(guildId);
|
||||||
broadcastState(guildId);
|
broadcastState(guildId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -169,7 +195,7 @@ async function startStream(
|
||||||
});
|
});
|
||||||
|
|
||||||
broadcastState(guildId);
|
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 };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue