fixed music up a lot, caching stats every 5 seconds.

This commit is contained in:
Master Kwoth 2016-01-29 13:28:51 +01:00
parent 0c8934f12f
commit 805c592fff
4 changed files with 139 additions and 110 deletions

View File

@ -14,7 +14,7 @@ namespace NadekoBot.Classes.Music {
public List<StreamRequest> SongQueue = new List<StreamRequest>(); public List<StreamRequest> SongQueue = new List<StreamRequest>();
public StreamRequest CurrentSong; public StreamRequest CurrentSong;
public bool IsPaused { get; internal set; } public bool IsPaused { get; internal set; } = false;
public IAudioClient VoiceClient; public IAudioClient VoiceClient;
private readonly object _voiceLock = new object(); private readonly object _voiceLock = new object();
@ -23,12 +23,15 @@ namespace NadekoBot.Classes.Music {
Task.Run(async () => { Task.Run(async () => {
while (true) { while (true) {
try { try {
if (CurrentSong == null) { lock (_voiceLock) {
if (SongQueue.Count > 0) if (CurrentSong == null) {
LoadNextSong(); if (SongQueue.Count > 0)
LoadNextSong();
} else if (CurrentSong.State == StreamState.Completed) { } else if (CurrentSong.State == StreamState.Completed || NextSong) {
LoadNextSong(); NextSong = false;
LoadNextSong();
}
} }
} catch (Exception e) { } catch (Exception e) {
Console.WriteLine("Bug in music task run. " + e); Console.WriteLine("Bug in music task run. " + e);
@ -43,38 +46,50 @@ namespace NadekoBot.Classes.Music {
} }
public void LoadNextSong() { public void LoadNextSong() {
Console.WriteLine("Loading next song.");
lock (_voiceLock) { lock (_voiceLock) {
if (SongQueue.Count == 0) { CurrentSong?.Stop();
CurrentSong = null; CurrentSong = null;
return; if (SongQueue.Count == 0) return;
}
CurrentSong = SongQueue[0]; CurrentSong = SongQueue[0];
SongQueue.RemoveAt(0); SongQueue.RemoveAt(0);
} }
CurrentSong.Start();
Console.WriteLine("Starting next song."); try {
CurrentSong?.Start();
} catch (Exception ex) {
Console.WriteLine($"Starting failed: {ex}");
CurrentSong?.Stop();
CurrentSong = null;
}
} }
internal void RemoveAllSongs() { internal void Stop() {
lock (_voiceLock) { lock (_voiceLock) {
foreach (var kvp in SongQueue) { foreach (var kvp in SongQueue) {
if(kvp != null) if(kvp != null)
kvp.Cancel(); kvp.Cancel();
} }
SongQueue.Clear(); SongQueue.Clear();
LoadNextSong();
VoiceClient.Disconnect(); VoiceClient.Disconnect();
VoiceClient = null; VoiceClient = null;
} }
} }
internal StreamRequest CreateStreamRequest(CommandEventArgs e, string query, Channel voiceChannel) { internal StreamRequest CreateStreamRequest(CommandEventArgs e, string query, Channel voiceChannel) {
if (VoiceChannel == null)
throw new ArgumentNullException("Please join a voicechannel.");
StreamRequest sr = null;
lock (_voiceLock) { lock (_voiceLock) {
if (VoiceClient == null) if (VoiceClient == null) {
VoiceClient = NadekoBot.client.Audio().Join(VoiceChannel).Result; VoiceClient = NadekoBot.client.Audio().Join(VoiceChannel).Result;
return new StreamRequest(e, query, VoiceClient); }
sr = new StreamRequest(e, query, this);
SongQueue.Add(sr);
} }
return sr;
} }
internal bool TogglePause() => IsPaused = !IsPaused;
} }
} }

View File

@ -29,50 +29,56 @@ namespace NadekoBot.Classes.Music {
public User User { get; } public User User { get; }
public string Query { get; } public string Query { get; }
public string Title { get; internal set; } = String.Empty; public string Title { get; internal set; } = String.Empty;
public IAudioClient VoiceClient { get; private set; } public IAudioClient VoiceClient { get; private set; }
private MusicStreamer musicStreamer = null; private MusicStreamer musicStreamer = null;
public StreamState State => musicStreamer?.State ?? StreamState.Resolving; public StreamState State => musicStreamer?.State ?? privateState;
private StreamState privateState = StreamState.Resolving;
public StreamRequest(CommandEventArgs e, string query, IAudioClient voiceClient) { public bool IsPaused => MusicControls.IsPaused;
private MusicControls MusicControls;
public StreamRequest(CommandEventArgs e, string query, MusicControls mc) {
if (e == null) if (e == null)
throw new ArgumentNullException(nameof(e)); throw new ArgumentNullException(nameof(e));
if (query == null) if (query == null)
throw new ArgumentNullException(nameof(query)); throw new ArgumentNullException(nameof(query));
if (voiceClient == null) if (mc.VoiceClient == null)
throw new NullReferenceException($"{nameof(voiceClient)} is null, bot didn't join any server."); throw new NullReferenceException($"{nameof(mc.VoiceClient)} is null, bot didn't join any server.");
this.MusicControls = mc;
this.VoiceClient = voiceClient; this.VoiceClient = mc.VoiceClient;
this.Server = e.Server; this.Server = e.Server;
this.Query = query; this.Query = query;
ResolveStreamLink(); Task.Run(() => ResolveStreamLink());
} }
private Task ResolveStreamLink() => private void ResolveStreamLink() {
Task.Run(() => { VideoInfo video = null;
try {
Console.WriteLine("Resolving video link"); Console.WriteLine("Resolving video link");
var video = DownloadUrlResolver.GetDownloadUrls(Searches.FindYoutubeUrlByKeywords(Query)) video = DownloadUrlResolver.GetDownloadUrls(Searches.FindYoutubeUrlByKeywords(Query))
.Where(v => v.AdaptiveType == AdaptiveType.Audio) .Where(v => v.AdaptiveType == AdaptiveType.Audio)
.OrderByDescending(v => v.AudioBitrate).FirstOrDefault(); .OrderByDescending(v => v.AudioBitrate).FirstOrDefault();
if (video == null) // do something with this error if (video == null) // do something with this error
throw new Exception("Could not load any video elements based on the query."); throw new Exception("Could not load any video elements based on the query.");
Title = video.Title; Title = video.Title;
} catch (Exception ex) {
privateState = StreamState.Completed;
Console.WriteLine($"Failed resolving the link.{ex.Message}");
return;
}
musicStreamer = new MusicStreamer(this, video.DownloadUrl, Channel); musicStreamer = new MusicStreamer(this, video.DownloadUrl, Channel);
if(OnQueued!=null) if (OnQueued != null)
OnQueued(); OnQueued();
}); }
internal string PrintStats() => musicStreamer?.Stats(); internal string PrintStats() => musicStreamer?.Stats();
internal void Pause() {
throw new NotImplementedException();
}
public Action OnQueued = null; public Action OnQueued = null;
public Action OnBuffering = null; public Action OnBuffering = null;
public Action OnStarted = null; public Action OnStarted = null;
@ -84,24 +90,28 @@ namespace NadekoBot.Classes.Music {
musicStreamer?.Cancel(); musicStreamer?.Cancel();
} }
internal void Stop() {
musicStreamer?.Stop();
}
internal Task Start() => internal Task Start() =>
Task.Run(async () => { Task.Run(async () => {
Console.WriteLine("Start called."); Console.WriteLine("Start called.");
int attemptsLeft = 7; int attemptsLeft = 4;
//wait for up to 7 seconds to resolve a link //wait for up to 4 seconds to resolve a link
while (State == StreamState.Resolving) { while (State == StreamState.Resolving) {
await Task.Delay(1000); await Task.Delay(1000);
Console.WriteLine("Resolving..."); Console.WriteLine("Resolving...");
if (--attemptsLeft == 0) { if (--attemptsLeft == 0) {
Console.WriteLine("Resolving timed out."); throw new TimeoutException("Resolving timed out.");
return;
} }
} }
try { try {
await musicStreamer.StartPlayback(); await musicStreamer.StartPlayback();
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine("Error in start playback." + ex); Console.WriteLine("Error in start playback." + ex.Message);
privateState = StreamState.Completed;
} }
}); });
} }
@ -113,6 +123,7 @@ namespace NadekoBot.Classes.Music {
public StreamState State { get; internal set; } public StreamState State { get; internal set; }
public string Url { get; } public string Url { get; }
private bool IsCanceled { get; set; } private bool IsCanceled { get; set; }
public bool IsPaused => parent.IsPaused;
StreamRequest parent; StreamRequest parent;
private readonly object _bufferLock = new object(); private readonly object _bufferLock = new object();
@ -152,14 +163,23 @@ namespace NadekoBot.Classes.Music {
while (true) { while (true) {
while (buffer.writePos - buffer.readPos > 5.MB() && State != StreamState.Completed) { while (buffer.writePos - buffer.readPos > 5.MB() && State != StreamState.Completed) {
if (bufferCancelSource.Token.CanBeCanceled && !bufferCancelSource.IsCancellationRequested) { if (!bufferCancelSource.IsCancellationRequested) {
bufferCancelSource.Cancel(); Console.WriteLine("Canceling buffer token");
Console.WriteLine("Canceling buffer token"); Task.Run(() => bufferCancelSource.Cancel());
} }
await Task.Delay(1000); await Task.Delay(50);
} }
if (State == StreamState.Completed) {
try {
p.CancelOutputRead();
p.Close();
} catch (Exception) { }
Console.WriteLine("Buffering canceled, stream is completed.");
return;
}
if (buffer.readPos > 5.MiB()) { // if buffer is over 5 MiB, create new one if (buffer.readPos > 5.MiB()) { // if buffer is over 5 MiB, create new one
Console.WriteLine("Buffer over 5 megs, clearing."); Console.WriteLine("Buffer over 5 megs, clearing.");
@ -175,16 +195,7 @@ namespace NadekoBot.Classes.Music {
buffer.readPos = newReadPos; buffer.readPos = newReadPos;
buffer.Position = newPos; buffer.Position = newPos;
} }
} }
if (State == StreamState.Completed) {
try {
p.CancelOutputRead();
p.Close();
} catch (Exception) { }
Console.WriteLine("Buffering canceled, stream is completed.");
return;
}
var buf = new byte[1024]; var buf = new byte[1024];
int read = 0; int read = 0;
@ -212,6 +223,7 @@ namespace NadekoBot.Classes.Music {
internal async Task StartPlayback() { internal async Task StartPlayback() {
Console.WriteLine("Starting playback."); Console.WriteLine("Starting playback.");
if (State == StreamState.Playing) return;
State = StreamState.Playing; State = StreamState.Playing;
if (parent.OnBuffering != null) if (parent.OnBuffering != null)
parent.OnBuffering(); parent.OnBuffering();
@ -253,22 +265,28 @@ namespace NadekoBot.Classes.Music {
} else } else
attempt = 0; attempt = 0;
if (State == StreamState.Completed) { if (State == StreamState.Completed) {
Console.WriteLine("Canceled"); Console.WriteLine("Canceled");
break; break;
} }
parent.VoiceClient.Send(voiceBuffer, 0, voiceBuffer.Length); parent.VoiceClient.Send(voiceBuffer, 0, voiceBuffer.Length);
while (IsPaused) {
await Task.Delay(50);
}
} }
parent.VoiceClient.Wait(); parent.VoiceClient.Wait();
StopPlayback(); Stop();
} }
internal void Cancel() { internal void Cancel() {
IsCanceled = true; IsCanceled = true;
} }
internal void StopPlayback() { internal void Stop() {
Console.WriteLine("Stopping playback"); Console.WriteLine("Stopping playback");
if (State != StreamState.Completed) { if (State != StreamState.Completed) {
State = StreamState.Completed; State = StreamState.Completed;

View File

@ -13,34 +13,20 @@ namespace NadekoBot.Modules {
public static ConcurrentDictionary<Server, MusicControls> musicPlayers = new ConcurrentDictionary<Server, MusicControls>(); public static ConcurrentDictionary<Server, MusicControls> musicPlayers = new ConcurrentDictionary<Server, MusicControls>();
internal static void CleanMusicPlayers() {
foreach (var mp in musicPlayers
.Where(kvp => kvp.Value.CurrentSong == null
&& kvp.Value.SongQueue.Count == 0)) {
var val = mp.Value;
(musicPlayers as System.Collections.IDictionary).Remove(mp.Key);
}
}
internal static string GetMusicStats() { internal static string GetMusicStats() {
var servers = 0; var servers = 0;
var queued = 0; var queued = 0;
musicPlayers.ForEach(kvp => { var stats = musicPlayers.Where(kvp => kvp.Value?.SongQueue.Count > 0 || kvp.Value?.CurrentSong != null);
var mp = kvp.Value;
if (mp.SongQueue.Count > 0 || mp.CurrentSong != null)
queued += mp.SongQueue.Count + 1;
servers++;
});
return $"Playing {queued} songs across {servers} servers."; return $"Playing {stats.Count()} songs, {stats.Sum(kvp => kvp.Value?.SongQueue?.Count ?? 0)} queued.";
} }
public Music() : base() { public Music() : base() {
Timer cleaner = new Timer(); /*Timer cleaner = new Timer();
cleaner.Elapsed += (s, e) => System.Threading.Tasks.Task.Run(() => CleanMusicPlayers()); cleaner.Elapsed += (s, e) => System.Threading.Tasks.Task.Run(() => CleanMusicPlayers());
cleaner.Interval = 10000; cleaner.Interval = 10000;
cleaner.Start(); cleaner.Start();
/*
Timer statPrinter = new Timer(); Timer statPrinter = new Timer();
NadekoBot.client.Connected += (s, e) => { NadekoBot.client.Connected += (s, e) => {
if (statPrinter.Enabled) return; if (statPrinter.Enabled) return;
@ -62,8 +48,8 @@ namespace NadekoBot.Modules {
.Alias("next") .Alias("next")
.Description("Goes to the next song in the queue.") .Description("Goes to the next song in the queue.")
.Do(e => { .Do(e => {
if (musicPlayers.ContainsKey(e.Server) == false || (musicPlayers[e.Server]?.CurrentSong) == null) return; if (musicPlayers.ContainsKey(e.Server) == false) return;
musicPlayers[e.Server].CurrentSong.Cancel(); musicPlayers[e.Server].LoadNextSong();
}); });
cgb.CreateCommand("s") cgb.CreateCommand("s")
@ -72,10 +58,9 @@ namespace NadekoBot.Modules {
.Do(e => { .Do(e => {
if (musicPlayers.ContainsKey(e.Server) == false) return; if (musicPlayers.ContainsKey(e.Server) == false) return;
var player = musicPlayers[e.Server]; var player = musicPlayers[e.Server];
player.RemoveAllSongs(); player.Stop();
if (player.CurrentSong != null) { MusicControls throwAwayValue;
player.CurrentSong.Cancel(); musicPlayers.TryRemove(e.Server, out throwAwayValue);
}
}); });
cgb.CreateCommand("p") cgb.CreateCommand("p")
@ -83,14 +68,10 @@ namespace NadekoBot.Modules {
.Description("Pauses the song") .Description("Pauses the song")
.Do(async e => { .Do(async e => {
if (musicPlayers.ContainsKey(e.Server) == false) return; if (musicPlayers.ContainsKey(e.Server) == false) return;
await e.Send("This feature is coming tomorrow."); if (musicPlayers[e.Server].TogglePause())
/* await e.Send("Music player paused.");
if (musicPlayers[e.Server].Pause()) else
if (musicPlayers[e.Server].IsPaused) await e.Send("Music player unpaused.");
await e.Send("Music player Paused");
else
await e.Send("Music player unpaused.");
*/
}); });
cgb.CreateCommand("q") cgb.CreateCommand("q")
@ -99,18 +80,23 @@ namespace NadekoBot.Modules {
.Parameter("query", ParameterType.Unparsed) .Parameter("query", ParameterType.Unparsed)
.Do(async e => { .Do(async e => {
if (musicPlayers.ContainsKey(e.Server) == false) if (musicPlayers.ContainsKey(e.Server) == false)
if (musicPlayers.Count > 25) { if (!musicPlayers.TryAdd(e.Server, new MusicControls(e.User.VoiceChannel))) {
await e.Send($"{e.User.Mention}, playlist supports up to 25 songs. If you think this is not enough, contact the owner.:warning:"); await e.Send("Failed to create a music player for this server");
return; return;
} }
else
(musicPlayers as System.Collections.IDictionary).Add(e.Server, new MusicControls(e.User.VoiceChannel));
var player = musicPlayers[e.Server]; var player = musicPlayers[e.Server];
if (player.SongQueue.Count > 25) {
await e.Send("Music player supports up to 25 songs atm. Contant the owner if you think this is not enough :warning:");
}
try { try {
var sr = player.CreateStreamRequest(e, e.GetArg("query"), player.VoiceChannel); var sr = player.CreateStreamRequest(e, e.GetArg("query"), player.VoiceChannel);
if (sr == null)
throw new NullReferenceException("StreamRequest is null.");
Message msg = null; Message msg = null;
sr.OnQueued += async() => { sr.OnQueued += async () => {
msg = await e.Send($":musical_note:**Queued** {sr.Title}"); msg = await e.Send($":musical_note:**Queued** {sr.Title}");
}; };
sr.OnCompleted += async () => { sr.OnCompleted += async () => {
@ -126,12 +112,9 @@ namespace NadekoBot.Modules {
if (msg != null) if (msg != null)
msg = await e.Send($":musical_note:**Buffering the song**...{sr.Title}"); msg = await e.Send($":musical_note:**Buffering the song**...{sr.Title}");
}; };
lock (player.SongQueue) {
player.SongQueue.Add(sr);
}
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine(); Console.WriteLine();
await e.Send($"Error. :anger:\n{ex.Message}"); await e.Send($":anger: {ex.Message}");
return; return;
} }
}); });
@ -171,5 +154,5 @@ namespace NadekoBot.Modules {
}); });
}); });
} }
} }
} }

