Discord.py bot 歌曲排队: voice_client.play() 在开始时播放所有循环

时间:2021-06-18 14:47:36

标签: python discord discord.py python-asyncio

我有一个不和谐的机器人,它允许用户对歌曲进行排队,这是代码的简化版本:

class Music(commands.Cog):
  def __init__(self, bot: Bot, config: Dict[str, Any]):
      self.bot = bot
      self.queue = asyncio.Queue()
      self.urlqueue = list()
      self.yt_api = YoutubeAPI(config["YD_DL_OPTS"], DL_DIR)
      self.load_queue.start()

  @commands.command(name="play", aliases=["p"], pass_context=True, usage=DOCS_PLAY)
  async def play(self, ctx: Context, *args):
      if ctx.voice_client.is_playing():
          ctx.voice_client.stop()
      await self.play_song(ctx)

  @commands.command(name="queue", aliases=["q"], pass_context=True, usage=DOCS_QUEUE)
  async def queue(self, ctx: Context, *args):    
      self.urlqueue.append(args[0])

  @tasks.loop(seconds=5.0)
  async def load_queue(self):
      if len(self.urlqueue) == 0:
          return
      for track in self.yt_api.create_tracks(self.urlqueue.pop(0)):
          if self.yt_api.download_track(track):
              await self.queue.put(track)
              logger.info("queued track [{}]".format(track.title))

  async def play_song(self, ctx: Context):
      logger.info("getting track [{}]".format(track.title))
      track = await self.queue.get()
      logger.info("playing track [{}]".format(track.title))
      await ctx.send(content="playing track {}".format(track.title))
      ctx.voice_client.play(discord.FFmpegPCMAudio(track.filename), after=await self.after(ctx))
      ctx.voice_client.is_playing()

  async def after(self, ctx):
      if not self.queue.empty() and not ctx.voice_client.is_playing():
          logger.info("looping start")
          await self.play_song(ctx)
          logger.info("looping end")

  def cog_unload(self):
      self.load_queue.cancel()

当使用 queue 命令传递 url 时,会通过循环 asyncio.Queue() 方法创建、下载轨道并将其添加到 load_queue()

问题发生在调用 play 命令时,在 play_song() 行上的方法 ctx.voice_client.play() 中,甚至在播放第一首曲目之前立即调用 after 参数.这导致该方法首先快速循环遍历队列中的所有歌曲并尝试一次播放它们。最后只有队列中的第一首歌曲被实际播放,因为其他歌曲都遇到了 ClientException('Already playing audio.') 异常。日志如下所示:

2021-06-18 10:38:09,004 | INFO     | 0033 | Bot online!
2021-06-18 10:38:23,442 | INFO     | 0222 | queued track [i miss you]
2021-06-18 10:38:26,882 | INFO     | 0222 | queued track [When you taste an energy drink for the first time]
2021-06-18 10:38:32,828 | INFO     | 0205 | joined General by request of Admin
2021-06-18 10:38:32,829 | INFO     | 0226 | got track [i miss you]
2021-06-18 10:38:32,829 | INFO     | 0230 | playing track [i miss you]
2021-06-18 10:38:32,832 | INFO     | 0236 | loop start
2021-06-18 10:38:32,832 | INFO     | 0226 | got track [When you taste an energy drink for the first time]
2021-06-18 10:38:32,833 | INFO     | 0230 | playing track [When you taste an energy drink for the first time]
2021-06-18 10:38:32,837 | INFO     | 0238 | loop end
Ignoring exception in command play:
Traceback (most recent call last):
  File "C:\Users\kenmu\Repos\DiscordBot\venv\lib\site-packages\discord\ext\commands\core.py", line 85, in wrapped
    ret = await coro(*args, **kwargs)
  File "C:\Users\kenmu\Repos\DiscordBot\music.py", line 91, in play
    await self.play_song(ctx)
  File "C:\Users\kenmu\Repos\DiscordBot\music.py", line 231, in play_song
    ctx.voice_client.play(discord.FFmpegPCMAudio(track.filename), after=await self.after(ctx))
  File "C:\Users\kenmu\Repos\DiscordBot\venv\lib\site-packages\discord\voice_client.py", line 558, in play
    raise ClientException('Already playing audio.')
discord.errors.ClientException: Already playing audio.

如您所见,它尝试在几毫秒内遍历整个队列。我以为 after 参数是在歌曲播放完毕后触发的?歌曲完成后如何让它触发?有没有更好的方法来处理播放歌曲队列?

1 个答案:

答案 0 :(得分:1)

According to the docs

<块引用>

终结器,在源耗尽或发生错误后调用。

...

  • after (Callable[[Exception], Any]) – 在流耗尽后调用的终结器。此函数必须有一个参数 error,它表示在播放期间引发的可选异常。

当您设置 await self.after(ctx) 参数时,您正在调用 after,该参数甚至在调用 ctx.voice_client.play 之前。您需要为它提供一个可调用的(例如一个函数),该函数在播放过程中引发异常,如果没有引发异常,则默认为 None

像这样:

    async def play_song(self, ctx: Context):
        logger.info("getting track [{}]".format(track.title))
        track = await self.queue.get()
        logger.info("playing track [{}]".format(track.title))
        await ctx.send(content="playing track {}".format(track.title))
        ctx.voice_client.play(
            discord.FFmpegPCMAudio(track.filename),
            after=lambda ex: asyncio.get_running_loop().create_task(self.after(ctx))
        )
        ctx.voice_client.is_playing()

    async def after(self, ctx):
        if not self.queue.empty() and not ctx.voice_client.is_playing():
            logger.info("looping start")
            await self.play_song(ctx)
            logger.info("looping end")

但是您不应该使用 after 参数来播放队列中的下一首歌曲。你可以see how it's done (with a while loop in the player_loop coroutine) here

相关问题