Pycord-Development/pycord

[voice] broken audio playback in stage channels

arvitus opened this issue · 1 comments

Summary

There is a really strange bug happening in stage channels. When playing audio in a stage channel, after pausing and resuming the playback, the bot does not play anything. Internally though the playback is continuing, according to VoiceClient.is_playing.

Yes the stage is started, the bot is a speaker and it has according permissions.

That is just one case, there are multiple others:

  • When using VoiceClient.play after the bot becomes a speaker in a stage channel, it will not play any audio. When using play before the bot becomes a speaker, it works. Until you pause ofc.
  • As long as the bot does not start/pause/stop playback while being a speaker, everything works fine. So the bot could start playing, become a speaker, become a listener, pause playing, resume playing and become a speaker again and you would hear something.

There are a few additional things that I discovered:

  • It's only a pycord problem. With discord.py it works just fine.
  • If you do VoiceClient._player.pause(update_speaking=False) and VoiceClient._player.resume(update_speaking=False), everything works. So it seems like it's related to the AudioPlayer._speak method. I did look further but could not find anything.
  • It happens only in stage channels, not in normal voice channels.
  • No difference between using discord.PCMAudio and discord.FFmpegPCMAudio (with local file or remote stream) as player

Reproduction Steps

Join a stage channel and start a stage instance. Become speaker. Start playing audio (might already not work, try starting playing before becoming speaker). Pause the playback. Resume the playback. (Or stop and start playing again)

Minimal Reproducible Code

import discord

GUILD_ID = guild id
TOKEN = bot token

bot = discord.Bot(debug_guilds=[GUILD_ID])


@bot.slash_command()
@discord.option(
    name="channel",
    required=True,
    type=discord.VoiceChannel | discord.StageChannel,
)
@discord.option(
    name="url",
    required=False,
    type=str,
)
@discord.guild_only()
async def play(
    ctx: discord.ApplicationContext,
    channel: discord.VoiceChannel | discord.StageChannel | None,
    url: str | None = "https://fm.live.brovenco.de/listen/webradio/radio.mp3",
):
    vc = ctx.voice_client or await channel.connect()
    vc.play(discord.FFmpegPCMAudio(url))
    await ctx.respond(f"Playing in {channel.mention}", ephemeral=True)


@bot.slash_command()
@discord.guild_only()
async def pause(ctx: discord.ApplicationContext):
    ctx.guild.voice_client.pause()
    await ctx.respond("Paused", ephemeral=True)


@bot.slash_command()
@discord.guild_only()
async def resume(ctx: discord.ApplicationContext):
    ctx.guild.voice_client.resume()
    await ctx.respond("Resumed", ephemeral=True)


@bot.slash_command()
@discord.guild_only()
async def stop(ctx: discord.ApplicationContext):
    ctx.guild.voice_client.stop()
    await ctx.respond("Stopped", ephemeral=True)


bot.run(TOKEN)

Expected Results

It works like it should and already does in normal voice channels.

Actual Results

Playback does not continue after pausing and resuming.

Intents

None, so default ig

System Information

Win 10 (Also tested on a debian server)
ffmpeg 5.0.1 (but does not seem to be the problem)
Pycord v2.4.1 & 2.5.0
discord.py v2.3.2
python v3.11.1 & 3.12.1

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.
  • I have removed my token from display, if visible.

I just replaced the AudioPlayer._speak function with a single return statement (so it doesn't do anything anymore) and that seems to do the trick. I don't know why but it seems to reliably work. At least for now.
But with this I was able to move the bot, pause, resume, stop and start the playback and it worked. Even when pausing the playback in one channel, then moving the bot to another channel and resuming the playback again.

But the odd thing is, that discord.py does the exact same thing (in the _speak method) and it works there...