From 450ca709b41c6d76bbcf276e5da1067354e4b3ed Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 1 Apr 2016 22:50:41 +0200 Subject: [PATCH] small refactor --- NadekoBot/Classes/Music/PoopyBuffer.cs | 120 +++++++++++ NadekoBot/Classes/Music/Song.cs | 270 +++++++++++-------------- NadekoBot/Commands/TriviaCommand.cs | 41 ++-- 3 files changed, 260 insertions(+), 171 deletions(-) create mode 100644 NadekoBot/Classes/Music/PoopyBuffer.cs diff --git a/NadekoBot/Classes/Music/PoopyBuffer.cs b/NadekoBot/Classes/Music/PoopyBuffer.cs new file mode 100644 index 00000000..9d59d35b --- /dev/null +++ b/NadekoBot/Classes/Music/PoopyBuffer.cs @@ -0,0 +1,120 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Classes.Music +{ + + /// + /// 💩 + /// + public class PoopyBuffer + { + + private readonly byte[] ringBuffer; + + public int WritePosition { get; private set; } = 0; + public int ReadPosition { get; private set; } = 0; + + public int ContentLength => (WritePosition >= ReadPosition ? + WritePosition - ReadPosition : + (BufferSize - ReadPosition) + WritePosition); + + public int BufferSize { get; } + + private readonly object readWriteLock = new object(); + + public PoopyBuffer(int size) + { + if (size <= 0) + throw new ArgumentException(); + BufferSize = size; + ringBuffer = new byte[size]; + } + + public int Read(byte[] buffer, int count) + { + if (buffer.Length < count) + throw new ArgumentException(); + //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); + lock (readWriteLock) + { + //read as much as you can if you're reading too much + if (count > ContentLength) + count = ContentLength; + //if nothing to read, return 0 + if (WritePosition == ReadPosition) + return 0; + // if buffer is in the "normal" state, just read + if (WritePosition > ReadPosition) + { + Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count); + ReadPosition += count; + //Console.WriteLine($"Read only normally1 {count}[{ReadPosition - count} to {ReadPosition}]"); + return count; + } + //else ReadPos buffer.Length) + throw new ArgumentException(); + while (ContentLength + count > BufferSize) + { + await Task.Delay(20, cancelToken); + if (cancelToken.IsCancellationRequested) + return; + } + //the while above assures that i cannot write past readposition with my write, so i don't have to check + // *unless its multithreaded or task is not awaited + lock (readWriteLock) + { + // if i can just write without hitting buffer.length, do it + if (WritePosition + count < BufferSize) + { + Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count); + WritePosition += count; + //Console.WriteLine($"Wrote only normally {count}[{WritePosition - count} to {WritePosition}]"); + return; + } + // otherwise, i have to write to the end, then write the rest from the start + + var wroteNormaly = BufferSize - WritePosition; + Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, wroteNormaly); + + //Console.WriteLine($"Wrote normally {wroteNormaly}[{WritePosition} to {BufferSize}]"); + + var wroteFromStart = count - wroteNormaly; + Buffer.BlockCopy(buffer, wroteNormaly, ringBuffer, 0, wroteFromStart); + + //Console.WriteLine($"and from start {wroteFromStart} [0 to {wroteFromStart}"); + + WritePosition = wroteFromStart; + } + } + } +} diff --git a/NadekoBot/Classes/Music/Song.cs b/NadekoBot/Classes/Music/Song.cs index 54a001fc..dab40651 100644 --- a/NadekoBot/Classes/Music/Song.cs +++ b/NadekoBot/Classes/Music/Song.cs @@ -10,116 +10,17 @@ using System.Threading; using System.Threading.Tasks; using VideoLibrary; -namespace NadekoBot.Classes.Music { - public class SongInfo { +namespace NadekoBot.Classes.Music +{ + public class SongInfo + { public string Provider { get; internal set; } public MusicType ProviderType { get; internal set; } public string Title { get; internal set; } public string Uri { get; internal set; } } - /// - /// 💩 - /// - public class PoopyBuffer { - - private readonly byte[] ringBuffer; - - public int WritePosition { get; private set; } = 0; - public int ReadPosition { get; private set; } = 0; - - public int ContentLength => (WritePosition >= ReadPosition ? - WritePosition - ReadPosition : - (BufferSize - ReadPosition) + WritePosition); - - public int BufferSize { get; } - - private readonly object readWriteLock = new object(); - - public PoopyBuffer(int size) { - if (size <= 0) - throw new ArgumentException(); - BufferSize = size; - ringBuffer = new byte[size]; - } - - public int Read(byte[] buffer, int count) { - if (buffer.Length < count) - throw new ArgumentException(); - //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); - lock (readWriteLock) { - //read as much as you can if you're reading too much - if (count > ContentLength) - count = ContentLength; - //if nothing to read, return 0 - if (WritePosition == ReadPosition) - return 0; - // if buffer is in the "normal" state, just read - if (WritePosition > ReadPosition) { - Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count); - ReadPosition += count; - //Console.WriteLine($"Read only normally1 {count}[{ReadPosition - count} to {ReadPosition}]"); - return count; - } - //else ReadPos buffer.Length) - throw new ArgumentException(); - while (ContentLength + count > BufferSize) { - await Task.Delay(20, cancelToken); - if (cancelToken.IsCancellationRequested) - return; - } - //the while above assures that i cannot write past readposition with my write, so i don't have to check - // *unless its multithreaded or task is not awaited - lock (readWriteLock) { - // if i can just write without hitting buffer.length, do it - if (WritePosition + count < BufferSize) { - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count); - WritePosition += count; - //Console.WriteLine($"Wrote only normally {count}[{WritePosition - count} to {WritePosition}]"); - return; - } - // otherwise, i have to write to the end, then write the rest from the start - - var wroteNormaly = BufferSize - WritePosition; - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, wroteNormaly); - - //Console.WriteLine($"Wrote normally {wroteNormaly}[{WritePosition} to {BufferSize}]"); - - var wroteFromStart = count - wroteNormaly; - Buffer.BlockCopy(buffer, wroteNormaly, ringBuffer, 0, wroteFromStart); - - //Console.WriteLine($"and from start {wroteFromStart} [0 to {wroteFromStart}"); - - WritePosition = wroteFromStart; - } - } - } - public class Song { + public class Song + { public StreamState State { get; internal set; } public string PrettyName => $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}`"; @@ -130,22 +31,27 @@ namespace NadekoBot.Classes.Music { private bool prebufferingComplete { get; set; } = false; public MusicPlayer MusicPlayer { get; set; } - public string PrettyCurrentTime() { + public string PrettyCurrentTime() + { var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); return $"【{(int)time.TotalMinutes}m {time.Seconds}s】"; } private ulong bytesSent { get; set; } = 0; - private Song(SongInfo songInfo) { + private Song(SongInfo songInfo) + { this.SongInfo = songInfo; } private Task BufferSong(CancellationToken cancelToken) => - Task.Run(async () => { + Task.Factory.StartNew(async () => + { Process p = null; - try { - p = Process.Start(new ProcessStartInfo { + try + { + p = Process.Start(new ProcessStartInfo + { FileName = "ffmpeg", Arguments = $"-i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet", UseShellExecute = false, @@ -156,11 +62,15 @@ namespace NadekoBot.Classes.Music { const int blockSize = 3840; var buffer = new byte[blockSize]; var attempt = 0; - while (!cancelToken.IsCancellationRequested) { + while (!cancelToken.IsCancellationRequested) + { var read = 0; - try { + try + { read = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize, cancelToken); - } catch { + } + catch + { return; } if (read == 0) @@ -168,7 +78,8 @@ namespace NadekoBot.Classes.Music { break; else await Task.Delay(100, cancelToken); - else { + else + { attempt = 0; await Task.Delay(5, cancelToken); } @@ -176,24 +87,35 @@ namespace NadekoBot.Classes.Music { if (songBuffer.ContentLength > 2.MB()) prebufferingComplete = true; } - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine($"Buffering errored: {ex.Message}"); - } finally { + } + finally + { Console.WriteLine($"Buffering done." + $" [{songBuffer.ContentLength}]"); - if (p != null) { - try { + if (p != null) + { + try + { p.Kill(); - } catch { } + } + catch { } p.Dispose(); } } - }); + }, TaskCreationOptions.LongRunning); - internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) { + internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) + { var bufferTask = new ConfiguredTaskAwaitable(); - try { + try + { bufferTask = BufferSong(cancelToken).ConfigureAwait(false); - } catch (Exception ex) { + } + catch (Exception ex) + { var clr = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"ERR BUFFER START : {ex.Message}\n{ex}"); @@ -202,26 +124,31 @@ namespace NadekoBot.Classes.Music { var bufferAttempts = 0; const int waitPerAttempt = 500; var toAttemptTimes = SongInfo.ProviderType != MusicType.Normal ? 5 : 9; - while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) { + while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) + { await Task.Delay(waitPerAttempt, cancelToken); } cancelToken.ThrowIfCancellationRequested(); Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); const int blockSize = 3840; var attempt = 0; - while (!cancelToken.IsCancellationRequested) { + while (!cancelToken.IsCancellationRequested) + { //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); byte[] buffer = new byte[blockSize]; var read = songBuffer.Read(buffer, blockSize); - unchecked { + unchecked + { bytesSent += (ulong)read; } if (read == 0) - if (attempt++ == 20) { + if (attempt++ == 20) + { voiceClient.Wait(); Console.WriteLine($"Song finished. [{songBuffer.ContentLength}]"); break; - } else + } + else await Task.Delay(100, cancelToken); else attempt = 0; @@ -239,11 +166,13 @@ namespace NadekoBot.Classes.Music { } //stackoverflow ftw - private static byte[] AdjustVolume(byte[] audioSamples, float volume) { + private static byte[] AdjustVolume(byte[] audioSamples, float volume) + { if (Math.Abs(volume - 1.0f) < 0.01f) return audioSamples; var array = new byte[audioSamples.Length]; - for (var i = 0; i < array.Length; i += 2) { + for (var i = 0; i < array.Length; i += 2) + { // convert byte pair to int short buf1 = audioSamples[i + 1]; @@ -263,35 +192,43 @@ namespace NadekoBot.Classes.Music { return array; } - public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) { + public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) + { if (string.IsNullOrWhiteSpace(query)) throw new ArgumentNullException(nameof(query)); - if (musicType != MusicType.Local && IsRadioLink(query)) { + if (musicType != MusicType.Local && IsRadioLink(query)) + { musicType = MusicType.Radio; query = await HandleStreamContainers(query) ?? query; } - try { - switch (musicType) { + try + { + switch (musicType) + { case MusicType.Local: - return new Song(new SongInfo { + return new Song(new SongInfo + { Uri = "\"" + Path.GetFullPath(query) + "\"", Title = Path.GetFileNameWithoutExtension(query), Provider = "Local File", ProviderType = musicType, }); case MusicType.Radio: - return new Song(new SongInfo { + return new Song(new SongInfo + { Uri = query, Title = $"{query}", Provider = "Radio Stream", ProviderType = musicType, }); } - if (SoundCloud.Default.IsSoundCloudLink(query)) { + if (SoundCloud.Default.IsSoundCloudLink(query)) + { var svideo = await SoundCloud.Default.GetVideoAsync(query); - return new Song(new SongInfo { + return new Song(new SongInfo + { Title = svideo.FullName, Provider = "SoundCloud", Uri = svideo.StreamLink, @@ -310,76 +247,99 @@ namespace NadekoBot.Classes.Music { if (video == null) // do something with this error throw new Exception("Could not load any video elements based on the query."); - return new Song(new SongInfo { + return new Song(new SongInfo + { Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" Provider = "YouTube", Uri = video.Uri, ProviderType = musicType, }); - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine($"Failed resolving the link.{ex.Message}"); return null; } } - private static async Task HandleStreamContainers(string query) { + private static async Task HandleStreamContainers(string query) + { string file = null; - try { + try + { file = await SearchHelper.GetResponseStringAsync(query); - } catch { + } + catch + { return query; } - if (query.Contains(".pls")) { + if (query.Contains(".pls")) + { //File1=http://armitunes.com:8000/ //Regex.Match(query) - try { + try + { var m = Regex.Match(file, "File1=(?.*?)\\n"); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .pls:\n{file}"); return null; } } - if (query.Contains(".m3u")) { + if (query.Contains(".m3u")) + { /* # This is a comment C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 */ - try { + try + { var m = Regex.Match(file, "(?^[^#].*)", RegexOptions.Multiline); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .m3u:\n{file}"); return null; } } - if (query.Contains(".asx")) { + if (query.Contains(".asx")) + { // - try { + try + { var m = Regex.Match(file, ".*?)\""); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .asx:\n{file}"); return null; } } - if (query.Contains(".xspf")) { + if (query.Contains(".xspf")) + { /* file:///mp3s/song_1.mp3 */ - try { + try + { var m = Regex.Match(file, "(?.*?)"); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .xspf:\n{file}"); return null; } diff --git a/NadekoBot/Commands/TriviaCommand.cs b/NadekoBot/Commands/TriviaCommand.cs index c2398e81..865361f5 100644 --- a/NadekoBot/Commands/TriviaCommand.cs +++ b/NadekoBot/Commands/TriviaCommand.cs @@ -1,35 +1,41 @@ -using System; -using System.Threading.Tasks; -using Discord.Commands; -using System.Collections.Concurrent; -using Discord; +using Discord.Commands; using NadekoBot.Modules; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; using TriviaGame = NadekoBot.Classes.Trivia.TriviaGame; -namespace NadekoBot.Commands { - internal class Trivia : DiscordCommand { +namespace NadekoBot.Commands +{ + internal class Trivia : DiscordCommand + { public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); - public Func DoFunc() => async e => { + public Func DoFunc() => async e => + { TriviaGame trivia; - if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) { + if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) + { var triviaGame = new TriviaGame(e); if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) await e.Channel.SendMessage("**Trivia game started!**\nFirst player to get to 10 points wins! You have 30 seconds per question.\nUse command `tq` if game was started by accident.**"); else await triviaGame.StopGame(); - } else + } + else await e.Channel.SendMessage("Trivia game is already running on this server.\n" + trivia.CurrentQuestion); }; - internal override void Init(CommandGroupBuilder cgb) { + internal override void Init(CommandGroupBuilder cgb) + { cgb.CreateCommand(Module.Prefix + "t") .Description("Starts a game of trivia.") .Do(DoFunc()); cgb.CreateCommand(Module.Prefix + "tl") .Description("Shows a current trivia leaderboard.") - .Do(async e=> { + .Do(async e => + { TriviaGame trivia; if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) await e.Channel.SendMessage(trivia.GetLeaderboard()); @@ -39,15 +45,18 @@ namespace NadekoBot.Commands { cgb.CreateCommand(Module.Prefix + "tq") .Description("Quits current trivia after current question.") - .Do(async e=> { + .Do(async e => + { TriviaGame trivia; - if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) { + if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) + { await trivia.StopGame(); - } else + } + else await e.Channel.SendMessage("No trivia is running on this server."); }); } - public Trivia(DiscordModule module) : base(module) {} + public Trivia(DiscordModule module) : base(module) { } } }