Music **should** be perfectly stable! .uid improved
This commit is contained in:
parent
db73723f67
commit
073d67c1ed
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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")
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user