diff --git a/NadekoBot/Classes/Music/MusicControls.cs b/NadekoBot/Classes/Music/MusicControls.cs index 03e18e86..0c8841f0 100644 --- a/NadekoBot/Classes/Music/MusicControls.cs +++ b/NadekoBot/Classes/Music/MusicControls.cs @@ -19,7 +19,8 @@ namespace NadekoBot.Classes.Music { Task.Run(async () => { while (true) { try { - if (CurrentSong == null || CurrentSong.State == StreamState.Completed) { + if ((CurrentSong == null && SongQueue.Count>0) || + CurrentSong?.State == StreamState.Completed) { LoadNextSong(); } } catch (Exception e) { @@ -30,17 +31,16 @@ namespace NadekoBot.Classes.Music { }); } - private void LoadNextSong() { + public void LoadNextSong() { + Console.WriteLine("Loading next song."); if (SongQueue.Count == 0) { - if (CurrentSong != null) - CurrentSong.Cancel(); CurrentSong = null; return; } CurrentSong = SongQueue[0]; SongQueue.RemoveAt(0); CurrentSong.Start(); - Console.WriteLine("starting"); + Console.WriteLine("Starting next song."); } internal void RemoveAllSongs() { diff --git a/NadekoBot/Classes/Music/StreamRequest.cs b/NadekoBot/Classes/Music/StreamRequest.cs index 96d3b54c..c396cab1 100644 --- a/NadekoBot/Classes/Music/StreamRequest.cs +++ b/NadekoBot/Classes/Music/StreamRequest.cs @@ -55,7 +55,7 @@ namespace NadekoBot.Classes.Music { .OrderByDescending(v => v.AudioBitrate).FirstOrDefault(); if (video == null) // do something with this error - throw new Exception("Could not load any video elements"); + throw new Exception("Could not load any video elements based on the query."); Title = video.Title; @@ -63,6 +63,8 @@ namespace NadekoBot.Classes.Music { OnQueued(); }); + internal string PrintStats() => musicStreamer?.Stats(); + internal void Pause() { throw new NotImplementedException(); } @@ -77,7 +79,7 @@ namespace NadekoBot.Classes.Music { //todo maybe add remove, in order to create remove at position command internal void Cancel() { - musicStreamer?.StopPlayback(); + musicStreamer?.Cancel(); } internal Task Start() => @@ -107,13 +109,12 @@ namespace NadekoBot.Classes.Music { private DualStream buffer; public StreamState State { get; internal set; } - public string Url { get; private set; } + public string Url { get; } + private bool IsCanceled { get; set; } StreamRequest parent; private readonly object _bufferLock = new object(); - private CancellationTokenSource cancelSource; - - public static Timer logTimer = new Timer(); + private CancellationTokenSource bufferCancelSource; public MusicStreamer(StreamRequest parent, string directUrl, Channel channel) { this.parent = parent; @@ -122,24 +123,15 @@ namespace NadekoBot.Classes.Music { this.Url = directUrl; Console.WriteLine("Created new streamer"); State = StreamState.Queued; - cancelSource = new CancellationTokenSource(); - - if (!logTimer.Enabled) { - logTimer.Interval = 5000; - logTimer.Start(); - } + bufferCancelSource = new CancellationTokenSource(); } - private void LogTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { - if (cancelSource.IsCancellationRequested || State != StreamState.Playing) { //don't log if canceld or not playing - logTimer.Elapsed -= LogTimer_Elapsed; - return; - } - Console.WriteLine($"Music stats for {string.Join("", parent.Title.Take(parent.Title.Length > 10 ? 10 : parent.Title.Length))}"); - Console.WriteLine($"Server: {parent.Server.Name}"); - Console.WriteLine($"Title: - Length:{buffer.Length * 1.0f / 1.MB()}MB Status: {State} - Canceled: { cancelSource.IsCancellationRequested}"); - Console.WriteLine("--------------------------------"); - } + public string Stats() => + "--------------------------------\n" + + $"Music stats for {string.Join("", parent.Title.Take(parent.Title.Length > 20 ? 20 : parent.Title.Length))}\n" + + $"Server: {parent.Server.Name}\n" + + $"Length:{buffer.Length * 1.0f / 1.MB()}MB Status: {State}\n" + + "--------------------------------\n"; //todo app will crash if song is too long, should load only next 20-ish seconds private async Task BufferSong() { @@ -154,44 +146,50 @@ namespace NadekoBot.Classes.Music { CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, }); + while (true) { - while (buffer.writePos - buffer.readPos > 2.MB() && !cancelSource.IsCancellationRequested) { + while (buffer.writePos - buffer.readPos > 2.MB() && State != StreamState.Completed) { + try { + if (bufferCancelSource.Token.CanBeCanceled && !bufferCancelSource.IsCancellationRequested) { + bufferCancelSource.Cancel(); + Console.WriteLine("Canceling buffer token"); + } + } catch (Exception ex) { Console.WriteLine($"Canceling buffer token failed {ex}"); } + Console.WriteLine("Waiting"); await Task.Delay(1000); } - if (cancelSource.IsCancellationRequested) { + if (State == StreamState.Completed) { try { p.CancelOutputRead(); p.Close(); } catch (Exception) { } + Console.WriteLine("Buffering canceled, stream is completed."); return; } - if (buffer.readPos > 10.MiB()) { // if buffer is over 10 MiB, create new one - Console.WriteLine("Buffer over 10 megs, clearing."); + if (buffer.readPos > 5.MiB()) { // if buffer is over 5 MiB, create new one + Console.WriteLine("Buffer over 5 megs, clearing."); - var skip = 10.MB(); //remove only 10 MB, just in case + var skip = 5.MB(); //remove only 10 MB, just in case byte[] data = buffer.ToArray().Skip(skip).ToArray(); - + + var newBuffer = new DualStream(); lock (_bufferLock) { - var newWritePos = buffer.writePos - skip; var newReadPos = buffer.readPos - skip; var newPos = buffer.Position - skip; - - buffer = new DualStream(); + buffer = newBuffer; buffer.Write(data, 0, data.Length); - - buffer.writePos = newWritePos; buffer.readPos = newReadPos; buffer.Position = newPos; } - + } - + var buf = new byte[1024]; int read = 0; read = await p.StandardOutput.BaseStream.ReadAsync(buf, 0, 1024); - + //Console.WriteLine($"Read: {read}"); if (read == 0) { try { p.CancelOutputRead(); @@ -203,63 +201,72 @@ namespace NadekoBot.Classes.Music { } await buffer.WriteAsync(buf, 0, read); } + } - internal Task StartPlayback() => - Task.Run(async () => { - logTimer.Elapsed += LogTimer_Elapsed; // start logging only when the song starts - Console.WriteLine("Starting playback."); - State = StreamState.Playing; - if (parent.OnBuffering != null) - parent.OnBuffering(); - Task.Run(async () => await BufferSong()); - await Task.Delay(2000,cancelSource.Token); - if (parent.OnStarted != null) - parent.OnStarted(); + internal async Task StartPlayback() { + Console.WriteLine("Starting playback."); + State = StreamState.Playing; + if (parent.OnBuffering != null) + parent.OnBuffering(); + BufferSong(); + try { + await Task.Delay(5000, bufferCancelSource.Token); + } catch (Exception) { + Console.WriteLine("Buffered enough in less than 5 seconds!"); + } + //Task.Run(async () => { while (true) { Console.WriteLine($"Title: {parent.Title} State:{State}"); await Task.Delay(200); } }); + if (parent.OnStarted != null) + parent.OnStarted(); + + if (buffer.Length > 0) { Console.WriteLine("Prebuffering complete."); - //for now wait for 3 seconds before starting playback. + } else { + Console.WriteLine("Didn't buffer jack shit."); + } + //for now wait for 3 seconds before starting playback. - var audio = NadekoBot.client.Audio(); - - var voiceClient = await audio.Join(channel); - int blockSize = 1920 * NadekoBot.client.Audio().Config.Channels; - byte[] voiceBuffer = new byte[blockSize]; + var audio = NadekoBot.client.Audio(); - while (true) { - int readCount = 0; - lock (_bufferLock) { - readCount = buffer.Read(voiceBuffer, 0, voiceBuffer.Length); - } + var voiceClient = await audio.Join(channel); + int blockSize = 1920 * NadekoBot.client.Audio().Config.Channels; + byte[] voiceBuffer = new byte[blockSize]; - if (readCount == 0) { - Console.WriteLine("Nothing to read, stream finished."); - break; - } - - // while (MusicControls.IsPaused && !cancelSource.IsCancellationRequested) - // await Task.Delay(100); - - if (cancelSource.IsCancellationRequested) { - Console.WriteLine("Canceled"); - break; - } - - voiceClient.Send(voiceBuffer, 0, voiceBuffer.Length); + while (!IsCanceled) { + int readCount = 0; + lock (_bufferLock) { + readCount = buffer.Read(voiceBuffer, 0, voiceBuffer.Length); } - voiceClient.Wait(); - await voiceClient.Disconnect(); - if (parent.OnCompleted != null) - parent.OnCompleted(); - State = StreamState.Completed; - Console.WriteLine("Song completed."); - }); + if (readCount == 0) { + Console.WriteLine("Nothing else to read."); + break; + } + + if (State == StreamState.Completed) { + Console.WriteLine("Canceled"); + break; + } + + voiceClient.Send(voiceBuffer, 0, voiceBuffer.Length); + } + + voiceClient.Wait(); + await voiceClient.Disconnect(); + + StopPlayback(); + } + + internal void Cancel() { + IsCanceled = true; + } internal void StopPlayback() { Console.WriteLine("Stopping playback"); - State = StreamState.Completed; - if(cancelSource.Token.CanBeCanceled) - cancelSource.Cancel(); + if (State != StreamState.Completed) { + State = StreamState.Completed; + parent.OnCompleted(); + } } } @@ -267,8 +274,9 @@ namespace NadekoBot.Classes.Music { public long readPos; public long writePos; - public DualStream() : base() { } - public DualStream(byte[] data) : base(data) { } + public DualStream() : base() { + readPos = writePos = 0; + } public override int Read(byte[] buffer, int offset, int count) { int read; diff --git a/NadekoBot/Modules/Administration.cs b/NadekoBot/Modules/Administration.cs index 2e49b1f1..17ed3144 100644 --- a/NadekoBot/Modules/Administration.cs +++ b/NadekoBot/Modules/Administration.cs @@ -256,15 +256,11 @@ namespace NadekoBot.Modules cgb.CreateCommand(".uid").Alias(".userid") .Description("Shows user id") - .Parameter("user", ParameterType.Required) + .Parameter("user", ParameterType.Optional) .Do(async e => { - var usr = e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault(); - if (usr == null) { - await e.Send("You must mention a user."); - return; - } - - await e.Send("Id of the user " + usr.Name + " is " + usr.Id); + var usr = e.User; + if(e.GetArg("user") != null) e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault(); + await e.Send($"Id of the user { usr.Name } is { usr.Id }"); }); cgb.CreateCommand(".cid").Alias(".channelid") diff --git a/NadekoBot/Modules/Music.cs b/NadekoBot/Modules/Music.cs index 1f71ed41..63351cf6 100644 --- a/NadekoBot/Modules/Music.cs +++ b/NadekoBot/Modules/Music.cs @@ -15,6 +15,7 @@ using System.Net; using System.Globalization; using System.Collections.Concurrent; using NadekoBot.Classes.Music; +using Timer = System.Timers.Timer; namespace NadekoBot.Modules { class Music : DiscordModule { @@ -44,10 +45,18 @@ namespace NadekoBot.Modules { } public Music() : base() { - System.Timers.Timer cleaner = new System.Timers.Timer(); + Timer cleaner = new Timer(); cleaner.Elapsed += (s, e) => CleanMusicPlayers(); cleaner.Interval = 10000; cleaner.Start(); + Timer statPrinter = new Timer(); + NadekoBot.client.Connected += (s, e) => { + if (statPrinter.Enabled) return; + statPrinter.Elapsed += (se, ev) => { Console.WriteLine($"<<--Music-->> {musicPlayers.Count} songs playing."); musicPlayers.ForEach(kvp => Console.WriteLine(kvp.Value?.CurrentSong?.PrintStats())); Console.WriteLine("<<--Music END-->>"); }; + statPrinter.Interval = 5000; + statPrinter.Start(); + }; + } public override void Install(ModuleManager manager) { @@ -74,7 +83,6 @@ namespace NadekoBot.Modules { player.RemoveAllSongs(); if (player.CurrentSong != null) { player.CurrentSong.Cancel(); - player.CurrentSong = null; } }); @@ -129,7 +137,7 @@ namespace NadekoBot.Modules { player.SongQueue.Add(sr); } catch (Exception ex) { Console.WriteLine(); - await e.Send("Error. :anger:"); + await e.Send($"Error. :anger:\n{ex.Message}"); return; } }); diff --git a/NadekoBot/NadekoBot.cs b/NadekoBot/NadekoBot.cs index 857d17af..5a274769 100644 --- a/NadekoBot/NadekoBot.cs +++ b/NadekoBot/NadekoBot.cs @@ -82,8 +82,7 @@ namespace NadekoBot { //reply to personal messages and forward if enabled. client.MessageReceived += Client_MessageReceived; - - + //add command service var commands = client.Services.Add(commandService); @@ -97,7 +96,8 @@ namespace NadekoBot { var audio = client.Services.Add(new AudioService(new AudioServiceConfig() { Channels = 2, EnableEncryption = false, - EnableMultiserver = true + EnableMultiserver = true, + Bitrate = 128, })); //install modules @@ -144,7 +144,7 @@ namespace NadekoBot { public static string GetUptimeString() { var time = (DateTime.Now - Process.GetCurrentProcess().StartTime); - return "I am online for " + time.Days + " days, " + time.Hours + " hours, and " + time.Minutes + " minutes."; + return time.Days + " days, " + time.Hours + " hours, and " + time.Minutes + " minutes."; } static bool repliedRecently = false;