Skip to content

Commit

Permalink
Pause resume updates (#2381)
Browse files Browse the repository at this point in the history
* Auto unpause when play-like commands are used.
Clean up the old auto pause code (cause I've learned since then)

* clean up a bit more, ensure things work.

* Flake8

* actually import typing.

* Quote the custom type

* run black on dev merge.  grumble grumble

* correct the function comment.
  • Loading branch information
itsTheFae committed Jan 7, 2024
1 parent 13484ce commit eb26ab9
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 139 deletions.
210 changes: 71 additions & 139 deletions musicbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
ftimedelta,
_func_,
_get_variable,
is_empty_voice_channel,
format_song_duration,
format_size_from_bytes,
)
Expand Down Expand Up @@ -324,15 +325,6 @@ async def _join_startup_channels(self, channels, *, autosummon=True):
joined_servers = set()
channel_map = {c.guild: c for c in channels}

def _autopause(player):
if self._check_if_empty(player.voice_client.channel):
log.info("Initial autopause in empty channel")

player.pause()
self.server_specific_data[player.voice_client.channel.guild.id][
"auto_paused"
] = True

for guild in self.guilds:
if guild.unavailable or guild in channel_map:
continue
Expand Down Expand Up @@ -391,8 +383,6 @@ def _autopause(player):
player.play()

if self.config.auto_playlist:
if self.config.auto_pause:
player.once("play", lambda player, **_: _autopause(player))
if not player.playlist.entries:
await self.on_player_finished_playing(player)

Expand Down Expand Up @@ -628,6 +618,7 @@ def _init_player(self, player, *, guild=None):

async def on_player_play(self, player, entry):
log.debug("Running on_player_play")
self._handle_guild_auto_pause(player)
await self.reset_player_inactivity(player)
await self.update_now_playing_status()
# manage the cache since we may have downloaded something.
Expand Down Expand Up @@ -777,15 +768,6 @@ async def on_player_finished_playing(self, player, **_):
if last_np_msg:
await self.safe_delete_message(last_np_msg)

def _autopause(player):
if self._check_if_empty(player.voice_client.channel):
log.info("Player finished playing, autopaused in empty channel")

player.pause()
self.server_specific_data[player.voice_client.channel.guild.id][
"auto_paused"
] = True

if (
not player.playlist.entries
and not player.current_entry
Expand Down Expand Up @@ -852,9 +834,6 @@ def _autopause(player):
# Do I check the initial conditions again?
# not (not player.playlist.entries and not player.current_entry and self.config.auto_playlist)

if self.config.auto_pause:
player.once("play", lambda player, **_: _autopause(player))

try:
await player.playlist.add_entry(
song_url, channel=None, author=None, head=False
Expand Down Expand Up @@ -1967,6 +1946,52 @@ async def _do_playlist_checks(self, permissions, player, author, testobj):
)
return True

def _handle_guild_auto_pause(self, player: MusicPlayer):
"""
function to handle guild activity pausing and unpausing.
"""
if not self.config.auto_pause:
return

if not player.voice_client:
return

channel = player.voice_client.channel

guild = channel.guild
auto_paused = self.server_specific_data[guild.id]["auto_paused"]

if is_empty_voice_channel(channel) and not auto_paused and player.is_playing:
log.info(
f"Playing in an empty voice channel, running auto pause for guild: {guild}"
)
player.pause()
self.server_specific_data[guild.id]["auto_paused"] = True

elif not is_empty_voice_channel(channel) and auto_paused and player.is_paused:
log.info(f"Previously auto paused player is unpausing for guild: {guild}")
player.resume()
self.server_specific_data[guild.id]["auto_paused"] = False

async def _do_cmd_unpause_check(
self, player: MusicPlayer, channel: discord.abc.GuildChannel
):
"""
Checks for paused player and resumes it while sending a notice.
This function should not be called from _cmd_play().
"""
if player and player.is_paused:
await self.safe_send_message(
channel,
self.str.get(
"cmd-unpause-check",
"Bot was previously paused, resuming playback now.",
),
expire_in=30,
)
player.resume()

async def cmd_play(
self, message, _player, channel, author, permissions, leftover_args, song_url
):
Expand All @@ -1983,6 +2008,7 @@ async def cmd_play(
it will use the metadata (e.g song name and artist) to find a YouTube
equivalent of the song. Streaming from Spotify is not possible.
"""
await self._do_cmd_unpause_check(_player, channel)

return await self._cmd_play(
message,
Expand All @@ -2004,6 +2030,8 @@ async def cmd_shuffleplay(
Adds a playlist to be shhuffled then played. Shorthand for doing {command_prefix}play and then {command_prefix}shuffle
If nothing is playing, then the first few songs will be played in order while the rest of the playlist downloads.
"""
await self._do_cmd_unpause_check(_player, channel)

# TO-DO, don't play the first few songs so the entire playlist can be shuffled
# In my test run it's only 2-3 songs that get played in the order this is because of how the _cmd_play works.
player = self.get_player_in(channel.guild)
Expand Down Expand Up @@ -2043,6 +2071,7 @@ async def cmd_playnext(
it will use the metadata (e.g song name and artist) to find a YouTube
equivalent of the song. Streaming from Spotify is not possible.
"""
await self._do_cmd_unpause_check(_player, channel)

return await self._cmd_play(
message,
Expand Down Expand Up @@ -2889,6 +2918,8 @@ async def cmd_stream(self, _player, channel, author, permissions, song_url):
streams, especially on poor connections. You have been warned.
"""

await self._do_cmd_unpause_check(_player, channel)

if _player:
player = _player
elif permissions.summonplay:
Expand Down Expand Up @@ -4997,7 +5028,12 @@ async def on_inactivity_timeout_expired(self, voice_channel):
)
await self.disconnect_voice_client(guild)

async def on_voice_state_update(self, member, before, after):
async def on_voice_state_update(
self,
member: discord.Member,
before: discord.VoiceState,
after: discord.VoiceState,
):
if not self.init_ok:
return # Ignore stuff before ready

Expand Down Expand Up @@ -5041,127 +5077,20 @@ async def on_voice_state_update(self, member, before, after):
)
event.set()

if before.channel:
channel = before.channel
elif after.channel:
channel = after.channel
else:
return

if (
member == self.user and not after.channel
): # if bot was disconnected from channel
await self.disconnect_voice_client(before.channel.guild)
return

if not self.config.auto_pause:
return

autopause_msg = "{state} in {channel.guild.name}/{channel.name} {reason}"

auto_paused = self.server_specific_data[channel.guild.id]["auto_paused"]

try:
player = await self.get_player(channel)
except exceptions.CommandError:
return

def is_active(member):
if not member.voice:
return False

if any([member.voice.deaf, member.voice.self_deaf, member.bot]):
return False

return True

if not member == self.user and is_active(member): # if the user is not inactive
if (
player.voice_client.channel != before.channel
and player.voice_client.channel == after.channel
): # if the person joined
if auto_paused and player.is_paused:
log.info(
autopause_msg.format(
state="Unpausing",
channel=player.voice_client.channel,
reason="",
).strip()
)

self.server_specific_data[player.voice_client.guild.id][
"auto_paused"
] = False
player.resume()
elif (
player.voice_client.channel == before.channel
and player.voice_client.channel != after.channel
):
if not any(
is_active(m) for m in player.voice_client.channel.members
): # channel is empty
if not auto_paused and player.is_playing:
log.info(
autopause_msg.format(
state="Pausing",
channel=player.voice_client.channel,
reason="(empty channel)",
).strip()
)

self.server_specific_data[player.voice_client.guild.id][
"auto_paused"
] = True
player.pause()
elif (
player.voice_client.channel == before.channel
and player.voice_client.channel == after.channel
): # if the person undeafen
if auto_paused and player.is_paused:
log.info(
autopause_msg.format(
state="Unpausing",
channel=player.voice_client.channel,
reason="(member undeafen)",
).strip()
)

self.server_specific_data[player.voice_client.guild.id][
"auto_paused"
] = False
player.resume()
else:
if any(
is_active(m) for m in player.voice_client.channel.members
): # channel is not empty
if auto_paused and player.is_paused:
log.info(
autopause_msg.format(
state="Unpausing",
channel=player.voice_client.channel,
reason="",
).strip()
)

self.server_specific_data[player.voice_client.guild.id][
"auto_paused"
] = False
player.resume()

else:
if not auto_paused and player.is_playing:
log.info(
autopause_msg.format(
state="Pausing",
channel=player.voice_client.channel,
reason="(empty channel or member deafened)",
).strip()
)

self.server_specific_data[player.voice_client.guild.id][
"auto_paused"
] = True
player.pause()
if before.channel:
player = self.get_player_in(before.channel.guild)
if player:
self._handle_guild_auto_pause(player)
if after.channel:
player = self.get_player_in(after.channel.guild)
if player:
self._handle_guild_auto_pause(player)

async def on_guild_update(self, before: discord.Guild, after: discord.Guild):
if before.region != after.region:
Expand Down Expand Up @@ -5217,6 +5146,9 @@ async def on_guild_available(self, guild: discord.Guild):
player.resume()

async def on_guild_unavailable(self, guild: discord.Guild):
if not self.init_ok:
return # Ignore pre-ready events.

log.debug('Guild "{}" has become unavailable.'.format(guild.name))

player = self.get_player_in(guild)
Expand Down
26 changes: 26 additions & 0 deletions musicbot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import logging
import aiohttp
import inspect
import typing

from typing import Union
from hashlib import md5
from .constants import DISCORD_MSG_CHAR_LIMIT

if typing.TYPE_CHECKING:
from discord import VoiceChannel

log = logging.getLogger(__name__)


Expand Down Expand Up @@ -173,6 +177,28 @@ def _get_variable(name):
del stack


def is_empty_voice_channel(
voice_channel: "VoiceChannel", *, exclude_me: bool = True, exclude_deaf: bool = True
) -> bool:
def _check(member):
if exclude_me and member == voice_channel.guild.me:
return False

if (
member.voice
and exclude_deaf
and any([member.voice.deaf, member.voice.self_deaf])
):
return False

if member.bot:
return False

return True

return not sum(1 for m in voice_channel.members if _check(m))


def format_song_duration(ftd):
duration_array = ftd.split(":")
return (
Expand Down

0 comments on commit eb26ab9

Please sign in to comment.