1
votes

Hey guys,

I have two little problems I dont know how to solve.

What do I want to do?

I want to create a pretty simple music bot with a few functions.

  1. Play music from a URL
  2. Play music from a Title
  3. Play music from a Queue

Now my problems:

Everytime I use

?play TITLE or URL

it skips the currently playing song - I want the bot to wait until the song is finished

and my second problem is pretty much the same - I want the bot to play a queue and wait until every song is finished - now he loops through the list of songs and only plays the first :(

What does my code look like:

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

    @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:

            async with ctx.typing():
                player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
                ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None)

            await ctx.send(f':mag_right: **Searching for** ``' + url + '``\n<:youtube:763374159567781890> **Now Playing:** ``{}'.format(player.title) + "``")

        except:

            await ctx.send("Somenthing went wrong - please try again later!")

    @commands.command()
    async def play_queue(self, ctx):

        for url in queue:

            try:

                async with ctx.typing():
                    player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
                    ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None)

                await ctx.send(f'<:youtube:763374159567781890> **Now Playing:** ``{url}``')

            except:

                await ctx.send("Somenthing went wrong - please try again later!")
            
        else:

            await ctx.send("Queue is now done!")

    @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.")
        elif ctx.voice_client.is_playing():
            ctx.voice_client.stop()

def setup(client):
    client.add_cog(Music(client))

Thanks for the help

1
I just want to say that you really described your issue very well, and everything is really well organized, nice job! - TheSuperRobert

1 Answers

0
votes

So I think what you need to do is to split the play and play_queue functions completely into a play and start_playing functions.

What I mean by this is currently your play command finds a song and plays it, when what it should be doing is finding a song and appending it to a queue, so the process should look something like this:

  1. Someone types play 'song'
  2. The bot checks if no songs are playing if len(self.queue) == 0: start_playing(song), if so it calls a function which starts playing the song, if there are songs playing it adds it to to the queue, the way I would suggest doing this would be to give each song an id, self.queue[len(self.queue)] = song
  3. Then if someone uses the play 'song' command again the song is added to the queue and played once the first song is finished

Essentially I'm suggesting the play_queue command be removed, the play to be turned into a play if no songs are playing otherwise add to queue command, and a function which is called when no songs are playing

The implementation of self.queue might look like this:

@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
        self.queue = {}
        await ctx.send(f'Connected to ``{channel}``')

    await channel.connect()

@commands.command()
async def play(self, ctx, *, url):

    try:

        async with ctx.typing():
            player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)

            if len(self.queue) == 0:

                self.start_playing(ctx.voice_client, player)
                await ctx.send(f':mag_right: **Searching for** ``' + url + '``\n<:youtube:763374159567781890> **Now Playing:** ``{}'.format(player.title) + "``")

            else:
                
                self.queue[len(self.queue)] = player
                await ctx.send(f':mag_right: **Searching for** ``' + url + '``\n<:youtube:763374159567781890> **Added to queue:** ``{}'.format(player.title) + "``")

    except:

        await ctx.send("Somenthing went wrong - please try again later!")

def start_playing(self, voice_client, player):

    self.queue[0] = player

    i = 0
    while i <  len(self.queue):
        try:
            voice_client.play(self.queue[i], after=lambda e: print('Player error: %s' % e) if e else None)

        except:
            pass
        i += 1

This should solve both the play and play queue problems