Discord.py音乐机器人播放与队列功能异常求助
解决Discord.py音乐机器人的播放队列问题
嘿,我看到你在开发Discord.py音乐机器人时遇到了两个播放相关的问题,让我一步步帮你解决它们!
问题1:播放新曲目时直接跳过当前歌曲
问题原因
你的ensure_voice装饰器里有这样一段代码:
elif ctx.voice_client.is_playing(): ctx.voice_client.stop()
这段代码会在当前有歌曲播放时直接终止它,然后播放新曲目,这就是为什么新曲目会跳过当前歌曲的原因。我们需要改成当前播放时将新曲目加入队列,而非停止当前播放。
解决方案
首先修改ensure_voice函数,移除停止当前播放的逻辑:
@play_queue.before_invoke @play.before_invoke async def ensure_voice(self, ctx): if ctx.voice_client is None: if ctx.author.voice: await ctx.author.voice.channel.connect() else: await ctx.send("You are not connected to a voice channel.") raise commands.CommandError("Author not connected to a voice channel.") # 移除停止当前播放的代码
然后修改play命令的逻辑,判断当前是否在播放:如果是,就把曲目加入队列;否则直接播放:
@commands.command() async def play(self, ctx, *, url): try: # 先检查是否在播放,如果是则加入队列 if ctx.voice_client.is_playing(): global queue queue.append(url) await ctx.send(f'``{url}`` was added to the queue!') return async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) # 在after回调里自动播放队列的下一首 ctx.voice_client.play(player, after=lambda e: asyncio.run_coroutine_threadsafe(self.play_next(ctx), self.bot.loop) if not e else print('Player error: %s' % e)) await ctx.send(f':mag_right: **Searching for** ``{url}``\n<:youtube:763374159567781890> **Now Playing:** ``{player.title}``') except Exception as e: await ctx.send(f"Something went wrong - please try again later!\nError: {str(e)}")
问题2:play_queue命令仅播放队列第一首歌
问题原因
你用for循环直接遍历队列并调用play,但ctx.voice_client.play是非阻塞的,循环会瞬间执行完所有play调用,导致只有最后一个请求生效(或者第一个,取决于执行顺序),其他的被覆盖。我们需要利用after回调来实现播放完一首后自动播放下一首的逻辑。
解决方案
首先添加一个辅助方法play_next,用来处理队列的下一首播放:
async def play_next(self, ctx): global queue if len(queue) > 0: next_url = queue.pop(0) try: player = await YTDLSource.from_url(next_url, loop=self.bot.loop, stream=True) ctx.voice_client.play(player, after=lambda e: asyncio.run_coroutine_threadsafe(self.play_next(ctx), self.bot.loop) if not e else print('Player error: %s' % e)) await ctx.send(f'<:youtube:763374159567781890> **Now Playing:** ``{player.title}``') except Exception as e: await ctx.send(f"Failed to play next track: {str(e)}") # 继续尝试播放下一首 await self.play_next(ctx) else: await ctx.send("Queue is now done!")
然后修改play_queue命令,只需要触发第一首的播放,剩下的交给after回调处理:
@commands.command() async def play_queue(self, ctx): global queue if len(queue) == 0: await ctx.send("Queue is empty!") return # 如果当前没有播放,就开始播放队列的第一首 if not ctx.voice_client.is_playing(): await self.play_next(ctx) else: await ctx.send("Bot is already playing music! Queue will continue after current track.")
修改后的完整代码
import discord import asyncio import os import youtube_dl import urllib.parse, urllib.request, re import requests from discord.ext import commands from discord import Embed, FFmpegPCMAudio from discord.utils import get ''' INSTALLING YOUTUBE-DL pip install -U git+https://github.com/l1ving/youtube-dl ''' queue = [] youtube_dl.utils.bug_reports_message = lambda: '' ytdl_format_options = { 'format': 'bestaudio/best', 'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s', 'restrictfilenames': True, 'noplaylist': True, 'nocheckcertificate': True, 'ignoreerrors': False, 'logtostderr': False, 'quiet': True, 'no_warnings': True, 'default_search': 'auto', 'source_address': '0.0.0.0' } ffmpeg_options = { 'options': '-vn' } ytdl = youtube_dl.YoutubeDL(ytdl_format_options) class YTDLSource(discord.PCMVolumeTransformer): def __init__(self, source, *, data, volume=0.5): super().__init__(source, volume) self.data = data self.title = data.get('title') self.url = data.get('url') @classmethod async def from_url(cls, url, *, loop=None, stream=False, play=False): loop = loop or asyncio.get_event_loop() data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream or play)) if 'entries' in data: data = data['entries'][0] filename = data['url'] if stream else ytdl.prepare_filename(data) return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) class Music(commands.Cog): def __init__(self, bot): self.bot = bot async def play_next(self, ctx): global queue if len(queue) > 0: next_url = queue.pop(0) try: player = await YTDLSource.from_url(next_url, loop=self.bot.loop, stream=True) ctx.voice_client.play(player, after=lambda e: asyncio.run_coroutine_threadsafe(self.play_next(ctx), self.bot.loop) if not e else print('Player error: %s' % e)) await ctx.send(f'<:youtube:763374159567781890> **Now Playing:** ``{player.title}``') except Exception as e: await ctx.send(f"Failed to play next track: {str(e)}") await self.play_next(ctx) else: await ctx.send("Queue is now done!") @commands.command() async def join(self, ctx): if not ctx.message.author.voice: await ctx.send("You are not connected to a voice channel!") return else: channel = ctx.message.author.voice.channel await ctx.send(f'Connected to ``{channel}``') await channel.connect() @commands.command() async def play(self, ctx, *, url): try: if ctx.voice_client.is_playing(): global queue queue.append(url) await ctx.send(f'``{url}`` was added to the queue!') return async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) ctx.voice_client.play(player, after=lambda e: asyncio.run_coroutine_threadsafe(self.play_next(ctx), self.bot.loop) if not e else print('Player error: %s' % e)) await ctx.send(f':mag_right: **Searching for** ``{url}``\n<:youtube:763374159567781890> **Now Playing:** ``{player.title}``') except Exception as e: await ctx.send(f"Something went wrong - please try again later!\nError: {str(e)}") @commands.command() async def play_queue(self, ctx): global queue if len(queue) == 0: await ctx.send("Queue is empty!") return if not ctx.voice_client.is_playing(): await self.play_next(ctx) else: await ctx.send("Bot is already playing music! Queue will continue after current track.") @commands.command() async def pause(self, ctx): voice = get(self.bot.voice_clients, guild=ctx.guild) voice.pause() user = ctx.message.author.mention await ctx.send(f"Bot was paused by {user}") @commands.command() async def resume(self, ctx): voice = get(self.bot.voice_clients, guild=ctx.guild) voice.resume() user = ctx.message.author.mention await ctx.send(f"Bot was resumed by {user}") @commands.command() async def add_queue(self, ctx, url): global queue try: queue.append(url) user = ctx.message.author.mention await ctx.send(f'``{url}`` was added to the queue by {user}!') except: await ctx.send(f"Couldnt add {url} to the queue!") @commands.command() async def remove_queue(self, ctx, number): global queue try: del(queue[int(number)]) if len(queue) < 1: await ctx.send("Your queue is empty now!") else: await ctx.send(f'Your queue is now {queue}') except: await ctx.send("List index out of range - the queue starts at 0") @commands.command() async def clear_queue(self, ctx): global queue queue.clear() user = ctx.message.author.mention await ctx.send(f"The queue was cleared by {user}") @commands.command() async def view_queue(self, ctx): if len(queue) < 1: await ctx.send("The queue is empty - nothing to see here!") else: await ctx.send(f'Your queue is now {queue}') @commands.command() async def leave(self, ctx): voice_client = ctx.message.guild.voice_client user = ctx.message.author.mention await voice_client.disconnect() await ctx.send(f'Disconnected from {user}') @play_queue.before_invoke @play.before_invoke async def ensure_voice(self, ctx): if ctx.voice_client is None: if ctx.author.voice: await ctx.author.voice.channel.connect() else: await ctx.send("You are not connected to a voice channel.") raise commands.CommandError("Author not connected to a voice channel.") # 移除了停止当前播放的逻辑 def setup(client): client.add_cog(Music(client))
关键修改点总结
- 移除了
ensure_voice中停止当前播放的代码,改为在play命令中判断是否播放,是则加入队列。 - 添加了
play_next辅助方法,利用after回调实现队列的自动续播。 - 修改了
play_queue命令,不再用循环,而是触发第一首播放,后续由回调处理。
这样修改后,你的机器人就能:
- 播放新曲目时,如果当前有歌曲在播放,自动加入队列等待。
- 执行
play_queue时,依次播放队列中的所有曲目,直到队列清空。
内容的提问来源于stack exchange,提问作者user14200689
相关产品推荐
相关产品推荐

