Music **should** be perfectly stable! .uid improved

This commit is contained in:
Master Kwoth 2016-01-28 04:38:26 +01:00
parent db73723f67
commit 073d67c1ed
5 changed files with 116 additions and 104 deletions

View File

@ -19,7 +19,8 @@ namespace NadekoBot.Classes.Music {
Task.Run(async () => { Task.Run(async () => {
while (true) { while (true) {
try { try {
if (CurrentSong == null || CurrentSong.State == StreamState.Completed) { if ((CurrentSong == null && SongQueue.Count>0) ||
CurrentSong?.State == StreamState.Completed) {
LoadNextSong(); LoadNextSong();
} }
} catch (Exception e) { } 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 (SongQueue.Count == 0) {
if (CurrentSong != null)
CurrentSong.Cancel();
CurrentSong = null; CurrentSong = null;
return; return;
} }
CurrentSong = SongQueue[0]; CurrentSong = SongQueue[0];
SongQueue.RemoveAt(0); SongQueue.RemoveAt(0);
CurrentSong.Start(); CurrentSong.Start();
Console.WriteLine("starting"); Console.WriteLine("Starting next song.");
} }
internal void RemoveAllSongs() { internal void RemoveAllSongs() {

View File

@ -55,7 +55,7 @@ namespace NadekoBot.Classes.Music {
.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"); throw new Exception("Could not load any video elements based on the query.");
Title = video.Title; Title = video.Title;
@ -63,6 +63,8 @@ namespace NadekoBot.Classes.Music {
OnQueued(); OnQueued();
}); });
internal string PrintStats() => musicStreamer?.Stats();
internal void Pause() { internal void Pause() {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -77,7 +79,7 @@ namespace NadekoBot.Classes.Music {
//todo maybe add remove, in order to create remove at position command //todo maybe add remove, in order to create remove at position command
internal void Cancel() { internal void Cancel() {
musicStreamer?.StopPlayback(); musicStreamer?.Cancel();
} }
internal Task Start() => internal Task Start() =>
@ -107,13 +109,12 @@ namespace NadekoBot.Classes.Music {
private DualStream buffer; private DualStream buffer;
public StreamState State { get; internal set; } public StreamState State { get; internal set; }
public string Url { get; private set; } public string Url { get; }
private bool IsCanceled { get; set; }
StreamRequest parent; StreamRequest parent;
private readonly object _bufferLock = new object(); private readonly object _bufferLock = new object();
private CancellationTokenSource cancelSource; private CancellationTokenSource bufferCancelSource;
public static Timer logTimer = new Timer();
public MusicStreamer(StreamRequest parent, string directUrl, Channel channel) { public MusicStreamer(StreamRequest parent, string directUrl, Channel channel) {
this.parent = parent; this.parent = parent;
@ -122,24 +123,15 @@ namespace NadekoBot.Classes.Music {
this.Url = directUrl; this.Url = directUrl;
Console.WriteLine("Created new streamer"); Console.WriteLine("Created new streamer");
State = StreamState.Queued; State = StreamState.Queued;
cancelSource = new CancellationTokenSource(); bufferCancelSource = new CancellationTokenSource();
if (!logTimer.Enabled) {
logTimer.Interval = 5000;
logTimer.Start();
}
} }
private void LogTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { public string Stats() =>
if (cancelSource.IsCancellationRequested || State != StreamState.Playing) { //don't log if canceld or not playing "--------------------------------\n" +
logTimer.Elapsed -= LogTimer_Elapsed; $"Music stats for {string.Join("", parent.Title.Take(parent.Title.Length > 20 ? 20 : parent.Title.Length))}\n" +
return; $"Server: {parent.Server.Name}\n" +
} $"Length:{buffer.Length * 1.0f / 1.MB()}MB Status: {State}\n" +
Console.WriteLine($"Music stats for {string.Join("", parent.Title.Take(parent.Title.Length > 10 ? 10 : parent.Title.Length))}"); "--------------------------------\n";
Console.WriteLine($"Server: {parent.Server.Name}");
Console.WriteLine($"Title: - Length:{buffer.Length * 1.0f / 1.MB()}MB Status: {State} - Canceled: { cancelSource.IsCancellationRequested}");
Console.WriteLine("--------------------------------");
}
//todo app will crash if song is too long, should load only next 20-ish seconds //todo app will crash if song is too long, should load only next 20-ish seconds
private async Task BufferSong() { private async Task BufferSong() {
@ -154,44 +146,50 @@ namespace NadekoBot.Classes.Music {
CreateNoWindow = true, CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
}); });
while (true) { 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); await Task.Delay(1000);
} }
if (cancelSource.IsCancellationRequested) { if (State == StreamState.Completed) {
try { try {
p.CancelOutputRead(); p.CancelOutputRead();
p.Close(); p.Close();
} catch (Exception) { } } catch (Exception) { }
Console.WriteLine("Buffering canceled, stream is completed.");
return; return;
} }
if (buffer.readPos > 10.MiB()) { // if buffer is over 10 MiB, create new one if (buffer.readPos > 5.MiB()) { // if buffer is over 5 MiB, create new one
Console.WriteLine("Buffer over 10 megs, clearing."); 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(); byte[] data = buffer.ToArray().Skip(skip).ToArray();
var newBuffer = new DualStream();
lock (_bufferLock) { lock (_bufferLock) {
var newWritePos = buffer.writePos - skip;
var newReadPos = buffer.readPos - skip; var newReadPos = buffer.readPos - skip;
var newPos = buffer.Position - skip; var newPos = buffer.Position - skip;
buffer = newBuffer;
buffer = new DualStream();
buffer.Write(data, 0, data.Length); buffer.Write(data, 0, data.Length);
buffer.writePos = newWritePos;
buffer.readPos = newReadPos; buffer.readPos = newReadPos;
buffer.Position = newPos; buffer.Position = newPos;
} }
} }
var buf = new byte[1024]; var buf = new byte[1024];
int read = 0; int read = 0;
read = await p.StandardOutput.BaseStream.ReadAsync(buf, 0, 1024); read = await p.StandardOutput.BaseStream.ReadAsync(buf, 0, 1024);
//Console.WriteLine($"Read: {read}");
if (read == 0) { if (read == 0) {
try { try {
p.CancelOutputRead(); p.CancelOutputRead();
@ -203,63 +201,72 @@ namespace NadekoBot.Classes.Music {
} }
await buffer.WriteAsync(buf, 0, read); await buffer.WriteAsync(buf, 0, read);
} }
} }
internal Task StartPlayback() => internal async Task StartPlayback() {
Task.Run(async () => { Console.WriteLine("Starting playback.");
logTimer.Elapsed += LogTimer_Elapsed; // start logging only when the song starts State = StreamState.Playing;
Console.WriteLine("Starting playback."); if (parent.OnBuffering != null)
State = StreamState.Playing; parent.OnBuffering();
if (parent.OnBuffering != null) BufferSong();
parent.OnBuffering(); try {
Task.Run(async () => await BufferSong()); await Task.Delay(5000, bufferCancelSource.Token);
await Task.Delay(2000,cancelSource.Token); } catch (Exception) {
if (parent.OnStarted != null) Console.WriteLine("Buffered enough in less than 5 seconds!");
parent.OnStarted(); }
//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."); 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 audio = NadekoBot.client.Audio();
var voiceClient = await audio.Join(channel);
int blockSize = 1920 * NadekoBot.client.Audio().Config.Channels;
byte[] voiceBuffer = new byte[blockSize];
while (true) { var voiceClient = await audio.Join(channel);
int readCount = 0; int blockSize = 1920 * NadekoBot.client.Audio().Config.Channels;
lock (_bufferLock) { byte[] voiceBuffer = new byte[blockSize];
readCount = buffer.Read(voiceBuffer, 0, voiceBuffer.Length);
}
if (readCount == 0) { while (!IsCanceled) {
Console.WriteLine("Nothing to read, stream finished."); int readCount = 0;
break; lock (_bufferLock) {
} readCount = buffer.Read(voiceBuffer, 0, voiceBuffer.Length);
// while (MusicControls.IsPaused && !cancelSource.IsCancellationRequested)
// await Task.Delay(100);
if (cancelSource.IsCancellationRequested) {
Console.WriteLine("Canceled");
break;
}
voiceClient.Send(voiceBuffer, 0, voiceBuffer.Length);
} }
voiceClient.Wait(); if (readCount == 0) {
await voiceClient.Disconnect(); Console.WriteLine("Nothing else to read.");
if (parent.OnCompleted != null) break;
parent.OnCompleted(); }
State = StreamState.Completed;
Console.WriteLine("Song completed."); 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() { internal void StopPlayback() {
Console.WriteLine("Stopping playback"); Console.WriteLine("Stopping playback");
State = StreamState.Completed; if (State != StreamState.Completed) {
if(cancelSource.Token.CanBeCanceled) State = StreamState.Completed;
cancelSource.Cancel(); parent.OnCompleted();
}
} }
} }
@ -267,8 +274,9 @@ namespace NadekoBot.Classes.Music {
public long readPos; public long readPos;
public long writePos; public long writePos;
public DualStream() : base() { } public DualStream() : base() {
public DualStream(byte[] data) : base(data) { } readPos = writePos = 0;
}
public override int Read(byte[] buffer, int offset, int count) { public override int Read(byte[] buffer, int offset, int count) {
int read; int read;

View File

@ -256,15 +256,11 @@ namespace NadekoBot.Modules
cgb.CreateCommand(".uid").Alias(".userid") cgb.CreateCommand(".uid").Alias(".userid")
.Description("Shows user id") .Description("Shows user id")
.Parameter("user", ParameterType.Required) .Parameter("user", ParameterType.Optional)
.Do(async e => { .Do(async e => {
var usr = e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault(); var usr = e.User;
if (usr == null) { if(e.GetArg("user") != null) e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault();
await e.Send("You must mention a user."); await e.Send($"Id of the user { usr.Name } is { usr.Id }");
return;
}
await e.Send("Id of the user " + usr.Name + " is " + usr.Id);
}); });
cgb.CreateCommand(".cid").Alias(".channelid") cgb.CreateCommand(".cid").Alias(".channelid")

View File

@ -15,6 +15,7 @@ using System.Net;
using System.Globalization; using System.Globalization;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using NadekoBot.Classes.Music; using NadekoBot.Classes.Music;
using Timer = System.Timers.Timer;
namespace NadekoBot.Modules { namespace NadekoBot.Modules {
class Music : DiscordModule { class Music : DiscordModule {
@ -44,10 +45,18 @@ namespace NadekoBot.Modules {
} }
public Music() : base() { public Music() : base() {
System.Timers.Timer cleaner = new System.Timers.Timer(); Timer cleaner = new Timer();
cleaner.Elapsed += (s, e) => CleanMusicPlayers(); cleaner.Elapsed += (s, e) => CleanMusicPlayers();
cleaner.Interval = 10000; cleaner.Interval = 10000;
cleaner.Start(); 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) { public override void Install(ModuleManager manager) {
@ -74,7 +83,6 @@ namespace NadekoBot.Modules {
player.RemoveAllSongs(); player.RemoveAllSongs();
if (player.CurrentSong != null) { if (player.CurrentSong != null) {
player.CurrentSong.Cancel(); player.CurrentSong.Cancel();
player.CurrentSong = null;
} }
}); });
@ -129,7 +137,7 @@ namespace NadekoBot.Modules {
player.SongQueue.Add(sr); player.SongQueue.Add(sr);
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine(); Console.WriteLine();
await e.Send("Error. :anger:"); await e.Send($"Error. :anger:\n{ex.Message}");
return; return;
} }
}); });

View File

@ -82,8 +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);
@ -97,7 +96,8 @@ namespace NadekoBot {
var audio = client.Services.Add<AudioService>(new AudioService(new AudioServiceConfig() { var audio = client.Services.Add<AudioService>(new AudioService(new AudioServiceConfig() {
Channels = 2, Channels = 2,
EnableEncryption = false, EnableEncryption = false,
EnableMultiserver = true EnableMultiserver = true,
Bitrate = 128,
})); }));
//install modules //install modules
@ -144,7 +144,7 @@ namespace NadekoBot {
public static string GetUptimeString() { public static string GetUptimeString() {
var time = (DateTime.Now - Process.GetCurrentProcess().StartTime); 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; static bool repliedRecently = false;