How to get audio clip to play entirely before disconnecting - python-3.x

I have the bot joining and playing the audio but it doesn't seem to be consistent all the time. Sometimes it joins and plays starting halfway through. I'm assuming this has to do with the nature of async. Is there a way to ensure that the bot joins plays the audio then leaves in that order? My code is below
#bot.command(name='play_audio')
async def play_audio(ctx):
if ctx.message.author == client.user:
return
voice = await join(ctx)
voice.play(discord.FFmpegPCMAudio(f"{os.path.dirname(os.path.abspath(__file__))}/audio.mp3"))
await leave(ctx)
#bot.command(name='j')
async def join(ctx):
if ctx.message.author == client.user:
return
channel = ctx.message.author.voice.channel
voice = get(bot.voice_clients, guild=ctx.guild)
if voice and voice.is_connected():
await voice.move_to(channel)
else:
voice = await channel.connect()
print(f"The bot has connected to {channel}\n")
return voice
#bot.command(name='l')
async def leave(ctx):
if ctx.message.author == client.user:
return
channel = ctx.message.author.voice.channel
voice = get(bot.voice_clients, guild=ctx.guild)
if voice and voice.is_connected():
await voice.disconnect()
print(f"The bot has left {channel}\n")
bot.run(TOKEN)

It appears that you are executing leave() before the playing is completed.
You can check is_playing() to wait for the mp3 to complete.
Also, using asyncio.sleep to chill the while loop.
Try adding the following while before you leave:
#bot.command(name='play_audio')
async def play_audio(ctx):
if ctx.message.author == client.user:
return
voice = await join(ctx)
voice.play(discord.FFmpegPCMAudio(f"{os.path.dirname(os.path.abspath(__file__))}/audio.mp3"))
while voice.is_playing():
await sleep(1)
await leave(ctx)

Related

Bot Radio Looping Audio discord py discord bot

I want to play a file over and over again, but I get an error. This is an error :
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'is_playing'
this is my bot discord code :
#client.command()
async def p(ctx):
await ctx.channel.purge(limit=1)
channel = ctx.author.voice.channel
voice = discord.utils.get(client.voice_clients, guild=ctx.guild)
def repeat(guild, voice, audio):
voice.play(audio, after=lambda e: repeat(guild, voice, audio))
voice.is_playing()
if channel and not voice.is_playing():
audio = discord.FFmpegPCMAudio('audio.mp3')
voice.play(audio, after=lambda e: repeat(ctx.guild, voice, audio))
voice.is_playing()
What is the problem?
Try this:
#client.command()
async def p(ctx):
await ctx.channel.purge(limit=1)
voice = ctx.voice_client
if not voice:
return await ctx.send("You are not in a voice channel")
await voice.connect()
def repeat(voice, audio):
voice.play(audio, after=lambda e: repeat(voice, audio))
voice.is_playing()
if voice and not voice.is_playing():
audio = discord.FFmpegPCMAudio('audio.mp3')
voice.play(audio, after=lambda e: repeat(voice, audio))
voice.is_playing()
After looking at your comment, you can make the bot leave and join the voice channel by making a rejoin command, which will leave the voice channel and join it again:
#client.command()
async def rejoin(ctx):
voice = ctx.voice_client
if not voice:
return await ctx.send("You're not connected to a voice channel")
if voice.is_connected():
await voice.disconnect()
await voice.connect()
else:
await voice.connect()

ctx.voice_client.pause() causes bot to leave the voice call rather than to pause the audio

