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

Discord.py中voice.play的after方法调用时无法发送消息问题

问题描述

使用discord.py开发音乐机器人时,遇到以下问题:

  • 从play命令直接调用play_song方法时,消息发送等功能正常
  • 通过VoiceClient.play的after参数调用play_song时,程序执行到await ctx.send会无限挂起

试过直接调用play函数、传入频道ID而非上下文、去掉await、使用异步生成器等方法,均未解决。

代码示例
from discord.ext import commands
from discord import FFmpegPCMAudio
from asyncio import run

FFMPEG_OPTIONS = {'before_options': '-avioflags direct', 'options': '-vn -sn -dn -movflags +faststart -b:a 49k'}
class Play(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.run = False
        self.voice = ""

    async def play_song(self, ctx ,data=()):
        if data != ():
            data = "+".join(data)
            print(data)
        else:
            print("Passes Automatically")
            await ctx.send("auto pass")

        await ctx.send("playing")

        source = FFmpegPCMAudio("https://www2.iis.fraunhofer.de/AAC/ChID-BLITS-EBU-Narration.mp4", **FFMPEG_OPTIONS)
        await self.voice.play(source, after=lambda x=None: run(self.play_song(ctx)))

    @commands.Cog.listener()
    async def on_ready(self):
        print("Play is working")


    @commands.command(pass_context=True)
    async def play(self, ctx, *initial_user_input):
        if not self.run:
            if ctx.author.voice:
                channel = ctx.message.author.voice.channel
                self.voice = await channel.connect()
                self.run = True
                await self.play_song(ctx, initial_user_input)
            else:
                await ctx.send("Channel Not Found")
        else:
            await self.play_song(ctx, initial_user_input)


async def setup(bot):
    await bot.add_cog(Play(bot))
问题原因
  1. VoiceClient.play是同步方法,它的after回调会在独立于discord.py主事件循环的线程中同步执行
  2. 使用asyncio.run()会创建一个全新的事件循环,和bot的主循环完全隔离,导致ctx.send这类依赖主循环的异步操作无法被调度,进而无限挂起
  3. 原代码中给self.voice.play加了await,但该方法本身不是异步方法,属于错误用法
修复方案

核心修改点

  • 移除self.voice.play前的await
  • 用bot的主事件循环来调度play_song异步任务,替换asyncio.run()

修改后的play_song方法

async def play_song(self, ctx ,data=()):
    if data != ():
        data = "+".join(data)
        print(data)
    else:
        print("Passes Automatically")
        await ctx.send("auto pass")

    await ctx.send("playing")

    source = FFmpegPCMAudio("https://www2.iis.fraunhofer.de/AAC/ChID-BLITS-EBU-Narration.mp4", **FFMPEG_OPTIONS)
    # 用bot的主循环创建异步任务,代替asyncio.run()
    self.voice.play(source, after=lambda x=None: self.bot.loop.create_task(self.play_song(ctx)))

额外说明

  • self.bot.loop.create_task()会把异步任务提交到bot的主事件循环中执行,确保ctx.send等操作能正常被调度
  • 如果需要处理after回调中的异常,可以在lambda中添加捕获逻辑,或者在play_song内部加入try-except块

内容的提问来源于stack exchange,提问作者MrMez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:25:21