Discord music bot joined the voice channel but plays no sound Discord.py

  discord, discord.py, python-3.x

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:
                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)
                    self.voice = await channel.connect()

                await ctx.send(f"Joined {channel}.")

                await ctx.send("Failed to join the channel with unexpected error.")

            await ctx.send("You must join a voice channel first.")

    # search function
    def yt_search(self, keywords):
        with YoutubeDL(self.YDL_OPTIONS) as ydl:
                info = ydl.extract_info(f"ytsearch:{keywords}", download=False)['entries'][0]
                # 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:
                    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']

                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())
            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.")
                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

                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]

                    # 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)
                        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()

            await ctx.send(f"{ctx.author.name}: Please join a voice channel first!")

Source: Python-3x Questions