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

Discord.py音乐机器人队列异常:多曲目添加后跳至最后曲目求助

解决Discord.py音乐机器人队列播放异常问题(基于Interaction Slash命令)

问题背景

使用discord.py开发Discord音乐机器人,采用全局列表queue = []实现队列功能,通过Interaction Slash命令/play添加曲目。当前仅能正常播放前两首,添加第三首及更多时,第一首结束后所有后续曲目会同时尝试播放,无法依次播放。原代码中通过while循环轮询播放状态的逻辑存在问题,尝试使用voice_client.play的after参数但未正确处理异步逻辑。

问题根源

  1. 阻塞式轮询导致并发冲突:原代码中elif voice_client.is_playing()分支的while循环会阻塞当前命令协程,每添加一首新曲目就会启动一个独立协程等待播放结束。当第一首曲目播放完毕后,所有等待的协程会同时触发play_next,导致队列被重复修改、多个曲目同时播放。
  2. 异步回调处理错误:直接用lambda e: play_next()作为after参数时,play_next是异步函数,但after回调是同步执行的,无法直接调用异步函数,必须通过事件循环创建任务来执行。

解决方案

核心逻辑调整

  • 移除阻塞式的while轮询,改用voice_client.play的after参数实现链式回调,当前曲目播放完毕后自动触发下一首播放。
  • 重构play_next函数,确保队列操作和播放逻辑原子化,每次仅处理一首曲目,并为下一首设置新的回调。
  • 为音频源添加requester属性,记录请求用户,方便后续发送播放通知。

修正后的完整代码

import asyncio
import discord
from discord.ext import commands
from your_ytdl_module import YTDLSource  # 替换为你的YTDLSource导入路径

queue = []

@client.tree.command(name="play", description="播放指定音乐")
async def play(interaction: discord.Interaction, query: str):
    await interaction.response.defer()
    
    # 获取音频源并添加到队列
    source = await YTDLSource.from_url(query, loop=client.loop)
    source.requester = interaction.user.mention  # 记录请求用户
    queue.append(source)
    
    # 发送添加到队列的通知
    description = f"**{source}**\n{source.data['webpage_url']}\nRequested by: {source.requester}"
    embed = discord.Embed(title="Added to queue", description=description, color=0x23EBE3)
    embed.set_thumbnail(url="THUMBNAIL_URL")
    await interaction.followup.send(embed=embed)
    
    # 获取语音客户端(确保已连接到语音频道)
    voice_client = interaction.guild.voice_client
    if not voice_client:
        await interaction.followup.send("未连接到语音频道!")
        return
    
    # 定义播放下一首的异步函数
    async def play_next():
        if len(queue) > 1:
            # 移除已播放完毕的曲目(当前播放的是queue[0])
            queue.pop(0)
            next_source = queue[0]
            
            # 发送当前播放通知
            play_description = f"**{next_source}**\n{next_source.data['webpage_url']}\nRequested by: {next_source.requester}"
            play_embed = discord.Embed(title="Now playing", description=play_description, color=0x23EBE3)
            play_embed.set_thumbnail(url="THUMBNAIL_URL")
            await interaction.followup.send(embed=play_embed)
            
            # 播放下一首,并设置after回调(同步回调中用create_task执行异步函数)
            voice_client.play(next_source, after=lambda e: asyncio.create_task(play_next()))
        elif len(queue) == 1:
            # 最后一首播放完毕,清空队列
            queue.pop(0)
            await interaction.followup.send("队列已空,等待新的音乐请求~")
    
    # 如果当前未播放,启动第一首并设置回调
    if not voice_client.is_playing():
        current_source = queue[0]
        # 发送当前播放通知
        current_description = f"**{current_source}**\n{current_source.data['webpage_url']}\nRequested by: {current_source.requester}"
        current_embed = discord.Embed(title="Now playing", description=current_description, color=0x23EBE3)
        current_embed.set_thumbnail(url="THUMBNAIL_URL")
        await interaction.followup.send(embed=current_embed)
        
        # 启动播放并绑定after回调
        voice_client.play(current_source, after=lambda e: asyncio.create_task(play_next()))

关键细节说明

  1. 链式回调机制:每首曲目播放时通过after参数绑定play_next,当前曲目播放完毕后自动触发下一首,避免了轮询带来的并发问题。
  2. 异步回调处理:由于after是同步回调函数,使用asyncio.create_task(play_next())将异步函数包装成任务提交给事件循环执行,确保异步逻辑正确运行。
  3. 队列原子操作:play_next中每次仅弹出一首已播放的曲目,然后检查队列是否有剩余曲目,保证队列操作的顺序性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 04:00:40