Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Audio] Better error handling after multiple subsequential errors #3165

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/audio/3165.bugfix.1.rst
@@ -0,0 +1 @@
Fixed an error that was thrown when running ``[p]audioset dj``.
1 change: 1 addition & 0 deletions changelog.d/audio/3165.enhance.1.rst
@@ -0,0 +1 @@
Better error handling the player is unable to play multiple tracks in sequence.
202 changes: 127 additions & 75 deletions redbot/cogs/audio/audio.py
Expand Up @@ -5,12 +5,11 @@
import heapq
import json
import logging
import os
import random
import re
import time
import traceback
from collections import namedtuple
from collections import namedtuple, Counter
from io import StringIO
from typing import List, Optional, Tuple, Union, cast

Expand Down Expand Up @@ -98,7 +97,7 @@ def __init__(self, bot):
status=False,
use_external_lavalink=False,
restrict=True,
current_version=redbot.core.VersionInfo.from_str("3.0.0a0").to_json(),
current_version=redbot.core.VersionInfo.from_str(__version__).to_json(),
Drapersniper marked this conversation as resolved.
Show resolved Hide resolved
localpath=str(cog_data_path(raw_name="Audio")),
**self._default_lavalink_settings,
)
Expand Down Expand Up @@ -139,6 +138,9 @@ def __init__(self, bot):
self.config.register_guild(**default_guild)
self.config.register_global(**default_global)
self.music_cache = MusicCache(bot, self.session, path=str(cog_data_path(raw_name="Audio")))
self._error_counter = Counter()
self._error_timer = {}
self._disconnected_players = {}
self.play_lock = {}

self._manager: Optional[ServerManager] = None
Expand Down Expand Up @@ -367,6 +369,62 @@ async def attempt_connect(self, timeout: int = 50):
"tracebacks for details."
)

async def error_reset(self, player: lavalink.Player):
guild = player.channel.guild
now = time.time()
seconds_allowed = 10
last_error = self._error_timer.setdefault(guild.id, now)
if now - seconds_allowed > last_error:
self._error_timer[guild.id] = 0
self._error_counter[guild.id] = 0

async def increase_error_counter(self, player: lavalink.Player) -> bool:
guild = player.channel.guild
now = time.time()
self._error_counter[guild.id] += 1
self._error_timer[guild.id] = now
return self._error_counter[guild.id] >= 5

@staticmethod
async def _players_check():
try:
get_single_title = lavalink.active_players()[0].current.title
query = audio_dataclasses.Query.process_input(lavalink.active_players()[0].current.uri)
if get_single_title == "Unknown title":
get_single_title = lavalink.active_players()[0].current.uri
if not get_single_title.startswith("http"):
get_single_title = get_single_title.rsplit("/", 1)[-1]
elif query.is_local:
get_single_title = "{} - {}".format(
lavalink.active_players()[0].current.author,
lavalink.active_players()[0].current.title,
)
else:
get_single_title = lavalink.active_players()[0].current.title
playing_servers = len(lavalink.active_players())
except IndexError:
get_single_title = None
playing_servers = 0
return get_single_title, playing_servers

async def _status_check(self, playing_servers):
if playing_servers == 0:
await self.bot.change_presence(activity=None)
if playing_servers == 1:
single_title = await self._players_check()
await self.bot.change_presence(
activity=discord.Activity(
name=single_title[0], type=discord.ActivityType.listening
)
)
if playing_servers > 1:
await self.bot.change_presence(
activity=discord.Activity(
name=_("music in {} servers").format(playing_servers),
type=discord.ActivityType.playing,
)
)

async def event_handler(
self, player: lavalink.Player, event_type: lavalink.LavalinkEvents, extra
):
Expand All @@ -376,46 +434,7 @@ async def event_handler(
status = await self.config.status()
repeat = await self.config.guild(player.channel.guild).repeat()

async def _players_check():
try:
get_single_title = lavalink.active_players()[0].current.title
query = audio_dataclasses.Query.process_input(
lavalink.active_players()[0].current.uri
)
if get_single_title == "Unknown title":
get_single_title = lavalink.active_players()[0].current.uri
if not get_single_title.startswith("http"):
get_single_title = get_single_title.rsplit("/", 1)[-1]
elif query.is_local:
get_single_title = "{} - {}".format(
lavalink.active_players()[0].current.author,
lavalink.active_players()[0].current.title,
)
else:
get_single_title = lavalink.active_players()[0].current.title
playing_servers = len(lavalink.active_players())
except IndexError:
get_single_title = None
playing_servers = 0
return get_single_title, playing_servers

async def _status_check(playing_servers):
if playing_servers == 0:
await self.bot.change_presence(activity=None)
if playing_servers == 1:
single_title = await _players_check()
await self.bot.change_presence(
activity=discord.Activity(
name=single_title[0], type=discord.ActivityType.listening
)
)
if playing_servers > 1:
await self.bot.change_presence(
activity=discord.Activity(
name=_("music in {} servers").format(playing_servers),
type=discord.ActivityType.playing,
)
)
await self.error_reset(player)

if event_type == lavalink.LavalinkEvents.TRACK_START:
self.skip_votes[player.channel.guild] = []
Expand Down Expand Up @@ -516,16 +535,16 @@ async def _status_check(playing_servers):
player.store("notify_message", notify_message)

if event_type == lavalink.LavalinkEvents.TRACK_START and status:
player_check = await _players_check()
await _status_check(player_check[1])
player_check = await self._players_check()
await self._status_check(player_check[1])

if event_type == lavalink.LavalinkEvents.TRACK_END and status:
await asyncio.sleep(1)
if not player.is_playing:
player_check = await _players_check()
await _status_check(player_check[1])
player_check = await self._players_check()
await self._status_check(player_check[1])

if event_type == lavalink.LavalinkEvents.QUEUE_END and notify and not autoplay:
if not autoplay and event_type == lavalink.LavalinkEvents.QUEUE_END and notify:
notify_channel = player.fetch("channel")
if notify_channel:
notify_channel = self.bot.get_channel(notify_channel)
Expand All @@ -535,44 +554,78 @@ async def _status_check(playing_servers):
)
await notify_channel.send(embed=embed)