I am trying to figure out why my bot is leaving the voice channel rather than pausing the currently playing audio. Here is my code along with other code I have tried:
#commands.command(brief = "Plays a locally hosted song.")
async def play(self, ctx, *, arg):
await ctx.send(f"Joining voice channel: **{ctx.author.voice.channel}**")
destination = ctx.author.voice.channel
await destination.connect()
await ctx.send(f"Joined voice channel: **{ctx.author.voice.channel}**")
voice = discord.utils.get(self.client.voice_clients, guild=ctx.guild)
voice.play(discord.FFmpegPCMAudio(source=f"./audio/{arg}.flac"))
while voice.is_playing():
await asyncio.sleep(.1)
await voice.disconnect()
#commands.command(brief = "Pauses the currently playing song.")
async def pause(self, ctx):
ctx.voice_client.pause()
I have also tried:
#commands.command(brief = "Pauses the currently playing song.")
async def pause(self, ctx):
voice = discord.utils.get(self.client.voice_clients, guild=ctx.guild)
if voice.is_playing():
voice.pause()
else:
await ctx.send("There is currently no audio playing.")
However, this achieves the same result as before. The bot is also not throwing any errors when it disconnects from the call. Any help would be appreciated.
I found your mistake in the code. In the play command you check if the client is playing and if not it disconnects. This was the only problem. To fix it you just need to change your while loop. You can copy my code(includes explanations) or just edit your one.
Documentation Used:
Discord.py Documentation
Code:
#commands.command(brief="Plays a locally hosted song.")
async def play(self, ctx, *, arg):
await ctx.send(f"Joining voice channel: **{ctx.author.voice.channel}**")
# Replaced Destination with ctx.voice_client, Added part where it checks if its already connected and needs to move to another channel
if ctx.voice_client is None:
await ctx.author.voice.channel.connect()
else:
await ctx.voice_client.move(ctx.author.voice.channel)
await ctx.send(f"Joined voice channel: **{ctx.author.voice.channel}**")
# Replaced voice with ctx.voice_client
ctx.voice_client.play(discord.FFmpegPCMAudio(source=f"./audio/{arg}.flac"))
# Edited Loop because it caused the Bot to disconnect
# Loop now runs if Bot is connected to channel
while ctx.voice_client.is_connected():
# checks if the bot is the only one in the channel
if len(ctx.voice_client.channel.members) == 1:
# disconnects
await ctx.voice_client.disconnect()
break
# checks if client is pause
elif ctx.voice_client.is_paused():
await asyncio.sleep(1)
# Checks if client is playing
elif ctx.voice_client.is_playing():
await asyncio.sleep(1)
# if nothing of the above is fulfilled
else:
# disconnect
await ctx.voice_client.disconnect()
break
#commands.command(brief="Pauses the currently playing song.")
async def pause(self, ctx):
# Checks if music is playing and pauses it, otherwise sends the player a message that nothing is playing
try:
ctx.voice_client.pause()
except:
await ctx.send(f"{ctx.author.mention} i'm not playing music at the moment!")
#commands.command(brief="Resumes the paused song.")
async def resume(self, ctx):
# Checks if music is paused and resumes it, otherwise sends the player a message that nothing is playing
try:
ctx.voice_client.resume()
except:
await ctx.send(f"{ctx.author.mention} i'm not playing music at the moment!")

How to make the discord bot queue local mp3?

I'm new to python, so I wonder if there's a way to do so.
Here's my play mp3 command:
#bot.command()
async def play_song1(ctx):
global voice
channel = ctx.message.author.voice.channel
voice = get(bot.voice_clients, guild=ctx.guild)
if voice and voice.is_connected():
await voice.move_to(channel)
else:
voice = await channel.connect()
voice.play(discord.FFmpegPCMAudio('./mp3/song1.mp3'))
voice.source = discord.PCMVolumeTransformer(voice.source)
voice.source.volume = 0.1
await ctx.send ('playing')
while voice.is_playing():
await asyncio.sleep(.1)
await voice.disconnect()
I made 2 more same commands but for song2 and song3, and now I want to queue the mp3 when someone calls them.
Seen as if you're not using cogs, you could try something like this:
guild_queues = {} # Multi-server support as a dict, just use a list for one server
# EDIT: Map song names in a dict
play_messages = {
"song1": "Now playing something cool!",
"song2": "And something even cooler just started playing!",
"song3": "THE COOLEST!"
}
async def handle_queue(ctx, song):
voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
channel = ctx.author.voice.channel
if voice and channel and voice.channel != channel:
await voice.move_to(channel)
elif not voice and channel:
voice = await channel.connect()
if not voice.is_playing():
audio = discord.FFmpegPCMAudio(f"./{song}.mp3")
source = discord.PCMVolumeTransformer(audio)
source.volume = 0.1
voice.play(audio)
await ctx.send(play_messages[song])
while voice.is_playing():
await asyncio.sleep(.1)
if len(guild_queues[ctx.guild.id]) > 0:
next_song = guild_queues[ctx.guild.id].pop(0)
await handle_queue(ctx, next_song)
else:
await voice.disconnect()
#bot.command()
async def play(ctx, *, song): # I'd recommend adding the filenames as an arg, but it's up to you
# Feel free to add a check if the filename exists
try:
# Get the current guild's queue
queue = guild_queues[ctx.guild.id]
except KeyError:
# Create a queue if it doesn't already exist
guild_queues[ctx.guild.id] = []
queue = guild_queues[ctx.guild.id]
voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
queue.append(song)
# The one song would be the one currently playing
if voice and len(queue) > 0:
await ctx.send("Added to queue!")
else:
current_song = queue.pop(0)
await handle_queue(ctx, current_song)
References:
utils.get()
Client.voice_clients
Context.guild
Member.voice
VoiceState.channel
VoiceClient.move_to()
VoiceClient.is_playing()
VoiceClient.play()
discord.FFmpegPCMAudio()
discord.PCMVolumeTransformer()
Context.send()

discord.py stop command work but doesn't clear the queue

