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

如何用discord.py实现Discord机器人歌曲列表顺序播放?

实现Discord服务器专属音乐队列与自动播放功能

嘿,我来帮你搞定这个自动播放的问题!其实核心思路就是给每个服务器维护一个播放队列+当前播放器实例,再利用播放器的回调机制实现歌曲结束后的自动续播,下面一步步给你拆解:

一、先搞定存储结构

你需要一个字典,用服务器ID(guild.id)作为键,值可以是一个包含「当前播放器」和「歌曲队列」的字典,这样就能把每个服务器的播放状态隔离开:

# 全局存储每个服务器的播放状态
guild_playback = {}
# 结构示例:guild_playback[guild_id] = {"player": ytdl_player_instance, "queue": ["歌曲URL1", "歌曲URL2"]}

二、核心:用after回调实现自动续播

比起轮询is_playing()或is_done(),更高效可靠的方式是利用播放器的after参数——这是一个播放结束后会自动触发的回调函数,完美适配自动播放场景。

代码示例(结合discord.py和yt-dlp)

先准备基础配置:

import discord
from discord.ext import commands
import yt_dlp as ytdl

bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())

# yt-dlp和FFmpeg配置
ytdl_opts = {
    "format": "bestaudio/best",
    "postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": "mp3"}],
}
ffmpeg_opts = {"options": "-vn"}
ytdl_client = ytdl.YoutubeDL(ytdl_opts)

# 存储每个服务器的播放状态
guild_playback = {}

然后写自动播放的核心函数:

def play_next(guild_id):
    """播放队列中的下一首歌"""
    playback = guild_playback.get(guild_id)
    if not playback or not playback["queue"]:
        # 队列空了,清空播放器实例
        playback["player"] = None
        return
    
    # 取出队列首的歌曲
    next_song_url = playback["queue"].pop(0)
    # 获取音频源
    song_info = ytdl_client.extract_info(next_song_url, download=False)
    audio_source = discord.FFmpegPCMAudio(song_info["url"], **ffmpeg_opts)
    
    # 获取对应服务器的语音客户端
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
    if voice_client and voice_client.is_connected():
        # 开始播放,并设置回调:播放结束后继续调用play_next
        playback["player"] = voice_client.play(audio_source, after=lambda e: play_next(guild_id))

再写点歌命令,整合队列逻辑:

@bot.command(name="play")
async def play_command(ctx, *, song_url):
    guild_id = ctx.guild.id
    # 初始化当前服务器的播放状态(如果不存在)
    if guild_id not in guild_playback:
        guild_playback[guild_id] = {"player": None, "queue": []}
    playback = guild_playback[guild_id]

    # 检查用户是否在语音频道
    if not ctx.author.voice:
        await ctx.send("先加入一个语音频道再点歌呀!")
        return
    voice_channel = ctx.author.voice.channel
    voice_client = ctx.voice_client

    # 连接到语音频道(如果还没连)
    if not voice_client or not voice_client.is_connected():
        voice_client = await voice_channel.connect()

    # 判断当前是否在播放
    if not voice_client.is_playing():
        # 直接播放当前歌曲
        song_info = ytdl_client.extract_info(song_url, download=False)
        audio_source = discord.FFmpegPCMAudio(song_info["url"], **ffmpeg_opts)
        playback["player"] = voice_client.play(audio_source, after=lambda e: play_next(guild_id))
        await ctx.send(f"🎵 正在播放:{song_info['title']}")
    else:
        # 加入队列
        playback["queue"].append(song_url)
        song_info = ytdl_client.extract_info(song_url, download=False)
        await ctx.send(f"📋 已加入队列:{song_info['title']}(当前队列长度:{len(playback['queue'])})")

三、关于is_playing()和is_done()的用法

如果确实需要手动检查播放状态,这两个方法的作用是:

  • voice_client.is_playing():返回True表示当前正在播放音频
  • player.is_done():返回True表示播放器已经完成了当前音频的播放

不过不推荐用轮询的方式(比如每隔1秒检查一次),因为会占用不必要的资源,而且响应不如回调及时。如果一定要用,示例代码大概是这样:

import asyncio

async def monitor_playback(guild_id):
    while True:
        await asyncio.sleep(1)
        playback = guild_playback.get(guild_id)
        if not playback or not playback["player"]:
            break
        voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
        if voice_client and not voice_client.is_playing() and playback["player"].is_done():
            play_next(guild_id)

最后提醒

记得给机器人开启必要的Intents(比如voice_states和guilds),不然可能无法获取语音频道信息哦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:04:44