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

Discord.py 歌曲循环功能异常:循环时机错误且无法取消

问题分析与修复方案

问题概述

我尝试制作一个Discord机器人命令,实现歌曲播放完毕后自动循环,但目前存在三个核心问题:

  1. 循环会在1-1.5分钟后触发,而非等待歌曲播放完毕
  2. 开启循环后无法取消,触发IndexError: list index out of range
  3. 出现FFmpeg读取错误,导致播放提前终止

原代码如下:

@client.command() 
@commands.is_owner()
async def loop(ctx):
    vc = ctx.voice_client
    current_song = queuee[ctx.guild.id][0]
    song_name = songs[ctx.guild.id][0]

    guild_id = ctx.guild.id
    if guild_id not in loop_status:
        loop_status[guild_id] = False

    if not loop_status[guild_id]:
        loop_status[guild_id] = True
        await ctx.send("Looping current song.")
        loop = True
        

    else:
        loop_status[guild_id] = False
        loop = False

        await ctx.send("Unlooping current song.")
async def check_queue(ctx):
    global loop
    if ctx.voice_client.is_connected():
        if queuee[ctx.guild.id]:
            if not ctx.voice_client.is_playing():
                if loop == False:
                    queuee[ctx.guild.id].pop(0)
                    songs[ctx.guild.id].pop(0)
                    vc = ctx.voice_client
                    player = discord.FFmpegPCMAudio(queuee[ctx.guild.id][0], **ffmpeg_options)
                    vc.play(player) 
                    return
                else:
                    vc = ctx.voice_client
                    player = discord.FFmpegPCMAudio(queuee[ctx.guild.id][0], **ffmpeg_options)
                    vc.play(player)
                    return
        else:
            await asyncio.sleep(30)
            await vc.disconnect()
    else:
        return

触发的错误信息:

Traceback (most recent call last):
  File "/home/container/.local/lib/python3.9/site-packages/discord/client.py", line 409, in _run_event
    await coro(*args, **kwargs)
  File "/home/container/main.py", line 1414, in on_command_error
    raise error
  File "/home/container/.local/lib/python3.9/site-packages/discord/ext/commands/core.py", line 190, in wrapped
    ret = await coro(*args, **kwargs)
  File "/home/container/main.py", line 570, in loop
    current_song = queuee[ctx.guild.id][0]
IndexError: list index out of range
[tls @ 0x564839bd9b40] Error in the pull function.
[matroska,webm @ 0x564839bd6900] Read error
[tls @ 0x564839bd9b40] The specified session has been invalidated for some reason.
    Last message repeated 1 times

问题根源

  1. 循环时机错误:原代码通过轮询is_playing()状态判断播放完成,轮询间隔过短或状态检测不准确,导致提前触发循环。
  2. 索引越界错误:loop命令中强制读取队列第一个元素,取消循环时若队列为空则触发索引错误;同时全局loop变量无法支持多服务器场景。
  3. FFmpeg读取错误:网络不稳定导致流媒体连接中断,未配置重连参数。

修复方案

1. 优化FFmpeg参数(解决网络读取错误)

添加重连配置,缓解网络波动导致的播放中断:

ffmpeg_options = {
    'options': '-vn',
    'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'
}

2. 修复loop命令(解决索引越界)

移除无用的队列读取操作,改用服务器独立的状态存储:

@client.command() 
@commands.is_owner()
async def loop(ctx):
    vc = ctx.voice_client
    if not vc or not vc.is_connected():
        await ctx.send("未连接到语音频道!")
        return
    
    guild_id = ctx.guild.id
    # 初始化当前服务器的循环状态
    if guild_id not in loop_status:
        loop_status[guild_id] = False
    
    # 切换循环状态
    loop_status[guild_id] = not loop_status[guild_id]
    if loop_status[guild_id]:
        await ctx.send("已开启当前歌曲循环。")
    else:
        await ctx.send("已关闭歌曲循环。")

3. 重构播放逻辑(解决循环时机错误)

使用Discord.py原生的after回调,精准检测歌曲播放完成事件:

def play_next(ctx):
    guild_id = ctx.guild.id
    vc = ctx.voice_client
    if not vc or not vc.is_connected():
        return
    
    # 检查队列是否为空
    if not queuee.get(guild_id, []):
        asyncio.create_task(disconnect_after_idle(vc))
        return
    
    # 根据循环状态处理播放逻辑
    if loop_status.get(guild_id, False):
        # 循环:直接重播当前歌曲
        player = discord.FFmpegPCMAudio(queuee[guild_id][0], **ffmpeg_options)
        vc.play(player, after=lambda e: play_next(ctx))
    else:
        # 不循环:播放下一首
        queuee[guild_id].pop(0)
        songs[guild_id].pop(0)
        if queuee[guild_id]:
            player = discord.FFmpegPCMAudio(queuee[guild_id][0], **ffmpeg_options)
            vc.play(player, after=lambda e: play_next(ctx))
        else:
            asyncio.create_task(disconnect_after_idle(vc))

async def disconnect_after_idle(vc):
    await asyncio.sleep(30)
    if vc.is_connected() and not vc.is_playing():
        await vc.disconnect()

4. 调用方式修改

在你的play命令中,启动播放时需要绑定after回调:

# 示例play命令中的播放逻辑
player = discord.FFmpegPCMAudio(song_url, **ffmpeg_options)
vc.play(player, after=lambda e: play_next(ctx))

修复说明

  • 移除了loop命令中危险的队列强制读取操作,彻底避免索引越界
  • 用loop_status字典替代全局loop变量,支持多服务器独立的循环状态
  • 通过after回调实现精准的播放完成检测,解决提前循环问题
  • 添加FFmpeg重连参数,缓解网络导致的播放中断

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 20:10:28