diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/NadekoBot/Modules/Music/Classes/MusicControls.cs index 146ca5ea..2f0082ee 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -2,7 +2,9 @@ using Discord.Audio; using NadekoBot.Extensions; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; namespace NadekoBot.Modules.Music.Classes @@ -52,6 +54,8 @@ namespace NadekoBot.Modules.Music.Classes public bool Autoplay { get; set; } = false; public uint MaxQueueSize { get; set; } = 0; + private ConcurrentQueue actionQueue { get; set; } = new ConcurrentQueue(); + public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) { if (startingVoiceChannel == null) @@ -66,65 +70,92 @@ namespace NadekoBot.Modules.Music.Classes Task.Run(async () => { - while (!Destroyed) + try { - try - { - if (audioClient?.State != ConnectionState.Connected) - audioClient = await PlaybackVoiceChannel.JoinAudio().ConfigureAwait(false); - } - catch - { - await Task.Delay(1000).ConfigureAwait(false); - continue; - } - CurrentSong = GetNextSong(); - var curSong = CurrentSong; - if (curSong != null) + while (!Destroyed) { try { - OnStarted(this, curSong); - await curSong.Play(audioClient, cancelToken).ConfigureAwait(false); + Action action; + if (actionQueue.TryDequeue(out action)) + { + action(); + } } - catch (OperationCanceledException) + finally { - Console.WriteLine("Song canceled"); + await Task.Delay(100).ConfigureAwait(false); } - catch (Exception ex) - { - Console.WriteLine($"Exception in PlaySong: {ex}"); - } - OnCompleted(this, curSong); - curSong = CurrentSong; //to check if its null now - if (curSong != null) - if (RepeatSong) - playlist.Insert(0, curSong); - else if (RepeatPlaylist) - playlist.Insert(playlist.Count, curSong); - SongCancelSource = new CancellationTokenSource(); - cancelToken = SongCancelSource.Token; } - await Task.Delay(1000).ConfigureAwait(false); } - }); + catch (Exception ex) + { + Console.WriteLine("Action queue crashed"); + Console.WriteLine(ex); + } + }).ConfigureAwait(false); + + var t = new Thread(new ThreadStart(async () => + { + try + { + while (!Destroyed) + { + try + { + if (audioClient.State != ConnectionState.Connected) + { + audioClient = await PlaybackVoiceChannel.JoinAudio(); + continue; + } + + var song = CurrentSong; + + if (song == null) + continue; + + try + { + await song.Play(audioClient, cancelToken); + } + catch (OperationCanceledException) + { + Console.WriteLine("Song canceled"); + SongCancelSource = new CancellationTokenSource(); + cancelToken = SongCancelSource.Token; + } + OnCompleted(this, song); + + if (RepeatPlaylist) + AddSong(song, song.QueuerName); + + if (RepeatSong) + AddSong(song, 0); + } + finally + { + await Task.Delay(300).ConfigureAwait(false); + } + } + } + catch (Exception ex) { + Console.WriteLine("Music thread crashed."); + Console.WriteLine(ex); + } + })); + + t.Start(); } public void Next() { - lock (playlistLock) - { - if (!SongCancelSource.IsCancellationRequested) - { - Paused = false; - SongCancelSource.Cancel(); - } - } + Paused = false; + SongCancelSource.Cancel(); } public void Stop() { - lock (playlistLock) + actionQueue.Enqueue(() => { playlist.Clear(); CurrentSong = null; @@ -132,19 +163,11 @@ namespace NadekoBot.Modules.Music.Classes RepeatSong = false; if (!SongCancelSource.IsCancellationRequested) SongCancelSource.Cancel(); - } + }); } public void TogglePause() => Paused = !Paused; - public void Shuffle() - { - lock (playlistLock) - { - playlist.Shuffle(); - } - } - public int SetVolume(int volume) { if (volume < 0) @@ -156,16 +179,15 @@ namespace NadekoBot.Modules.Music.Classes return volume; } - private Song GetNextSong() + private Song GetNextSong() => + playlist.FirstOrDefault(); + + public void Shuffle() { - lock (playlistLock) + actionQueue.Enqueue(() => { - if (playlist.Count == 0) - return null; - var toReturn = playlist[0]; - playlist.RemoveAt(0); - return toReturn; - } + playlist.Shuffle(); + }); } public void AddSong(Song s, string username) @@ -173,42 +195,63 @@ namespace NadekoBot.Modules.Music.Classes if (s == null) throw new ArgumentNullException(nameof(s)); ThrowIfQueueFull(); - lock (playlistLock) + actionQueue.Enqueue(() => { s.MusicPlayer = this; s.QueuerName = username.TrimTo(10); playlist.Add(s); - } + }); } public void AddSong(Song s, int index) { if (s == null) throw new ArgumentNullException(nameof(s)); - lock (playlistLock) + actionQueue.Enqueue(() => { playlist.Insert(index, s); - } + }); } public void RemoveSong(Song s) { if (s == null) throw new ArgumentNullException(nameof(s)); - lock (playlistLock) + actionQueue.Enqueue(() => { playlist.Remove(s); - } + }); } public void RemoveSongAt(int index) { - lock (playlistLock) + actionQueue.Enqueue(() => { if (index < 0 || index >= playlist.Count) - throw new ArgumentException("Invalid index"); + return; playlist.RemoveAt(index); - } + }); + } + + internal void ClearQueue() + { + actionQueue.Enqueue(() => + { + playlist.Clear(); + }); + } + + public void Destroy() + { + actionQueue.Enqueue(() => + { + playlist.Clear(); + Destroyed = true; + CurrentSong = null; + if (!SongCancelSource.IsCancellationRequested) + SongCancelSource.Cancel(); + audioClient.Disconnect(); + }); } internal Task MoveToVoiceChannel(Channel voiceChannel) @@ -219,27 +262,6 @@ namespace NadekoBot.Modules.Music.Classes return PlaybackVoiceChannel.JoinAudio(); } - internal void ClearQueue() - { - lock (playlistLock) - { - playlist.Clear(); - } - } - - public void Destroy() - { - lock (playlistLock) - { - playlist.Clear(); - Destroyed = true; - CurrentSong = null; - if (!SongCancelSource.IsCancellationRequested) - SongCancelSource.Cancel(); - audioClient.Disconnect(); - } - } - internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist;