1

I created this music bot and I want to improve my queue command because it isn't very functionnal at the moment. Every time that I queue a song I have to use the play command to play it, but I want to play automatically the next song, also it would be cool to implement the queue command into the play command but I've no idea on how to do it. Can you please help??

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' # bind to ipv4 since ipv6 addresses cause issues sometimes
}

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):
        loop = loop or asyncio.get_event_loop()
        data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))

        if 'entries' in data:
            # take first item from a playlist
            data = data['entries'][0]

        filename = data['url'] if stream else ytdl.prepare_filename(data)
        return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)

queue = []


@client.command(name='queue', help='This command adds a song to the queue')
async def queue_(ctx, url):
    global queue

    queue.append(url)
    await ctx.send(f'`{url}` added to queue!')



@client.command(name='play', help='This command plays songs')
async def play(ctx):
    global queue

    server = ctx.message.guild
    voice_channel = server.voice_client

    async with ctx.typing():
        player = await YTDLSource.from_url(queue[0], loop=client.loop, stream=True)
        voice_channel.play(player, after=lambda e: print('Player error: %s' % e) if e else None)

    await ctx.send('**Now playing:** {}'.format(player.title))
    del(queue[0])

Edit: So I tried to do something like this but it doesn't work, when I try to use !play when a song is playing it doesn't put it in the queue, it says that:ClientException: Already playing audio. When the song is finished it says that: TypeError: next() missing 2 required positional arguments: 'queue' and 'song' This is the code:

queue = []

def next(client, queue, song):
    if len(queue)>0:
        new_song= queue[0]
        del queue[0]
        play(client, queue, new_song)

@bot.command()
async def join (ctx):
    member = ctx.author
    if not ctx.message.author.voice:
        await ctx.send(f"{member.mention} You are not connected to a voice channel  ❌")
    else:
        channel=ctx.message.author.voice.channel
        await channel.connect()


@bot.command(help="This command plays a song.")
async def play (ctx,*args):
    server = ctx.message.guild
    voice_channel= server.voice_client
    url=""
    for word in args:
        url+=word
        url+=''

    async with ctx.typing():
        player = await YTDLSource.from_url(url ,loop=bot.loop, stream=True)
        queue.append(player)
        voice_channel.play (player, after=lambda e: next(ctx))
        
    await ctx.send(f"**Now playing:** {player.title}")
  • Does [this](https://stackoverflow.com/a/62107725/12874027) answer your question? – MrSpaar Apr 27 '21 at 18:00
  • You could try an OOP approach and write a Queue class, which would offer more functionality/help. – Xiddoc Apr 27 '21 at 19:21
  • I think it can work but I don't know how to implement it in my code because I actually stream the song without downloading it and also because I'm new to programming, so I don't know what to do... @Mr_Spaar –  Apr 27 '21 at 19:23
  • I don't know how to do, can you help??? @Xiddoc. I've been triying @ Mr_Spaar method but I don't know how to implement it. –  Apr 28 '21 at 12:45

1 Answers1

0

Inside your asynchronous play function, you have the following line of code:

voice_channel.play(player, after=lambda e: print('Player error: %s' % e) if e else None)

As you can see, there is a parameter that can be used to presumably preform an action after the bot is done playing audio to the voice channel. Instead of using it in the way you currently have, I'd recommend that you change the code to recursively play the next song, using the after= parameter. You can do this by changing the current print statement you have in the lambda to asynchronously calling the play function again.

I'd also like to point out that while learning how to use the del() function is great for learning how to optimize code, this isn't exactly the way you want to implement it. In this case, you should instead .pop() the value to get it out of the queue (Like so: queue.pop(0). You should also know that popping the value returns the value, so you can implement it directly into the first parameter of the YTDLSource.from_url() function).

The last nugget of info that I can offer is that you write a class for your queue system. Obviously this won't actually change what you need to do to fix your problem, but learning how to write OOP objects and classes will help you in the future, and it also makes writing code easier since it becomes more organized.

To summarize:

  • Start by removing the del(queue[0]), and instead swap the queue[0] parameter value in the from_url() method with queue.pop(0).
  • Optimally, you should change the whole queue array with a Queue class, since you can add methods and other useful wrapper functions to it very easily, however this isn't a must.
  • Finally, instead of the print statement in the the lambda (in the .play() method's after parameter), asynchronously call the play function again. (For example, like so- lambda e: await play(ctx))
Xiddoc
  • 3,369
  • 3
  • 11
  • 37
  • 1
    When i change the ```voice_channel.play(player, after=lambda e: print('Player error: %s' % e) if e else None)``` to ```voice_channel.play(player, after=lambda e: await play(ctx))``` it gives me a syntax error: ```await outside async function``` –  Apr 28 '21 at 17:26
  • Check out [this](https://stackoverflow.com/a/66330279/11985743). If my answer helped you, make sure to upvote it and mark it as valid (checkmark on the left of the answer)! – Xiddoc Apr 28 '21 at 18:32
  • 1
    I edited my code but I have an error, could you look at it, please??? –  Apr 29 '21 at 11:28