Merge branch 'music-rewrite' into dev

This commit is contained in:
Kwoth 2016-07-21 17:42:15 +02:00
commit 31c4e315c6
2 changed files with 124 additions and 94 deletions

View File

@ -2,7 +2,9 @@
using Discord.Audio; using Discord.Audio;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Music.Classes namespace NadekoBot.Modules.Music.Classes
@ -20,7 +22,6 @@ namespace NadekoBot.Modules.Music.Classes
{ {
Resolving, Resolving,
Queued, Queued,
Buffering, //not using it atm
Playing, Playing,
Completed Completed
} }
@ -33,7 +34,7 @@ namespace NadekoBot.Modules.Music.Classes
public IReadOnlyCollection<Song> Playlist => playlist; public IReadOnlyCollection<Song> Playlist => playlist;
private readonly object playlistLock = new object(); 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 CancellationTokenSource SongCancelSource { get; set; }
private CancellationToken cancelToken { get; set; } private CancellationToken cancelToken { get; set; }
@ -52,6 +53,8 @@ namespace NadekoBot.Modules.Music.Classes
public bool Autoplay { get; set; } = false; public bool Autoplay { get; set; } = false;
public uint MaxQueueSize { get; set; } = 0; public uint MaxQueueSize { get; set; } = 0;
private ConcurrentQueue<Action> actionQueue { get; set; } = new ConcurrentQueue<Action>();
public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume)
{ {
if (startingVoiceChannel == null) if (startingVoiceChannel == null)
@ -66,85 +69,110 @@ namespace NadekoBot.Modules.Music.Classes
Task.Run(async () => Task.Run(async () =>
{ {
while (!Destroyed) try
{ {
try while (!Destroyed)
{
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)
{ {
try try
{ {
OnStarted(this, curSong); Action action;
await curSong.Play(audioClient, cancelToken).ConfigureAwait(false); 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() public void Next()
{ {
lock (playlistLock) actionQueue.Enqueue(() =>
{ {
if (!SongCancelSource.IsCancellationRequested) Paused = false;
{ SongCancelSource.Cancel();
Paused = false; });
SongCancelSource.Cancel();
}
}
} }
public void Stop() public void Stop()
{ {
lock (playlistLock) actionQueue.Enqueue(() =>
{ {
playlist.Clear();
CurrentSong = null;
RepeatPlaylist = false; RepeatPlaylist = false;
RepeatSong = false; RepeatSong = false;
playlist.Clear();
if (!SongCancelSource.IsCancellationRequested) if (!SongCancelSource.IsCancellationRequested)
SongCancelSource.Cancel(); SongCancelSource.Cancel();
} });
} }
public void TogglePause() => Paused = !Paused; public void TogglePause() => Paused = !Paused;
public void Shuffle()
{
lock (playlistLock)
{
playlist.Shuffle();
}
}
public int SetVolume(int volume) public int SetVolume(int volume)
{ {
if (volume < 0) if (volume < 0)
@ -156,16 +184,15 @@ namespace NadekoBot.Modules.Music.Classes
return volume; return volume;
} }
private Song GetNextSong() private Song GetNextSong() =>
playlist.FirstOrDefault();
public void Shuffle()
{ {
lock (playlistLock) actionQueue.Enqueue(() =>
{ {
if (playlist.Count == 0) playlist.Shuffle();
return null; });
var toReturn = playlist[0];
playlist.RemoveAt(0);
return toReturn;
}
} }
public void AddSong(Song s, string username) public void AddSong(Song s, string username)
@ -173,42 +200,64 @@ namespace NadekoBot.Modules.Music.Classes
if (s == null) if (s == null)
throw new ArgumentNullException(nameof(s)); throw new ArgumentNullException(nameof(s));
ThrowIfQueueFull(); ThrowIfQueueFull();
lock (playlistLock) actionQueue.Enqueue(() =>
{ {
s.MusicPlayer = this; s.MusicPlayer = this;
s.QueuerName = username.TrimTo(10); s.QueuerName = username.TrimTo(10);
playlist.Add(s); playlist.Add(s);
} });
} }
public void AddSong(Song s, int index) public void AddSong(Song s, int index)
{ {
if (s == null) if (s == null)
throw new ArgumentNullException(nameof(s)); throw new ArgumentNullException(nameof(s));
lock (playlistLock) actionQueue.Enqueue(() =>
{ {
playlist.Insert(index, s); playlist.Insert(index, s);
} });
} }
public void RemoveSong(Song s) public void RemoveSong(Song s)
{ {
if (s == null) if (s == null)
throw new ArgumentNullException(nameof(s)); throw new ArgumentNullException(nameof(s));
lock (playlistLock) actionQueue.Enqueue(() =>
{ {
playlist.Remove(s); playlist.Remove(s);
} });
} }
public void RemoveSongAt(int index) public void RemoveSongAt(int index)
{ {
lock (playlistLock) actionQueue.Enqueue(() =>
{ {
if (index < 0 || index >= playlist.Count) if (index < 0 || index >= playlist.Count)
throw new ArgumentException("Invalid index"); return;
playlist.RemoveAt(index); 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) internal Task MoveToVoiceChannel(Channel voiceChannel)
@ -219,27 +268,6 @@ namespace NadekoBot.Modules.Music.Classes
return PlaybackVoiceChannel.JoinAudio(); 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 ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong;
internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist; internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist;

View File

@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Music.Classes
public SongInfo SongInfo { get; } public SongInfo SongInfo { get; }
public string QueuerName { get; set; } 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; private bool prebufferingComplete { get; set; } = false;
public MusicPlayer MusicPlayer { get; set; } public MusicPlayer MusicPlayer { get; set; }
@ -137,6 +137,9 @@ namespace NadekoBot.Modules.Music.Classes
internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) 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 bufferTask = BufferSong(cancelToken).ConfigureAwait(false);
var bufferAttempts = 0; var bufferAttempts = 0;
const int waitPerAttempt = 500; const int waitPerAttempt = 500;
@ -145,7 +148,6 @@ namespace NadekoBot.Modules.Music.Classes
{ {
await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false); await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false);
} }
cancelToken.ThrowIfCancellationRequested();
Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}");
const int blockSize = 3840; const int blockSize = 3840;
var attempt = 0; var attempt = 0;