elif event_type == lavalink.LavalinkEvents.QUEUE_END and disconnect and not autoplay:
self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild)
elif not autoplay and event_type == lavalink.LavalinkEvents.QUEUE_END and disconnect:
await player.disconnect()
self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild)

if event_type == lavalink.LavalinkEvents.QUEUE_END and status:
player_check = await _players_check()
await _status_check(player_check[1])
player_check = await self._players_check()
await self._status_check(player_check[1])

if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION:
message_channel = player.fetch("channel")
if message_channel:
message_channel = self.bot.get_channel(message_channel)
query = audio_dataclasses.Query.process_input(player.current.uri)
if player.current and query.is_local:
query = audio_dataclasses.Query.process_input(player.current.uri)
if player.current.title == "Unknown title":
description = "{}".format(query.track.to_string_hidden())
else:
song = bold("{} - {}").format(player.current.author, player.current.title)
description = "{}\n{}".format(song, query.track.to_string_hidden())
else:
description = bold("[{}]({})").format(player.current.title, player.current.uri)

embed = discord.Embed(
colour=(await self.bot.get_embed_color(message_channel)),
title=_("Track Error"),
description="{}\n{}".format(extra, description),
)
embed.set_footer(text=_("Skipping..."))
await message_channel.send(embed=embed)
while True:
if player.current in player.queue:
player.queue.remove(player.current)
else:
break
if repeat:
player.current = None
await player.skip()
self._error_counter.setdefault(player.channel.guild.id, 0)
if player.channel.guild.id not in self._error_counter:
self._error_counter[player.channel.guild.id] = 0
early_exit = await self.increase_error_counter(player)
if early_exit:
self._disconnected_players[player.channel.guild.id] = True
self.play_lock[player.channel.guild.id] = False
eq = player.fetch("eq")
player.queue = []
player.store("playing_song", None)
if eq:
await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set(
eq.bands
)
await player.stop()
await player.disconnect()
self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild)
if message_channel:
message_channel = self.bot.get_channel(message_channel)
if early_exit:
embed = discord.Embed(
colour=(await self.bot.get_embed_color(message_channel)),
title=_("Multiple errors detected"),
description=_(
"Closing the audio player "
"due to multiple errors being detected. "
"If this persists, please inform the bot owner "
"as the Audio cog may be temporally unavailable."
),
)
await message_channel.send(embed=embed)
else:
query = audio_dataclasses.Query.process_input(player.current.uri)
if player.current and query.is_local:
query = audio_dataclasses.Query.process_input(player.current.uri)
if player.current.title == "Unknown title":
description = "{}".format(query.track.to_string_hidden())
else:
song = bold("{} - {}").format(
player.current.author, player.current.title
)
description = "{}\n{}".format(song, query.track.to_string_hidden())
else:
description = bold("[{}]({})").format(
player.current.title, player.current.uri
)

embed = discord.Embed(
colour=(await self.bot.get_embed_color(message_channel)),
title=_("Track Error"),
description="{}\n{}".format(extra, description),
)
embed.set_footer(text=_("Skipping..."))
await message_channel.send(embed=embed)
await player.skip()

async def play_query(
self,
Expand Down Expand Up @@ -984,7 +1037,7 @@ async def dj(self, ctx: commands.Context):
try:
pred = MessagePredicate.valid_role(ctx)
await ctx.bot.wait_for("message", timeout=15.0, check=pred)
await ctx.invoke(self.role, pred.result)
await ctx.invoke(self.role, role_name=pred.result)
except asyncio.TimeoutError:
return await self._embed_msg(ctx, _("Response timed out, try again later."))

Expand Down Expand Up @@ -6020,7 +6073,6 @@ async def _build_search_page(ctx: commands.Context, tracks, page_num):
search_track_num, track.title, track.uri
)
except AttributeError:
# query = Query.process_input(track)
track = audio_dataclasses.Query.process_input(track)
if track.is_local and command != "search":
search_list += "`{}.` **{}**\n".format(
Expand Down