-----Play Section this works well-----
async def play(self, ctx, *, arg):
await ctx.channel.purge(limit=1)
try: channel = ctx.author.voice.channel
except: await ctx.send("❌ You're not connected to any channel!", delete_after = 5.0)
else:
channel = ctx.author.voice.channel
voice = get(self.bot.voice_clients, guild=ctx.guild)
song = Music.search(ctx.author.mention, arg)
------queue section------
if voice and voice.is_connected():
await voice.move_to(channel)
else:
voice = await channel.connect()
if not voice.is_playing():
self.song_queue[ctx.guild] = [song]
self.message[ctx.guild] = await ctx.send(embed=song['embed'])
voice.play(discord.FFmpegPCMAudio(song['source'], **Music.FFMPEG_OPTIONS), after=lambda e: self.play_next(ctx))
voice.is_playing()
else:
self.song_queue[ctx.guild].append(song)
await self.edit_message(ctx)
----stop section----
only skips music and don't clear queue
i don't know what is wrong here
#commands.command(brief='$stop')
async def stop(self, ctx):
voice = get(self.bot.voice_clients, guild=ctx.guild)
channel = ctx.message.author.voice.channel
await ctx.channel.purge(limit=1)
if voice and voice.is_playing():
await ctx.send('⛔ Music Stopped', delete_after = 5.0)
voice.stop(ctx.guild)
else:
await ctx.send("❌ I'm not playing any songs!", delete_after = 5.0)

How to Make a Discord Music Bot Using Discord.py Cogs

My Current Project
I'm trying to convert a Python music bot I made off a YouTube video into a cog, so my main Python Discord bot file isn't so cluttered.
My Problem
The code below I wrote isn't working. I read the docs on cogs, but I can't seem to find what I did wrong.
Here's the code I tried making, but it
import discord
from discord.ext import commands
from discord.utils import get
import youtube_dl
import os
from time import sleep
rpgmusicpath = r"path\to\music.mp3"
class Music(commands.Cog):
def __init__(self, client):
self.bot = client
#commands.Cog.listener()
async def on_ready(self):
print('Music cog successfully loaded.')
#commands.command(pass_context=True)
async def rpgmusic(ctx, self):
global voice
channel = ctx.message.author.voice.channel
voice = get(bot.voice_clients, guild=ctx.guild)
if voice and voice.is_connected():
await voice.move_to(channel)
else:
voice = await channel.connect()
print(f'Bot connected to voice channel {channel}\n')
await ctx.send(f'Playing some RPG music in {channel}.')
sleep(3)
voice.play(discord.FFmpegPCMAudio('rpgmusic.mp3'), after=lambda e: print(f'RPG music in {channel} has finished playing.'))
voice.source = discord.PCMVolumeTransformer(voice.source)
voice.source.volume = 0.05
#commands.command(pass_context=True)
async def join(ctx, self):
global voice
channel = ctx.message.author.voice.channel
voice = get(bot.voice_clients, guild=ctx.guild)
if voice and voice.is_connected():
await voice.move_to(channel)
else:
voice = await channel.connect()
print(f'Bot connected to voice channel {channel}\n')
await ctx.send(f'I joined {channel}.')
#commands.command(pass_context=True)
async def leave(ctx, self):
channel = ctx.message.author.voice.channel
voice = get(bot.voice_clients, guild=ctx.guild)
if voice and voice.is_connected():
await voice.disconnect()
print(f'Bot disconnected from channel {channel}.')
else:
print('Not able to disconnect to a voice channel because bot wasn\'t in one.')
#commands.command(pass_context=True)
async def play(ctx, url: str, self):
song_there = os.path.isfile('song.mp3')
try:
if song_there:
os.remove('song.mp3')
print('Removed current song.')
except PermissionError:
print('Error in deleting song file. (Song in use.)')
await ctx.send('Unable to request song. (Song already in use.)')
return
await ctx.send('Preparing song. Please wait.')
voice = get(bot.voice_clients, guild=ctx.guild)
ydl_opts = {
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
print('Downloading audio now.\n')
ydl.download([url])
for file in os.listdir('./'):
if file.endswith('.mp3'):
name = file
print(f'Renamed File: {file}.')
os.rename(file, 'song.mp3')
voice.play(discord.FFmpegPCMAudio('song.mp3'), after=lambda e: print(f'{name} has finished playing.'))
voice.source = discord.PCMVolumeTransformer(voice.source)
voice.source.volume = 0.06
nname = name.rsplit('-', 2)
await ctx.send(f'Now playing {name}.')
print('Now playing.\n')
def setup(bot):
bot.add_cog(Music(bot))
Two problems :
You didn't replaced bot by self.bot.
In rpgmusic, join, leave and play commands, change :
voice = get(bot.voice_clients, guild=ctx.guild)
To :
voice = get(self.bot.voice_clients, guild=ctx.guild)
Your first argument must be self, and not ctx:
#commands.command(pass_context=True)
async def rpgmusic(self, ctx)
#commands.command(pass_context=True)
async def join(self, ctx)
#commands.command(pass_context=True)
async def play(self, ctx)
Also, since you have a join function, you can await it in rpgmusic (you also don't need global voice):
#commands.command(pass_context=True)
async def rpgmusic(ctx, self):
await self.join(ctx)
await ctx.send(f'Playing some RPG music in {channel}.')
sleep(3)
voice.play(discord.FFmpegPCMAudio('rpgmusic.mp3'), after=lambda e: print(f'RPG music in {channel} has finished playing.'))
voice.source = discord.PCMVolumeTransformer(voice.source)
voice.source.volume = 0.05

Resources