music rewritten

This commit is contained in:
Master Kwoth 2016-02-28 04:30:16 +01:00
parent 29a0dbdfce
commit 99656095b7
9 changed files with 202 additions and 82 deletions

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Classes.Music {
public enum MusicType {
@ -29,7 +28,7 @@ namespace NadekoBot.Classes.Music {
private List<Song> _playlist = new List<Song>();
public IReadOnlyCollection<Song> Playlist => _playlist;
private object playlistLock = new object();
private readonly object playlistLock = new object();
public Song CurrentSong { get; set; } = default(Song);
private CancellationTokenSource SongCancelSource { get; set; }
@ -44,26 +43,31 @@ namespace NadekoBot.Classes.Music {
public Channel PlaybackVoiceChannel { get; private set; }
public MusicPlayer(Channel startingVoiceChannel, float defaultVolume) {
public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) {
if (startingVoiceChannel == null)
throw new ArgumentNullException(nameof(startingVoiceChannel));
if (startingVoiceChannel.Type != ChannelType.Voice)
throw new ArgumentException("Channel must be of type voice");
Volume = defaultVolume ?? 1.0f;
PlaybackVoiceChannel = startingVoiceChannel;
SongCancelSource = new CancellationTokenSource();
cancelToken = SongCancelSource.Token;
Task.Run(async () => {
while (_client?.State != ConnectionState.Disconnected &&
_client?.State != ConnectionState.Disconnecting) {
while (true) {
try {
_client = await PlaybackVoiceChannel.JoinAudio();
}
catch {
await Task.Delay(1000);
continue;
}
CurrentSong = GetNextSong();
if (CurrentSong != null) {
try {
_client = await PlaybackVoiceChannel.JoinAudio();
OnStarted(CurrentSong);
CurrentSong.Play(_client, cancelToken);
await CurrentSong.Play(_client, cancelToken);
}
catch (OperationCanceledException) {
Console.WriteLine("Song canceled");
@ -71,7 +75,10 @@ namespace NadekoBot.Classes.Music {
catch (Exception ex) {
Console.WriteLine($"Exception in PlaySong: {ex}");
}
OnCompleted(CurrentSong);
try {
OnCompleted(CurrentSong);
}
catch { }
SongCancelSource = new CancellationTokenSource();
cancelToken = SongCancelSource.Token;
}
@ -81,30 +88,31 @@ namespace NadekoBot.Classes.Music {
}
public void Next() {
if (!SongCancelSource.IsCancellationRequested)
SongCancelSource.Cancel();
lock (playlistLock) {
if (!SongCancelSource.IsCancellationRequested) {
Paused = false;
SongCancelSource.Cancel();
}
}
}
public async Task Stop() {
lock (_playlist) {
public void Stop() {
lock (playlistLock) {
_playlist.Clear();
try {
if (!SongCancelSource.IsCancellationRequested)
SongCancelSource.Cancel();
}
catch {
Console.WriteLine("STOP");
}
}
try {
if (!SongCancelSource.IsCancellationRequested)
SongCancelSource.Cancel();
}
catch {
Console.WriteLine("This shouldn't happen");
}
Console.WriteLine("Disconnecting");
await _client?.Disconnect();
}
public void TogglePause() => Paused = !Paused;
public void Shuffle() {
lock (_playlist) {
lock (playlistLock) {
_playlist.Shuffle();
}
}
@ -115,7 +123,8 @@ namespace NadekoBot.Classes.Music {
if (volume > 150)
volume = 150;
return (int)(Volume = volume / 100.0f);
Volume = volume / 100.0f;
return volume;
}
private Song GetNextSong() {

View File

@ -10,42 +10,157 @@ using System.Threading.Tasks;
using VideoLibrary;
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; }
}
/// <summary>
/// 💩
/// </summary>
public class PoopyBuffer {
private byte[] ringBuffer;
public int WritePosition { get; private set; } = 0;
public PoopyBuffer(int size) {
ringBuffer = new byte[size];
}
public int Read(byte[] buffer, int count) {
lock (this) {
if (count > WritePosition)
count = WritePosition;
if (count == 0)
return 0;
Buffer.BlockCopy(ringBuffer, 0, buffer, 0, count);
Buffer.BlockCopy(ringBuffer, count, ringBuffer, 0, WritePosition -= count);
return count;
}
}
public async Task WriteAsync(byte[] buffer, int count, CancellationToken cancelToken) {
if (count > buffer.Length)
throw new ArgumentException();
while (count + WritePosition > ringBuffer.Length) {
await Task.Delay(20);
if (cancelToken.IsCancellationRequested)
return;
}
lock (this) {
Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count);
WritePosition += count;
}
}
}
public class Song {
public StreamState State { get; internal set; }
public object PrettyName =>
public string PrettyName =>
$"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}`";
public SongInfo SongInfo { get; }
private PoopyBuffer songBuffer { get; } = new PoopyBuffer(10.MB());
private bool prebufferingComplete { get; set; } = false;
public MusicPlayer MusicPlayer { get; set; }
private Song(SongInfo songInfo) {
this.SongInfo = songInfo;
}
internal void Play(IAudioClient voiceClient, CancellationToken cancelToken) {
var p = Process.Start(new ProcessStartInfo {
FileName = "ffmpeg",
Arguments = $"-i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false,
RedirectStandardOutput = true,
private Task BufferSong(CancellationToken cancelToken) =>
Task.Run(async () => {
var p = Process.Start(new ProcessStartInfo {
FileName = "ffmpeg",
Arguments = $"-i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false,
RedirectStandardOutput = true,
});
int blockSize = 512;
byte[] buffer = new byte[blockSize];
int attempt = 0;
while (!cancelToken.IsCancellationRequested) {
int read = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize);
if (read == 0)
if (attempt++ == 10)
break;
else
await Task.Delay(50);
else
attempt = 0;
await songBuffer.WriteAsync(buffer, read, cancelToken);
if (songBuffer.WritePosition > 5.MB())
prebufferingComplete = true;
}
Console.WriteLine("Buffering done.");
});
Task.Delay(2000); //give it 2 seconds to get some dataz
int blockSize = 3840; // 1920 for mono
internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) {
var t = BufferSong(cancelToken).ConfigureAwait(false);
int bufferAttempts = 0;
int waitPerAttempt = 500;
int toAttemptTimes = SongInfo.ProviderType != MusicType.Normal ? 4 : 8;
while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) {
await Task.Delay(waitPerAttempt);
}
int blockSize = 3840;
byte[] buffer = new byte[blockSize];
int read;
int attempt = 0;
while (!cancelToken.IsCancellationRequested) {
read = p.StandardOutput.BaseStream.Read(buffer, 0, blockSize);
int read = songBuffer.Read(buffer, blockSize);
if (read == 0)
break; //nothing to read
if (attempt++ == 10) {
voiceClient.Wait();
Console.WriteLine("Playing done.");
return;
}
else
await Task.Delay(50);
else
attempt = 0;
while (this.MusicPlayer.Paused)
await Task.Delay(200);
buffer = adjustVolume(buffer, MusicPlayer.Volume);
voiceClient.Send(buffer, 0, read);
}
voiceClient.Wait();
//try {
// voiceClient.Clear();
// Console.WriteLine("CLEARED");
//}
//catch {
// Console.WriteLine("CLEAR FAILED!!!");
//}
}
//stackoverflow ftw
private byte[] adjustVolume(byte[] audioSamples, float volume) {
if (volume == 1.0f)
return audioSamples;
byte[] array = new byte[audioSamples.Length];
for (int i = 0; i < array.Length; i += 2) {
// convert byte pair to int
short buf1 = audioSamples[i + 1];
short buf2 = audioSamples[i];
buf1 = (short)((buf1 & 0xff) << 8);
buf2 = (short)(buf2 & 0xff);
short res = (short)(buf1 | buf2);
res = (short)(res * volume);
// convert back
array[i] = (byte)res;
array[i + 1] = (byte)(res >> 8);
}
return array;
}
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal) {
@ -63,6 +178,7 @@ namespace NadekoBot.Classes.Music {
Uri = "\"" + Path.GetFullPath(query) + "\"",
Title = Path.GetFileNameWithoutExtension(query),
Provider = "Local File",
ProviderType = musicType,
});
}
else if (musicType == MusicType.Radio) {
@ -70,6 +186,7 @@ namespace NadekoBot.Classes.Music {
Uri = query,
Title = $"{query}",
Provider = "Radio Stream",
ProviderType = musicType,
});
}
else if (SoundCloud.Default.IsSoundCloudLink(query)) {
@ -78,6 +195,7 @@ namespace NadekoBot.Classes.Music {
Title = svideo.FullName,
Provider = "SoundCloud",
Uri = svideo.StreamLink,
ProviderType = musicType,
});
}
else {
@ -97,6 +215,7 @@ namespace NadekoBot.Classes.Music {
Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
Provider = "YouTube",
Uri = video.Uri,
ProviderType = musicType,
});
}

View File

@ -293,30 +293,6 @@ namespace NadekoBot.Classes.Music {
if (oldState == StreamState.Playing)
OnCompleted();
}
//stackoverflow ftw
private byte[] adjustVolume(byte[] audioSamples, float volume) {
if (volume == 1.0f)
return audioSamples;
byte[] array = new byte[audioSamples.Length];
for (int i = 0; i < array.Length; i += 2) {
// convert byte pair to int
short buf1 = audioSamples[i + 1];
short buf2 = audioSamples[i];
buf1 = (short)((buf1 & 0xff) << 8);
buf2 = (short)(buf2 & 0xff);
short res = (short)(buf1 | buf2);
res = (short)(res * volume);
// convert back
array[i] = (byte)res;
array[i + 1] = (byte)(res >> 8);
}
return array;
}
}
public class DualStream : MemoryStream {

View File

@ -58,8 +58,14 @@ namespace NadekoBot.Modules {
.Description("Completely stops the music, unbinds the bot from the channel, and cleans up files.")
.Do(async e => {
MusicPlayer musicPlayer;
if (!musicPlayers.TryRemove(e.Server, out musicPlayer)) return;
await musicPlayer.Stop();
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
musicPlayer.Stop();
var msg = await e.Channel.SendMessage("⚠Due to music issues, NadekoBot is unable to leave voice channels at this moment.\nIf this presents inconvenience, you can use `!m mv` command to make her join your current voice channel.");
await Task.Delay(5000);
try {
await msg.Delete();
}
catch { }
});
cgb.CreateCommand("p")
@ -95,6 +101,8 @@ namespace NadekoBot.Modules {
string toSend = "🎵 **" + musicPlayer.Playlist.Count + "** `videos currently queued.` ";
if (musicPlayer.Playlist.Count >= MusicPlayer.MaximumPlaylistSize)
toSend += "**Song queue is full!**\n";
else
toSend += "\n";
int number = 1;
await e.Channel.SendMessage(toSend + string.Join("\n", musicPlayer.Playlist.Take(15).Select(v => $"`{number++}.` {v.PrettyName}")));
});
@ -313,7 +321,7 @@ namespace NadekoBot.Modules {
float throwAway;
if (defaultMusicVolumes.TryGetValue(TextCh.Server.Id, out throwAway))
vol = throwAway;
musicPlayer = new MusicPlayer(VoiceCh) {
musicPlayer = new MusicPlayer(VoiceCh, vol) {
OnCompleted = async (song) => {
try {
await TextCh.SendMessage($"🎵`Finished`{song.PrettyName}");
@ -331,7 +339,7 @@ namespace NadekoBot.Modules {
musicPlayers.TryAdd(TextCh.Server, musicPlayer);
}
var resolvedSong = await Song.ResolveSong(query, musicType);
resolvedSong.MusicPlayer = musicPlayer;
if(!silent)
await TextCh.Send($"🎵`Queued`{resolvedSong.PrettyName}");
musicPlayer.AddSong(resolvedSong);

View File

@ -66,6 +66,13 @@ namespace NadekoBot {
//create new discord client
client = new DiscordClient(new DiscordConfigBuilder() {
MessageCacheSize = 20,
LogLevel = LogSeverity.Warning,
LogHandler = (s, e) => {
try {
Console.WriteLine($"Severity: {e.Severity}\nMessage: {e.Message}\nExceptionMessage: {e.Exception?.Message ?? "-"}\nException: {(e.Exception?.ToString() ?? "-")}");
}
catch { }
}
});
//create a command service
@ -139,17 +146,18 @@ namespace NadekoBot {
}
Classes.Permissions.PermissionsHandler.Initialize();
client.ClientAPI.SendingRequest += (s, e) => {
try {
var request = e.Request as Discord.API.Client.Rest.SendMessageRequest;
if (request != null) {
request.Content = request.Content?.Replace("@everyone", "@everyοne") ?? "_error_";
//@everyοne
request.Content = request.Content?.Replace("@everyone", "@everryone") ?? "_error_";
if (string.IsNullOrWhiteSpace(request.Content))
e.Cancel = true;
else
Console.WriteLine("Sending request.");
var content = request.Content;
//else
// Console.WriteLine("Sending request");
}
}
catch {
@ -157,15 +165,15 @@ namespace NadekoBot {
}
};
client.ClientAPI.SentRequest += (s, e) => {
try {
var request = e.Request as Discord.API.Client.Rest.SendMessageRequest;
if (request != null) {
Console.WriteLine("Sent.");
}
}
catch { Console.WriteLine("SENT REQUEST ERRORED!!!"); }
};
//client.ClientAPI.SentRequest += (s, e) => {
// try {
// var request = e.Request as Discord.API.Client.Rest.SendMessageRequest;
// if (request != null) {
// Console.WriteLine("Sent.");
// }
// }
// catch { Console.WriteLine("SENT REQUEST ERRORED!!!"); }
//};
});
Console.WriteLine("Exiting...");
Console.ReadKey();

Binary file not shown.