diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/NadekoBot/Modules/Music/Classes/MusicControls.cs index 146ca5ea..7cc43771 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 @@ -20,7 +22,6 @@ namespace NadekoBot.Modules.Music.Classes { Resolving, Queued, - Buffering, //not using it atm Playing, Completed } @@ -33,7 +34,7 @@ namespace NadekoBot.Modules.Music.Classes public IReadOnlyCollection Playlist => playlist; private readonly object playlistLock = new object(); - public Song CurrentSong { get; set; } = default(Song); + public Song CurrentSong { get; private set; } private CancellationTokenSource SongCancelSource { get; set; } private CancellationToken cancelToken { get; set; } @@ -52,6 +53,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,85 +69,110 @@ 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; + } + + CurrentSong = GetNextSong(); + RemoveSongAt(0); + + if (CurrentSong == null) + continue; + + try + { + OnStarted(this, CurrentSong); + await CurrentSong.Play(audioClient, cancelToken); + } + catch (OperationCanceledException) + { + Console.WriteLine("Song canceled"); + SongCancelSource = new CancellationTokenSource(); + cancelToken = SongCancelSource.Token; + } + OnCompleted(this, CurrentSong); + + if (RepeatPlaylist) + AddSong(CurrentSong, CurrentSong.QueuerName); + + if (RepeatSong) + AddSong(CurrentSong, 0); + + } + finally + { + await Task.Delay(300).ConfigureAwait(false); + CurrentSong = null; + } + } + } + catch (Exception ex) { + Console.WriteLine("Music thread crashed."); + Console.WriteLine(ex); + } + })); + + t.Start(); } public void Next() { - lock (playlistLock) + actionQueue.Enqueue(() => { - if (!SongCancelSource.IsCancellationRequested) - { - Paused = false; - SongCancelSource.Cancel(); - } - } + Paused = false; + SongCancelSource.Cancel(); + }); } public void Stop() { - lock (playlistLock) + actionQueue.Enqueue(() => { - playlist.Clear(); - CurrentSong = null; RepeatPlaylist = false; RepeatSong = false; + playlist.Clear(); 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 +184,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 +200,64 @@ 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(() => + { + RepeatPlaylist = false; + RepeatSong = false; + Destroyed = true; + playlist.Clear(); + if (!SongCancelSource.IsCancellationRequested) + SongCancelSource.Cancel(); + audioClient.Disconnect(); + }); } internal Task MoveToVoiceChannel(Channel voiceChannel) @@ -219,27 +268,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; diff --git a/NadekoBot/Modules/Music/Classes/Song.cs b/NadekoBot/Modules/Music/Classes/Song.cs index e718302e..025863e0 100644 --- a/NadekoBot/Modules/Music/Classes/Song.cs +++ b/NadekoBot/Modules/Music/Classes/Song.cs @@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Music.Classes public SongInfo SongInfo { get; } public string QueuerName { get; set; } - private PoopyBuffer songBuffer { get; } = new PoopyBuffer(NadekoBot.Config.BufferSize); + private PoopyBuffer songBuffer { get; set; } private bool prebufferingComplete { get; set; } = false; public MusicPlayer MusicPlayer { get; set; } @@ -137,6 +137,9 @@ namespace NadekoBot.Modules.Music.Classes internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) { + // initialize the buffer here because if this song was playing before (requeued), we must delete old buffer data + songBuffer = new PoopyBuffer(NadekoBot.Config.BufferSize); + var bufferTask = BufferSong(cancelToken).ConfigureAwait(false); var bufferAttempts = 0; const int waitPerAttempt = 500; @@ -145,7 +148,6 @@ namespace NadekoBot.Modules.Music.Classes { await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false); } - cancelToken.ThrowIfCancellationRequested(); Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); const int blockSize = 3840; var attempt = 0;