diff --git a/examples/Voice/BasicVoice/Audio/BasicAudioPlayer.cs b/examples/Voice/BasicVoice/Audio/BasicAudioPlayer.cs index 09025ffb6..e6e48dc21 100644 --- a/examples/Voice/BasicVoice/Audio/BasicAudioPlayer.cs +++ b/examples/Voice/BasicVoice/Audio/BasicAudioPlayer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Disqord; using Disqord.Bot; @@ -46,35 +47,29 @@ public BasicAudioPlayer( _queue = new(); } - private async Task SendNotificationAsync(AudioSource finishedSource, bool wasReplaced, AudioSource? queuedSource) + /// + /// Invoked when this audio player is stopped. + /// + /// The exception that caused the stop or if no exception occurred. + /// + /// A representing the work. + /// + protected override async ValueTask OnStopped(Exception? exception) { - // Yield, so the code below runs in background. - await Task.Yield(); - - // Below we access metadata on the audio sources. - // This metadata is set in AudioModule. - string? notification = null; - if (finishedSource.TryGetMetadata(AudioMetadataKeys.Title, out var title)) + if (exception == null) { - notification = $"{(wasReplaced ? "Skipped" : "Finished")} playing {Markdown.Code(title)}.\n"; - } - - if (queuedSource != null && queuedSource.TryGetMetadata(AudioMetadataKeys.Title, out title)) - { - notification += $"Now playing {Markdown.Code(title)}."; - } + // If an exception occurred, we'll log it. + Bot.Logger.LogError(exception, "An exception occurred in the audio player for guild ID {GuildId}.", GuildId); - if (notification != null) - { - try - { - var message = new LocalMessage().WithContent(notification.TrimStart()); - await Bot.SendMessageAsync(NotificationsChannelId, message); - } - catch (Exception ex) + // Here you can add different handling for different exceptions that might occur + // but for VoiceConnectionException the logic should always be basically the same. + // VoiceConnectionException indicates that the connection object was rendered unusable + // because, for example, the bot was disconnected from the voice channel. + // We dispose of this audio player allowing for a new one to be created. + if (exception is VoiceConnectionException) { - // If an exception occurred, we'll log it. - Bot.Logger.LogError(ex, "An exception occurred while sending the notification in the audio player for guild ID {GuildId}.", GuildId); + var playerService = Bot.Services.GetRequiredService(); + await playerService.DisposePlayerAsync(GuildId); } } } @@ -91,18 +86,10 @@ private async Task SendNotificationAsync(AudioSource finishedSource, bool wasRep /// protected override ValueTask OnSourceFinished(AudioSource source, bool wasReplaced) { - AudioSource? queuedSource; - lock (_queueLock) - { - if (_queue.TryDequeue(out queuedSource)) - { - // If there's a queued source we start playing it. - TrySetSource(queuedSource); - } - } + var nextSource = PlayNextSource(); // Not awaited so that we don't block the audio playback. - _ = SendNotificationAsync(source, wasReplaced, queuedSource); + _ = SendNotificationAsync(source, wasReplaced, exception: null, nextSource); return default; } @@ -123,9 +110,70 @@ protected override ValueTask OnSourceErrored(AudioSource source, Exception excep Bot.Logger.LogError(exception, "An exception occurred in the audio source '{Title}' ({AudioSourceType}) " + "in the audio player for guild ID {GuildId}.", title ?? "unknown", source.GetType().Name, GuildId); + var nextSource = PlayNextSource(); + + // Not awaited so that we don't block the audio playback. + _ = SendNotificationAsync(source, wasReplaced: false, exception, nextSource); return default; } + private AudioSource? PlayNextSource() + { + lock (_queueLock) + { + if (_queue.TryDequeue(out var queuedSource)) + { + // If there's a queued source we start playing it. + if (TrySetSource(queuedSource)) + { + return queuedSource; + } + } + } + + return null; + } + + private async Task SendNotificationAsync(AudioSource finishedSource, bool wasReplaced, Exception? exception, AudioSource? nextSource) + { + // Yield, so the code below runs in background. + await Task.Yield(); + + // Below we access metadata on the audio sources. + // This metadata is set in AudioModule. + string? notification = null; + if (finishedSource.TryGetMetadata(AudioMetadataKeys.Title, out var title)) + { + if (exception != null) + { + notification += $"An error occurred while playing {Markdown.Code(title)}.\n"; + } + else + { + notification += $"{(wasReplaced ? "Skipped" : "Finished")} playing {Markdown.Code(title)}.\n"; + } + } + + if (nextSource != null && nextSource.TryGetMetadata(AudioMetadataKeys.Title, out title)) + { + notification += $"Now playing {Markdown.Code(title)}."; + } + + if (notification != null) + { + try + { + var message = new LocalMessage().WithContent(notification.TrimStart()); + await Bot.SendMessageAsync(NotificationsChannelId, message); + } + catch (Exception ex) + { + // If an exception occurred, we'll log it. + Bot.Logger.LogError(ex, "An exception occurred while sending the notification in the audio player for guild ID {GuildId}.", GuildId); + } + } + } + /// /// Enqueues the specified audio source. /// @@ -148,31 +196,4 @@ public bool Enqueue(AudioSource source) return false; } - - /// - /// Invoked when this audio player is stopped. - /// - /// The exception that caused the stop or if no exception occurred. - /// - /// A representing the work. - /// - protected override async ValueTask OnStopped(Exception? exception) - { - if (exception != null) - { - // If an exception occurred, we'll log it. - Bot.Logger.LogError(exception, "An exception occurred in the audio player for guild ID {GuildId}.", GuildId); - - // Here you can add different handling for different exceptions that might occur - // but for VoiceConnectionException the logic should always be basically the same. - // VoiceConnectionException indicates that the connection object was rendered unusable - // because, for example, the bot was disconnected from the voice channel. - // We dispose of this audio player allowing for a new one to be created. - if (exception is VoiceConnectionException) - { - var playerService = Bot.Services.GetRequiredService(); - await playerService.DisposePlayerAsync(GuildId); - } - } - } } diff --git a/src/Disqord.Extensions.Voice/Audio/Default/AudioPlayer.cs b/src/Disqord.Extensions.Voice/Audio/Default/AudioPlayer.cs index 8ca5ed981..1d3791007 100644 --- a/src/Disqord.Extensions.Voice/Audio/Default/AudioPlayer.cs +++ b/src/Disqord.Extensions.Voice/Audio/Default/AudioPlayer.cs @@ -344,7 +344,14 @@ private async Task WaitIfPausedAsync(CancellationToken cancellationToken) await connection.SetSpeakingFlagsAsync(SpeakingFlags.None, cancellationToken).ConfigureAwait(false); await OnPaused().ConfigureAwait(false); - await SendSilenceAsync(connection, cancellationToken).ConfigureAwait(false); + try + { + await SendSilenceAsync(connection, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + // TODO: handle exception + } await pauseTask.ConfigureAwait(false); @@ -444,7 +451,14 @@ private async Task ExecuteAsync(CancellationToken cancellationToken) break; } - await connection.SendPacketAsync(packet, linkedCancellationToken).ConfigureAwait(false); + try + { + await connection.SendPacketAsync(packet, linkedCancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + // TODO: handle exception + } } if (continueOuter)