Discord Bot异常:VoiceChannel.connect()后无法执行播放与断开操作
Discord语音Bot失效问题
一年多前编写的Discord Bot,原本在用户加入语音频道时会播放指定音效,此前运行正常,约一周前突然失效。调试过程中发现:
- 用户加入语音频道,触发
on_voice_state_update()调用 - Bot成功加入该语音频道
- Bot的加入操作会触发第二次
on_voice_state_update()调用,此次调用无任何操作后返回 - 后续无其他事件被处理,Bot虽已连接频道,但不会播放音效也不会自动退出
相关代码如下:
@bot.event async def on_voice_state_update(member, before, after): # Ignore voice state changes that don't involve changing channels. if before.channel == after.channel: return # Only do stuff if the user has a hook set up. member_id = str(member.id) if member_id in target_members: # Disconnect bot from any existing voice clients before joining the new one. for vc in bot.voice_clients: await vc.disconnect() # If user is disconnecting, after channel is None. Therefore, do nothing. if after.channel is None: return # Join the user's voice channel and play the sound. target_audio = target_members[member_id] voice_client = await after.channel.connect() # This line cases the new call to on_voice_state_update() voice_client.play(discord.FFmpegPCMAudio(target_audio)) # After playing the sound, disconnect after a short delay. while voice_client.is_playing(): await asyncio.sleep(1) await voice_client.disconnect() return
解决方案
核心问题包括Bot自身状态更新触发重复事件、阻塞式循环卡死事件循环,以及音频对象被提前回收三个点,对应解决方法如下:
- 过滤Bot自身的语音状态更新
在函数最开头添加判断,直接忽略Bot自己的状态变化,避免重复触发事件:
if member == bot.user: return
- 替换阻塞循环为回调函数
原代码中while循环等待播放结束的方式会阻塞Discord.py的事件循环,导致后续操作无法执行。改用play()方法的after参数传入回调函数,在音频播放完成后异步执行断开操作:
def after_playing(error): asyncio.run_coroutine_threadsafe(voice_client.disconnect(), bot.loop)
- 保留音频源对象引用
创建音频源变量并保留引用,防止Python垃圾回收机制提前回收音频对象导致播放失败:
audio_source = discord.FFmpegPCMAudio(target_audio) voice_client.play(audio_source, after=after_playing)
修改后的完整代码:
@bot.event async def on_voice_state_update(member, before, after): # 忽略Bot自身的状态变化 if member == bot.user: return # 忽略未切换频道的状态变化 if before.channel == after.channel: return member_id = str(member.id) if member_id in target_members: # 断开所有已连接的语音客户端 for vc in bot.voice_clients: await vc.disconnect() # 用户断开频道则不处理 if after.channel is None: return target_audio = target_members[member_id] voice_client = await after.channel.connect() # 保存音频源引用,避免被GC回收 audio_source = discord.FFmpegPCMAudio(target_audio) # 定义播放完成后的回调函数,处理错误并断开连接 def after_playing(error): if error: print(f"播放出错: {error}") asyncio.run_coroutine_threadsafe(voice_client.disconnect(), bot.loop) voice_client.play(audio_source, after=after_playing) return
内容的提问来源于stack exchange,提问作者GotCubes
相关产品推荐
相关产品推荐

