如何用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
相关产品推荐
相关产品推荐

