I am trying to create a discord music bot that plays music. And the bot with the code below doesn't play the music at all (but join successfully).
The code for extracting info and getting the URL by using youtube_dl is correct I think. I can open the URL it extracts and it's the correct song that I want to play.
But when it runs to the line:
self.voice.play(discord.FFmpegPCMAudio(yt_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
It seems the bot stuck there and plays no sound at all.
By the way, the bot also shows voice activity when I only do join
command, so this is also weird.
Here is all my code related to this:
@commands.command(description="Join the channel", aliases=['j'])
async def join(self, ctx):
if ctx.message.author.voice is not None:
try:
channel = ctx.message.author.voice.channel
self.voice = ctx.guild.voice_client
if self.voice and self.voice.is_connected(): # User in voice channel & Bot also in voice channel somewhere else maybe.
await self.voice.move_to(channel)
else:
self.voice = await channel.connect()
await ctx.send(f"Joined {channel}.")
except:
await ctx.send("Failed to join the channel with unexpected error.")
else:
await ctx.send("You must join a voice channel first.")
# search function
def yt_search(self, keywords):
with YoutubeDL(self.YDL_OPTIONS) as ydl:
try:
info = ydl.extract_info(f"ytsearch:{keywords}", download=False)['entries'][0]
print(info)
# print(info['entries'][0])
# return 251/250/249/140/171 otherwise the first one
all_options = info['formats'] # all dicts
# create a dict for high quality url if available
high_quality_dict = {}
for option in all_options:
print(option)
if option['format_id'] == '249' or option['format_id'] == '250' or option['format_id'] == '251' or \
option['format_id'] == '140' or option['format_id'] == '171':
high_quality_dict[option['format_id']] = option['url'] # throw it into dictionary
# check the high quality one
if '251' in high_quality_dict:
return high_quality_dict['251']
elif '250' in high_quality_dict:
return high_quality_dict['250']
elif '249' in high_quality_dict:
return high_quality_dict['249']
elif '140' in high_quality_dict:
return high_quality_dict['140']
elif '171' in high_quality_dict:
return high_quality_dict['171']
else: # if no high quality
return all_options[0]['url']
except:
return False
# play next func
def play_next(self):
with open('resources/server_playlist.json', 'r') as f:
current_playlist = json.load(f)
current_playlist[self.str_guild_id].pop(0) # pop the one just played
if len(current_playlist[self.str_guild_id]) > 0:
yt_url = current_playlist[self.str_guild_id][0]
self.voice.play(discord.FFmpegPCMAudio(yt_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
else:
self.is_playing = False
# play command
@commands.command(description="Play music from youtube", aliases=['p', 'listen'])
async def play(self, ctx, *, args):
search_words = " ".join(args)
if ctx.author.voice is not None:
song = self.yt_search(search_words)
if song is False: # failed to achieve url in the search step
await ctx.send("Failed to load the song. Unexpected error.")
else:
self.str_guild_id = str(ctx.guild.id)
with open('resources/server_playlist.json', 'r') as f:
server_playlist = json.load(f)
# check whether server_id is in the json
if self.str_guild_id not in server_playlist:
server_playlist[self.str_guild_id] = list()
# add the songs into queue
server_playlist[self.str_guild_id].append(song)
with open('resources/server_playlist.json', 'w') as f:
json.dump(server_playlist, f, indent=4)
# check if the bot currently is playing then connect to vc.
if self.is_playing is False:
yt_url = server_playlist[self.str_guild_id][0]
print(yt_url)
# join voice channel
self.voice = ctx.guild.voice_client
self.channel = ctx.author.voice.channel
if self.voice and self.voice.is_connected(): # User in voice channel & Bot also in voice channel somewhere else maybe.
await self.voice.move_to(self.channel)
else:
self.voice = await self.channel.connect()
self.is_playing = True
print("before playing")
# play the music (**Having Problem!!!**)
self.voice.play(discord.FFmpegPCMAudio(yt_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
# self.voice.is_playing()
else:
await ctx.send(f"{ctx.author.name}: Please join a voice channel first!")
After asking people from discord.py discord server, I should include the ffmpeg installed for windows though I set download=False
.
And I also need to add the executable=<ffmpeg path>
in the self.voice.play
line of code. (my path is C:/ffmpeg/ffmpeg.exe)
So, it should be self.voice.play(discord.FFmpegPCMAudio(executable="C:/ffmpeg/ffmpeg.exe", source=yt_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())