You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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自身状态更新触发重复事件、阻塞式循环卡死事件循环,以及音频对象被提前回收三个点,对应解决方法如下:

  1. 过滤Bot自身的语音状态更新
    在函数最开头添加判断,直接忽略Bot自己的状态变化,避免重复触发事件:
if member == bot.user:
    return
  1. 替换阻塞循环为回调函数
    原代码中while循环等待播放结束的方式会阻塞Discord.py的事件循环,导致后续操作无法执行。改用play()方法的after参数传入回调函数,在音频播放完成后异步执行断开操作:
def after_playing(error):
    asyncio.run_coroutine_threadsafe(voice_client.disconnect(), bot.loop)
  1. 保留音频源对象引用
    创建音频源变量并保留引用,防止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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 19:43:08