View File

@ -82,7 +82,7 @@ namespace NadekoBot {
//reply to personal messages and forward if enabled. //reply to personal messages and forward if enabled.
client.MessageReceived += Client_MessageReceived; client.MessageReceived += Client_MessageReceived;
//add command service //add command service
var commands = client.Services.Add<CommandService>(commandService); var commands = client.Services.Add<CommandService>(commandService);
@ -110,10 +110,13 @@ namespace NadekoBot {
if (loadTrello) if (loadTrello)
modules.Add(new Trello(), "Trello", ModuleFilter.None); modules.Add(new Trello(), "Trello", ModuleFilter.None);
//start the timer for stats
_statsSW.Start();
//run the bot //run the bot
client.ExecuteAndWait(async () => { client.ExecuteAndWait(async () => {
await client.Connect(c.Username, c.Password); await client.Connect(c.Username, c.Password);
LoadStats();
Console.WriteLine("-----------------"); Console.WriteLine("-----------------");
Console.WriteLine(GetStats()); Console.WriteLine(GetStats());
Console.WriteLine("-----------------"); Console.WriteLine("-----------------");
@ -127,12 +130,21 @@ namespace NadekoBot {
Console.WriteLine("Exiting..."); Console.WriteLine("Exiting...");
Console.ReadKey(); Console.ReadKey();
} }
private static string _statsCache = "";
public static string GetStats() => private static Stopwatch _statsSW = new Stopwatch();
public static string GetStats() {
if (_statsSW.ElapsedTicks > 5) {
LoadStats();
_statsSW.Restart();
}
return _statsCache;
}
private static void LoadStats() {
_statsCache =
"Author: Kwoth" + "Author: Kwoth" +
$"\nDiscord.Net version: {DiscordConfig.LibVersion}"+ $"\nDiscord.Net version: {DiscordConfig.LibVersion}" +
$"\nRuntime: {client.GetRuntime()}" + $"\nRuntime: {client.GetRuntime()}" +
$"\nBot Version: {BotVersion}"+ $"\nBot Version: {BotVersion}" +
$"\nLogged in as: {client.CurrentUser.Name}" + $"\nLogged in as: {client.CurrentUser.Name}" +
$"\nBot id: {client.CurrentUser.Id}" + $"\nBot id: {client.CurrentUser.Id}" +
$"\nUptime: {GetUptimeString()}" + $"\nUptime: {GetUptimeString()}" +
@ -141,6 +153,7 @@ namespace NadekoBot {
$"\nUsers: {client.Servers.SelectMany(x => x.Users.Select(y => y.Id)).Count()} ({client.Servers.SelectMany(x => x.Users.Select(y => y.Id)).Distinct().Count()} unique) ({client.Servers.SelectMany(x => x.Users.Where(y => y.Status != UserStatus.Offline).Select(y => y.Id)).Distinct().Count()} online)" + $"\nUsers: {client.Servers.SelectMany(x => x.Users.Select(y => y.Id)).Count()} ({client.Servers.SelectMany(x => x.Users.Select(y => y.Id)).Distinct().Count()} unique) ({client.Servers.SelectMany(x => x.Users.Where(y => y.Status != UserStatus.Offline).Select(y => y.Id)).Distinct().Count()} online)" +
$"\nHeap: {Math.Round(GC.GetTotalMemory(true) / (1024.0 * 1024.0), 2).ToString()}MB" + $"\nHeap: {Math.Round(GC.GetTotalMemory(true) / (1024.0 * 1024.0), 2).ToString()}MB" +
$"\nCommands Ran this session: {commandsRan}"; $"\nCommands Ran this session: {commandsRan}";
}
public static string GetUptimeString() { public static string GetUptimeString() {
var time = (DateTime.Now - Process.GetCurrentProcess().StartTime); var time = (DateTime.Now - Process.GetCurrentProcess().StartTime);
@ -167,8 +180,8 @@ namespace NadekoBot {
} }
if (ForwardMessages && OwnerUser != null) if (ForwardMessages && OwnerUser != null)
await OwnerUser.SendMessage(e.User +": ```\n"+e.Message.Text+"\n```"); await OwnerUser.SendMessage(e.User + ": ```\n" + e.Message.Text + "\n```");
if (repliedRecently = !repliedRecently) { if (repliedRecently = !repliedRecently) {
await e.Send("You can type `-h` or `-help` or `@MyName help` in any of the channels I am in and I will send you a message with my commands.\n Or you can find out what i do here: https://github.com/Kwoth/NadekoBot\nYou can also just send me an invite link to a server and I will join it.\nIf you don't want me on your server, you can simply ban me ;(\nBot Creator's server: https://discord.gg/0ehQwTK2RBhxEi0X"); await e.Send("You can type `-h` or `-help` or `@MyName help` in any of the channels I am in and I will send you a message with my commands.\n Or you can find out what i do here: https://github.com/Kwoth/NadekoBot\nYou can also just send me an invite link to a server and I will join it.\nIf you don't want me on your server, you can simply ban me ;(\nBot Creator's server: https://discord.gg/0ehQwTK2RBhxEi0X");
Timer t = new Timer(); Timer t = new Timer();