Huge amount of work on the music rework. Around 60% done. Fixed bot getting stuck when server region is changed.

This commit is contained in:
Master Kwoth 2017-07-01 08:15:58 +02:00
parent 8f5c63a057
commit d242952d4a
15 changed files with 2304 additions and 1753 deletions

View File

@ -74,25 +74,25 @@ namespace NadekoBot.DataStructures.Replacements
return this; return this;
} }
public ReplacementBuilder WithMusic(MusicService ms) //public ReplacementBuilder WithMusic(MusicService ms)
{ //{
_reps.TryAdd("%playing%", () => // _reps.TryAdd("%playing%", () =>
{ // {
var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); // var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
if (cnt != 1) return cnt.ToString(); // if (cnt != 1) return cnt.ToString();
try // try
{ // {
var mp = ms.MusicPlayers.FirstOrDefault(); // var mp = ms.MusicPlayers.FirstOrDefault();
return mp.Value.CurrentSong.SongInfo.Title; // return mp.Value.CurrentSong.SongInfo.Title;
} // }
catch // catch
{ // {
return "No songs"; // return "No songs";
} // }
}); // });
_reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()); // _reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString());
return this; // return this;
} //}
public ReplacementBuilder WithRngRegex() public ReplacementBuilder WithRngRegex()
{ {

View File

@ -0,0 +1,23 @@
using Discord.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
//public class SyncPrecondition : PreconditionAttribute
//{
// public override Task<PreconditionResult> CheckPermissions(ICommandContext context,
// CommandInfo command,
// IServiceProvider services)
// {
// }
//}
//public enum SyncType
//{
// Guild
//}
}

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@ namespace NadekoBot.Services.Administration
_rep = new ReplacementBuilder() _rep = new ReplacementBuilder()
.WithClient(client) .WithClient(client)
.WithStats(client) .WithStats(client)
.WithMusic(music) //.WithMusic(music)
.Build(); .Build();
_t = new Timer(async (objState) => _t = new Timer(async (objState) =>

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Services.Impl
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly DateTime _started; private readonly DateTime _started;
public const string BotVersion = "1.52"; public const string BotVersion = "1.53";
public string Author => "Kwoth#2560"; public string Author => "Kwoth#2560";
public string Library => "Discord.Net"; public string Library => "Discord.Net";

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
{
public class SyncPreconditionService
{
}
}

View File

@ -2,7 +2,7 @@
namespace NadekoBot.Services.Music namespace NadekoBot.Services.Music
{ {
class PlaylistFullException : Exception public class PlaylistFullException : Exception
{ {
public PlaylistFullException(string message) : base(message) public PlaylistFullException(string message) : base(message)
{ {
@ -10,11 +10,19 @@ namespace NadekoBot.Services.Music
public PlaylistFullException() : base("Queue is full.") { } public PlaylistFullException() : base("Queue is full.") { }
} }
class SongNotFoundException : Exception public class SongNotFoundException : Exception
{ {
public SongNotFoundException(string message) : base(message) public SongNotFoundException(string message) : base(message)
{ {
} }
public SongNotFoundException() : base("Song is not found.") { } public SongNotFoundException() : base("Song is not found.") { }
} }
public class NotInVoiceChannelException : Exception
{
public NotInVoiceChannelException(string message) : base(message)
{
}
public NotInVoiceChannelException() : base("You're not in the voice channel on this server.") { }
}
} }

View File

@ -1,381 +0,0 @@
using Discord;
using Discord.Audio;
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Music
{
public enum StreamState
{
Resolving,
Queued,
Playing,
Completed
}
public class MusicPlayer
{
private IAudioClient AudioClient { get; set; }
/// <summary>
/// Player will prioritize different queuer name
/// over the song position in the playlist
/// </summary>
public bool FairPlay { get; set; } = false;
/// <summary>
/// Song will stop playing after this amount of time.
/// To prevent people queueing radio or looped songs
/// while other people want to listen to other songs too.
/// </summary>
public uint MaxPlaytimeSeconds { get; set; } = 0;
// this should be written better
public TimeSpan TotalPlaytime =>
_playlist.Any(s => s.TotalTime == TimeSpan.MaxValue) ?
TimeSpan.MaxValue :
new TimeSpan(_playlist.Sum(s => s.TotalTime.Ticks));
/// <summary>
/// Users who recently got their music wish
/// </summary>
private ConcurrentHashSet<string> RecentlyPlayedUsers { get; } = new ConcurrentHashSet<string>();
private readonly List<Song> _playlist = new List<Song>();
private readonly Logger _log;
private readonly IGoogleApiService _google;
public IReadOnlyCollection<Song> Playlist => _playlist;
public Song CurrentSong { get; private set; }
public CancellationTokenSource SongCancelSource { get; private set; }
private CancellationToken CancelToken { get; set; }
public bool Paused { get; set; }
public float Volume { get; private set; }
public event Action<MusicPlayer, Song> OnCompleted = delegate { };
public event Action<MusicPlayer, Song> OnStarted = delegate { };
public event Action<bool> OnPauseChanged = delegate { };
public IVoiceChannel PlaybackVoiceChannel { get; private set; }
public ITextChannel OutputTextChannel { get; set; }
private bool Destroyed { get; set; }
public bool RepeatSong { get; private set; }
public bool RepeatPlaylist { get; private set; }
public bool Autoplay { get; set; }
public uint MaxQueueSize { get; set; } = 0;
private ConcurrentQueue<Action> ActionQueue { get; } = new ConcurrentQueue<Action>();
public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%";
public event Action<Song, int> SongRemoved = delegate { };
public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume, IGoogleApiService google)
{
_log = LogManager.GetCurrentClassLogger();
_google = google;
OutputTextChannel = outputChannel;
Volume = defaultVolume ?? 1.0f;
PlaybackVoiceChannel = startingVoiceChannel ?? throw new ArgumentNullException(nameof(startingVoiceChannel));
SongCancelSource = new CancellationTokenSource();
CancelToken = SongCancelSource.Token;
Task.Run(async () =>
{
try
{
while (!Destroyed)
{
try
{
if (ActionQueue.TryDequeue(out Action action))
{
action();
}
}
finally
{
await Task.Delay(100).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
_log.Warn("Action queue crashed");
_log.Warn(ex);
}
}).ConfigureAwait(false);
var t = new Thread(async () =>
{
while (!Destroyed)
{
try
{
CurrentSong = GetNextSong();
if (CurrentSong == null)
continue;
while (AudioClient?.ConnectionState == ConnectionState.Disconnecting ||
AudioClient?.ConnectionState == ConnectionState.Connecting)
{
_log.Info("Waiting for Audio client");
await Task.Delay(200).ConfigureAwait(false);
}
if (AudioClient == null || AudioClient.ConnectionState == ConnectionState.Disconnected)
AudioClient = await PlaybackVoiceChannel.ConnectAsync().ConfigureAwait(false);
var index = _playlist.IndexOf(CurrentSong);
if (index != -1)
RemoveSongAt(index, true);
OnStarted(this, CurrentSong);
try
{
await CurrentSong.Play(AudioClient, CancelToken);
}
catch (OperationCanceledException)
{
}
finally
{
OnCompleted(this, CurrentSong);
}
if (RepeatPlaylist & !RepeatSong)
AddSong(CurrentSong, CurrentSong.QueuerName);
if (RepeatSong)
AddSong(CurrentSong, 0);
}
catch (Exception ex)
{
_log.Warn("Music thread almost crashed.");
_log.Warn(ex);
await Task.Delay(3000).ConfigureAwait(false);
}
finally
{
if (!CancelToken.IsCancellationRequested)
{
SongCancelSource.Cancel();
}
SongCancelSource = new CancellationTokenSource();
CancelToken = SongCancelSource.Token;
CurrentSong = null;
await Task.Delay(300).ConfigureAwait(false);
}
}
});
t.Start();
}
public void Next()
{
ActionQueue.Enqueue(() =>
{
Paused = false;
SongCancelSource.Cancel();
});
}
public void Stop()
{
ActionQueue.Enqueue(() =>
{
RepeatPlaylist = false;
RepeatSong = false;
Autoplay = false;
_playlist.Clear();
if (!SongCancelSource.IsCancellationRequested)
SongCancelSource.Cancel();
});
}
public void TogglePause() => OnPauseChanged(Paused = !Paused);
public int SetVolume(int volume)
{
if (volume < 0)
volume = 0;
if (volume > 100)
volume = 100;
Volume = volume / 100.0f;
return volume;
}
private Song GetNextSong()
{
if (!FairPlay)
{
return _playlist.FirstOrDefault();
}
var song = _playlist.FirstOrDefault(c => !RecentlyPlayedUsers.Contains(c.QueuerName))
?? _playlist.FirstOrDefault();
if (song == null)
return null;
if (RecentlyPlayedUsers.Contains(song.QueuerName))
{
RecentlyPlayedUsers.Clear();
}
RecentlyPlayedUsers.Add(song.QueuerName);
return song;
}
public void Shuffle()
{
ActionQueue.Enqueue(() =>
{
var oldPlaylist = _playlist.ToArray();
_playlist.Clear();
_playlist.AddRange(oldPlaylist.Shuffle());
});
}
public void AddSong(Song s, string username)
{
if (s == null)
throw new ArgumentNullException(nameof(s));
ThrowIfQueueFull();
ActionQueue.Enqueue(() =>
{
s.MusicPlayer = this;
s.QueuerName = username.TrimTo(10);
_playlist.Add(s);
});
}
public void AddSong(Song s, int index)
{
if (s == null)
throw new ArgumentNullException(nameof(s));
ActionQueue.Enqueue(() =>
{
_playlist.Insert(index, s);
});
}
public void RemoveSong(Song s)
{
if (s == null)
throw new ArgumentNullException(nameof(s));
ActionQueue.Enqueue(() =>
{
_playlist.Remove(s);
});
}
public void RemoveSongAt(int index, bool silent = false)
{
ActionQueue.Enqueue(() =>
{
if (index < 0 || index >= _playlist.Count)
return;
var song = _playlist.ElementAtOrDefault(index);
if (_playlist.Remove(song) && !silent)
{
SongRemoved(song, index);
}
});
}
public void ClearQueue()
{
ActionQueue.Enqueue(() =>
{
_playlist.Clear();
});
}
public async Task UpdateSongDurationsAsync()
{
var curSong = CurrentSong;
var toUpdate = _playlist.Where(s => s.SongInfo.ProviderType == MusicType.Normal &&
s.TotalTime == TimeSpan.Zero)
.ToArray();
if (curSong != null)
{
Array.Resize(ref toUpdate, toUpdate.Length + 1);
toUpdate[toUpdate.Length - 1] = curSong;
}
var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3))
.Distinct();
var durations = await _google.GetVideoDurationsAsync(ids);
toUpdate.ForEach(s =>
{
foreach (var kvp in durations)
{
if (s.SongInfo.Query.EndsWith(kvp.Key))
{
s.TotalTime = kvp.Value;
return;
}
}
});
}
public void Destroy()
{
ActionQueue.Enqueue(async () =>
{
RepeatPlaylist = false;
RepeatSong = false;
Autoplay = false;
Destroyed = true;
_playlist.Clear();
try { await AudioClient.StopAsync(); } catch { }
if (!SongCancelSource.IsCancellationRequested)
SongCancelSource.Cancel();
});
}
//public async Task MoveToVoiceChannel(IVoiceChannel voiceChannel)
//{
// if (audioClient?.ConnectionState != ConnectionState.Connected)
// throw new InvalidOperationException("Can't move while bot is not connected to voice channel.");
// PlaybackVoiceChannel = voiceChannel;
// audioClient = await voiceChannel.ConnectAsync().ConfigureAwait(false);
//}
public bool ToggleRepeatSong() => RepeatSong = !RepeatSong;
public bool ToggleRepeatPlaylist() => RepeatPlaylist = !RepeatPlaylist;
public bool ToggleAutoplay() => Autoplay = !Autoplay;
public void ThrowIfQueueFull()
{
if (MaxQueueSize == 0)
return;
if (_playlist.Count >= MaxQueueSize)
throw new PlaylistFullException();
}
}
}

View File

@ -0,0 +1,657 @@
using Discord;
using Discord.Audio;
using System;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using System.Diagnostics;
namespace NadekoBot.Services.Music
{
public enum StreamState
{
Resolving,
Queued,
Playing,
Completed
}
public class MusicPlayer : IDisposable
{
private readonly Task _player;
private readonly IVoiceChannel VoiceChannel;
private readonly Logger _log;
private MusicQueue Queue { get; } = new MusicQueue();
public bool Exited { get; set; } = false;
public bool Stopped { get; private set; } = false;
public float Volume { get; private set; } = 1.0f;
public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%";
private TaskCompletionSource<bool> pauseTaskSource { get; set; } = null;
private CancellationTokenSource SongCancelSource { get; set; }
public ITextChannel OutputTextChannel { get; set; }
public (int Index, SongInfo Current) Current
{
get
{
if (Stopped)
return (0, null);
return Queue.Current;
}
}
public bool RepeatCurrentSong { get; private set; }
private IAudioClient _audioClient;
private readonly object locker = new object();
#region events
public event Action<MusicPlayer, SongInfo> OnStarted;
public event Action<MusicPlayer, SongInfo> OnCompleted;
public event Action<MusicPlayer, bool> OnPauseChanged;
#endregion
public MusicPlayer(MusicService musicService, IVoiceChannel vch, ITextChannel output, float volume)
{
_log = LogManager.GetCurrentClassLogger();
this.Volume = volume;
this.VoiceChannel = vch;
this.SongCancelSource = new CancellationTokenSource();
this.OutputTextChannel = output;
_player = Task.Run(async () =>
{
while (!Exited)
{
CancellationToken cancelToken;
(int Index, SongInfo Song) data;
lock (locker)
{
data = Queue.Current;
cancelToken = SongCancelSource.Token;
}
try
{
_log.Info("Checking for songs");
if (data.Song == null)
continue;
_log.Info("Connecting");
_log.Info("Starting");
var p = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"-i {data.Song.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true,
});
var ac = await GetAudioClient();
if (ac == null)
continue;
var pcm = ac.CreatePCMStream(AudioApplication.Music);
OnStarted?.Invoke(this, data.Song);
byte[] buffer = new byte[3840];
int bytesRead = 0;
try
{
while ((bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) > 0)
{
var vol = Volume;
if (vol != 1)
AdjustVolume(buffer, vol);
await pcm.WriteAsync(buffer, 0, bytesRead, cancelToken);
await (pauseTaskSource?.Task ?? Task.CompletedTask);
}
}
catch (OperationCanceledException)
{
_log.Info("Song Canceled");
}
catch (Exception ex)
{
_log.Warn(ex);
}
finally
{
//flush is known to get stuck from time to time, just cancel it if it takes more than 1 second
var flushCancel = new CancellationTokenSource();
var flushToken = flushCancel.Token;
var flushDelay = Task.Delay(1000, flushToken);
await Task.WhenAny(flushDelay, pcm.FlushAsync(flushToken));
flushCancel.Cancel();
OnCompleted?.Invoke(this, data.Song);
}
}
finally
{
_log.Info("Next song");
do
{
await Task.Delay(100);
}
while (Stopped && !Exited);
if(!RepeatCurrentSong)
Queue.Next();
}
}
}, SongCancelSource.Token);
}
private async Task<IAudioClient> GetAudioClient(bool reconnect = false)
{
if (_audioClient == null ||
_audioClient.ConnectionState != ConnectionState.Connected ||
reconnect)
try
{
_audioClient = await VoiceChannel.ConnectAsync();
}
catch
{
return null;
}
return _audioClient;
}
public (bool Success, int Index) Enqueue(SongInfo song)
{
_log.Info("Adding song");
Queue.Add(song);
return (true, Queue.Count);
}
public void Next()
{
lock (locker)
{
Stopped = false;
Unpause();
}
CancelCurrentSong();
}
public void Stop(bool clearQueue = false)
{
lock (locker)
{
Stopped = true;
Queue.ResetCurrent();
if (clearQueue)
Queue.Clear();
Unpause();
}
CancelCurrentSong();
}
private void Unpause()
{
if (pauseTaskSource != null)
{
pauseTaskSource.TrySetResult(true);
pauseTaskSource = null;
}
}
public void TogglePause()
{
lock (locker)
{
if (pauseTaskSource == null)
pauseTaskSource = new TaskCompletionSource<bool>();
else
{
Unpause();
}
}
OnPauseChanged?.Invoke(this, pauseTaskSource != null);
}
public void SetVolume(int volume)
{
if (volume < 0 || volume > 100)
throw new ArgumentOutOfRangeException(nameof(volume));
Volume = ((float)volume) / 100;
}
public SongInfo RemoveAt(int index)
{
lock (locker)
{
var cur = Queue.Current;
if (cur.Index == index)
Next();
return Queue.RemoveAt(index);
}
}
private void CancelCurrentSong()
{
lock (locker)
{
var cs = SongCancelSource;
SongCancelSource = new CancellationTokenSource();
cs.Cancel();
}
}
public void ClearQueue()
{
lock (locker)
{
Queue.Clear();
}
}
public (int CurrentIndex, SongInfo[] Songs) QueueArray()
=> Queue.ToArray();
//aidiakapi ftw
public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume)
{
if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples;
// 16-bit precision for the multiplication
var volumeFixed = (int)Math.Round(volume * 65536d);
var count = audioSamples.Length / 2;
fixed (byte* srcBytes = audioSamples)
{
var src = (short*)srcBytes;
for (var i = count; i != 0; i--, src++)
*src = (short)(((*src) * volumeFixed) >> 16);
}
return audioSamples;
}
public bool ToggleRepeatSong()
{
lock (locker)
{
return RepeatCurrentSong = !RepeatCurrentSong;
}
}
public void Dispose()
{
_log.Info("Disposing");
lock (locker)
{
Exited = true;
Unpause();
}
CancelCurrentSong();
OnCompleted = null;
OnPauseChanged = null;
OnStarted = null;
}
//private IAudioClient AudioClient { get; set; }
///// <summary>
///// Player will prioritize different queuer name
///// over the song position in the playlist
///// </summary>
//public bool FairPlay { get; set; } = false;
///// <summary>
///// Song will stop playing after this amount of time.
///// To prevent people queueing radio or looped songs
///// while other people want to listen to other songs too.
///// </summary>
//public uint MaxPlaytimeSeconds { get; set; } = 0;
//// this should be written better
//public TimeSpan TotalPlaytime =>
// _playlist.Any(s => s.TotalTime == TimeSpan.MaxValue) ?
// TimeSpan.MaxValue :
// new TimeSpan(_playlist.Sum(s => s.TotalTime.Ticks));
///// <summary>
///// Users who recently got their music wish
///// </summary>
//private ConcurrentHashSet<string> RecentlyPlayedUsers { get; } = new ConcurrentHashSet<string>();
//private readonly List<Song> _playlist = new List<Song>();
//private readonly Logger _log;
//private readonly IGoogleApiService _google;
//public IReadOnlyCollection<Song> Playlist => _playlist;
//public Song CurrentSong { get; private set; }
//public CancellationTokenSource SongCancelSource { get; private set; }
//private CancellationToken CancelToken { get; set; }
//public bool Paused { get; set; }
//public float Volume { get; private set; }
//public event Action<MusicPlayer, Song> OnCompleted = delegate { };
//public event Action<MusicPlayer, Song> OnStarted = delegate { };
//public event Action<bool> OnPauseChanged = delegate { };
//public IVoiceChannel PlaybackVoiceChannel { get; private set; }
//public ITextChannel OutputTextChannel { get; set; }
//private bool Destroyed { get; set; }
//public bool RepeatSong { get; private set; }
//public bool RepeatPlaylist { get; private set; }
//public bool Autoplay { get; set; }
//public uint MaxQueueSize { get; set; } = 0;
//private ConcurrentQueue<Action> ActionQueue { get; } = new ConcurrentQueue<Action>();
//public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%";
//public event Action<Song, int> SongRemoved = delegate { };
//public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume, IGoogleApiService google)
//{
// _log = LogManager.GetCurrentClassLogger();
// _google = google;
// OutputTextChannel = outputChannel;
// Volume = defaultVolume ?? 1.0f;
// PlaybackVoiceChannel = startingVoiceChannel ?? throw new ArgumentNullException(nameof(startingVoiceChannel));
// SongCancelSource = new CancellationTokenSource();
// CancelToken = SongCancelSource.Token;
// Task.Run(async () =>
// {
// try
// {
// while (!Destroyed)
// {
// try
// {
// if (ActionQueue.TryDequeue(out Action action))
// {
// action();
// }
// }
// finally
// {
// await Task.Delay(100).ConfigureAwait(false);
// }
// }
// }
// catch (Exception ex)
// {
// _log.Warn("Action queue crashed");
// _log.Warn(ex);
// }
// }).ConfigureAwait(false);
// var t = new Thread(async () =>
// {
// while (!Destroyed)
// {
// try
// {
// CurrentSong = GetNextSong();
// if (CurrentSong == null)
// continue;
// while (AudioClient?.ConnectionState == ConnectionState.Disconnecting ||
// AudioClient?.ConnectionState == ConnectionState.Connecting)
// {
// _log.Info("Waiting for Audio client");
// await Task.Delay(200).ConfigureAwait(false);
// }
// if (AudioClient == null || AudioClient.ConnectionState == ConnectionState.Disconnected)
// AudioClient = await PlaybackVoiceChannel.ConnectAsync().ConfigureAwait(false);
// var index = _playlist.IndexOf(CurrentSong);
// if (index != -1)
// RemoveSongAt(index, true);
// OnStarted(this, CurrentSong);
// try
// {
// await CurrentSong.Play(AudioClient, CancelToken);
// }
// catch (OperationCanceledException)
// {
// }
// finally
// {
// OnCompleted(this, CurrentSong);
// }
// if (RepeatPlaylist & !RepeatSong)
// AddSong(CurrentSong, CurrentSong.QueuerName);
// if (RepeatSong)
// AddSong(CurrentSong, 0);
// }
// catch (Exception ex)
// {
// _log.Warn("Music thread almost crashed.");
// _log.Warn(ex);
// await Task.Delay(3000).ConfigureAwait(false);
// }
// finally
// {
// if (!CancelToken.IsCancellationRequested)
// {
// SongCancelSource.Cancel();
// }
// SongCancelSource = new CancellationTokenSource();
// CancelToken = SongCancelSource.Token;
// CurrentSong = null;
// await Task.Delay(300).ConfigureAwait(false);
// }
// }
// });
// t.Start();
//}
//public void Next()
//{
// ActionQueue.Enqueue(() =>
// {
// Paused = false;
// SongCancelSource.Cancel();
// });
//}
//public void Stop()
//{
// ActionQueue.Enqueue(() =>
// {
// RepeatPlaylist = false;
// RepeatSong = false;
// Autoplay = false;
// _playlist.Clear();
// if (!SongCancelSource.IsCancellationRequested)
// SongCancelSource.Cancel();
// });
//}
//public void TogglePause() => OnPauseChanged(Paused = !Paused);
//public int SetVolume(int volume)
//{
// if (volume < 0)
// volume = 0;
// if (volume > 100)
// volume = 100;
// Volume = volume / 100.0f;
// return volume;
//}
//private Song GetNextSong()
//{
// if (!FairPlay)
// {
// return _playlist.FirstOrDefault();
// }
// var song = _playlist.FirstOrDefault(c => !RecentlyPlayedUsers.Contains(c.QueuerName))
// ?? _playlist.FirstOrDefault();
// if (song == null)
// return null;
// if (RecentlyPlayedUsers.Contains(song.QueuerName))
// {
// RecentlyPlayedUsers.Clear();
// }
// RecentlyPlayedUsers.Add(song.QueuerName);
// return song;
//}
//public void Shuffle()
//{
// ActionQueue.Enqueue(() =>
// {
// var oldPlaylist = _playlist.ToArray();
// _playlist.Clear();
// _playlist.AddRange(oldPlaylist.Shuffle());
// });
//}
//public void AddSong(Song s, string username)
//{
// if (s == null)
// throw new ArgumentNullException(nameof(s));
// ThrowIfQueueFull();
// ActionQueue.Enqueue(() =>
// {
// s.MusicPlayer = this;
// s.QueuerName = username.TrimTo(10);
// _playlist.Add(s);
// });
//}
//public void AddSong(Song s, int index)
//{
// if (s == null)
// throw new ArgumentNullException(nameof(s));
// ActionQueue.Enqueue(() =>
// {
// _playlist.Insert(index, s);
// });
//}
//public void RemoveSong(Song s)
//{
// if (s == null)
// throw new ArgumentNullException(nameof(s));
// ActionQueue.Enqueue(() =>
// {
// _playlist.Remove(s);
// });
//}
//public void RemoveSongAt(int index, bool silent = false)
//{
// ActionQueue.Enqueue(() =>
// {
// if (index < 0 || index >= _playlist.Count)
// return;
// var song = _playlist.ElementAtOrDefault(index);
// if (_playlist.Remove(song) && !silent)
// {
// SongRemoved(song, index);
// }
// });
//}
//public void ClearQueue()
//{
// ActionQueue.Enqueue(() =>
// {
// _playlist.Clear();
// });
//}
//public async Task UpdateSongDurationsAsync()
//{
// var curSong = CurrentSong;
// var toUpdate = _playlist.Where(s => s.SongInfo.ProviderType == MusicType.Normal &&
// s.TotalTime == TimeSpan.Zero)
// .ToArray();
// if (curSong != null)
// {
// Array.Resize(ref toUpdate, toUpdate.Length + 1);
// toUpdate[toUpdate.Length - 1] = curSong;
// }
// var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3))
// .Distinct();
// var durations = await _google.GetVideoDurationsAsync(ids);
// toUpdate.ForEach(s =>
// {
// foreach (var kvp in durations)
// {
// if (s.SongInfo.Query.EndsWith(kvp.Key))
// {
// s.TotalTime = kvp.Value;
// return;
// }
// }
// });
//}
//public void Destroy()
//{
// ActionQueue.Enqueue(async () =>
// {
// RepeatPlaylist = false;
// RepeatSong = false;
// Autoplay = false;
// Destroyed = true;
// _playlist.Clear();
// try { await AudioClient.StopAsync(); } catch { }
// if (!SongCancelSource.IsCancellationRequested)
// SongCancelSource.Cancel();
// });
//}
////public async Task MoveToVoiceChannel(IVoiceChannel voiceChannel)
////{
//// if (audioClient?.ConnectionState != ConnectionState.Connected)
//// throw new InvalidOperationException("Can't move while bot is not connected to voice channel.");
//// PlaybackVoiceChannel = voiceChannel;
//// audioClient = await voiceChannel.ConnectAsync().ConfigureAwait(false);
////}
//public bool ToggleRepeatSong() => RepeatSong = !RepeatSong;
//public bool ToggleRepeatPlaylist() => RepeatPlaylist = !RepeatPlaylist;
//public bool ToggleAutoplay() => Autoplay = !Autoplay;
//public void ThrowIfQueueFull()
//{
// if (MaxQueueSize == 0)
// return;
// if (_playlist.Count >= MaxQueueSize)
// throw new PlaylistFullException();
//}
}
}

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Music
{
public class MusicQueue : IDisposable
{
private LinkedList<SongInfo> Songs { get; } = new LinkedList<SongInfo>();
private int _currentIndex = 0;
private int CurrentIndex
{
get
{
return _currentIndex;
}
set
{
lock (locker)
{
if (Songs.Count == 0)
_currentIndex = 0;
else
_currentIndex = value %= Songs.Count;
}
}
}
public (int Index, SongInfo Song) Current
{
get
{
var cur = CurrentIndex;
return (cur, Songs.ElementAtOrDefault(cur));
}
}
private readonly object locker = new object();
private TaskCompletionSource<bool> nextSource { get; } = new TaskCompletionSource<bool>();
public int Count
{
get
{
lock (locker)
{
return Songs.Count;
}
}
}
public void Add(SongInfo song)
{
lock (locker)
{
Songs.AddLast(song);
}
}
public void Next()
{
CurrentIndex++;
}
public void Dispose()
{
Clear();
}
public SongInfo RemoveAt(int index)
{
lock (locker)
{
if (index < 0 || index >= Songs.Count)
throw new ArgumentOutOfRangeException(nameof(index));
var current = Songs.First;
for (int i = 0; i < Songs.Count; i++)
{
if (i == index)
{
Songs.Remove(current);
if (CurrentIndex != 0)
{
if (CurrentIndex >= index)
{
--CurrentIndex;
}
}
break;
}
}
return current.Value;
}
}
public void Clear()
{
lock (locker)
{
Songs.Clear();
CurrentIndex = 0;
}
}
public (int, SongInfo[]) ToArray()
{
lock (locker)
{
return (CurrentIndex, Songs.ToArray());
}
}
public void ResetCurrent()
{
CurrentIndex = 0;
}
}
}

View File

@ -11,6 +11,7 @@ using System.IO;
using VideoLibrary; using VideoLibrary;
using System.Net.Http; using System.Net.Http;
using System.Collections.Generic; using System.Collections.Generic;
using Discord.Commands;
namespace NadekoBot.Services.Music namespace NadekoBot.Services.Music
{ {
@ -48,28 +49,49 @@ namespace NadekoBot.Services.Music
Directory.CreateDirectory(MusicDataPath); Directory.CreateDirectory(MusicDataPath);
} }
public MusicPlayer GetPlayer(ulong guildId) // public MusicPlayer GetPlayer(ulong guildId)
// {
// MusicPlayers.TryGetValue(guildId, out var player);
// return player;
// }
public float GetDefaultVolume(ulong guildId)
{ {
MusicPlayers.TryGetValue(guildId, out var player); return _defaultVolumes.GetOrAdd(guildId, (id) =>
return player; {
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume;
}
});
} }
public MusicPlayer GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh) public Task<MusicPlayer> GetOrCreatePlayer(ICommandContext context)
{
var gUsr = (IGuildUser)context.User;
var txtCh = (ITextChannel)context.Channel;
var vCh = gUsr.VoiceChannel;
return GetOrCreatePlayer(context.Guild.Id, vCh, txtCh);
}
public async Task<MusicPlayer> GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh)
{ {
string GetText(string text, params object[] replacements) => string GetText(string text, params object[] replacements) =>
_strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements); _strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
return MusicPlayers.GetOrAdd(guildId, server => if (voiceCh == null || voiceCh.Guild != textCh.Guild)
{ {
var vol = _defaultVolumes.GetOrAdd(guildId, (id) => if (textCh != null)
{ {
using (var uow = _db.UnitOfWork) await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
{ }
return uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume; throw new ArgumentException(nameof(voiceCh));
} }
});
return MusicPlayers.GetOrAdd(guildId, _ =>
{
var vol = GetDefaultVolume(guildId);
var mp = new MusicPlayer(this, voiceCh, textCh, vol);
var mp = new MusicPlayer(voiceCh, textCh, vol, _google);
IUserMessage playingMessage = null; IUserMessage playingMessage = null;
IUserMessage lastFinishedMessage = null; IUserMessage lastFinishedMessage = null;
mp.OnCompleted += async (s, song) => mp.OnCompleted += async (s, song) =>
@ -91,30 +113,30 @@ namespace NadekoBot.Services.Music
// ignored // ignored
} }
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal) //todo autoplay should be independent from event handlers
{ //if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal)
var relatedVideos = (await _google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList(); //{
if (relatedVideos.Count > 0) // var relatedVideos = (await _google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList();
await QueueSong(await textCh.Guild.GetCurrentUserAsync(), // if (relatedVideos.Count > 0)
textCh, // await QueueSong(await textCh.Guild.GetCurrentUserAsync(),
voiceCh, // textCh,
relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)], // voiceCh,
true).ConfigureAwait(false); // relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)],
} // true).ConfigureAwait(false);
//}
} }
catch catch
{ {
// ignored // ignored
} }
}; };
mp.OnStarted += async (player, song) => mp.OnStarted += async (player, song) =>
{ {
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); } //try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
catch //catch
{ //{
// ignored // // ignored
} //}
var sender = player; var sender = player;
if (sender == null) if (sender == null)
return; return;
@ -125,7 +147,7 @@ namespace NadekoBot.Services.Music
playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon()) .WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon())
.WithDescription(song.PrettyName) .WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo))) .WithFooter(ef => ef.WithText(mp.PrettyVolume + " | " + song.PrettyInfo)))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
catch catch
@ -133,7 +155,7 @@ namespace NadekoBot.Services.Music
// ignored // ignored
} }
}; };
mp.OnPauseChanged += async (paused) => mp.OnPauseChanged += async (player, paused) =>
{ {
try try
{ {
@ -150,291 +172,228 @@ namespace NadekoBot.Services.Music
// ignored // ignored
} }
}; };
//mp.SongRemoved += async (song, index) =>
//{
// try
// {
// var embed = new EmbedBuilder()
// .WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon())
// .WithDescription(song.PrettyName)
// .WithFooter(ef => ef.WithText(song.PrettyInfo))
// .WithErrorColor();
mp.SongRemoved += async (song, index) => // await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false);
{
try
{
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo))
.WithErrorColor();
await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false); // }
// catch
} // {
catch // // ignored
{ // }
// ignored //};
}
};
return mp; return mp;
}); });
} }
public async Task<SongInfo> ResolveSong(string query, string queuerName, MusicType musicType = MusicType.Normal)
public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
{ {
string GetText(string text, params object[] replacements) => query.ThrowIfNull(nameof(query));
_strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
if (voiceCh == null || voiceCh.Guild != textCh.Guild) SongInfo sinfo;
{
if (!silent)
await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
throw new ArgumentNullException(nameof(voiceCh));
}
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
throw new ArgumentException("Invalid song query.", nameof(query));
var musicPlayer = GetOrCreatePlayer(textCh.Guild.Id, voiceCh, textCh); sinfo = await ResolveYoutubeSong(query, queuerName).ConfigureAwait(false);
Song resolvedSong;
try
{
musicPlayer.ThrowIfQueueFull();
resolvedSong = await ResolveSong(query, musicType).ConfigureAwait(false);
if (resolvedSong == null) return sinfo;
throw new SongNotFoundException(); }
musicPlayer.AddSong(resolvedSong, queuer.Username); public async Task<SongInfo> ResolveYoutubeSong(string query, string queuerName)
} {
catch (PlaylistFullException) var link = (await _google.GetVideoLinksByKeywordAsync(query).ConfigureAwait(false)).FirstOrDefault();
if (string.IsNullOrWhiteSpace(link))
throw new OperationCanceledException("Not a valid youtube query.");
var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
var video = videos
.Where(v => v.AudioBitrate < 256)
.OrderByDescending(v => v.AudioBitrate)
.FirstOrDefault();
if (video == null) // do something with this error
throw new Exception("Could not load any video elements based on the query.");
//var m = Regex.Match(query, @"\?t=(?<t>\d*)");
//int gotoTime = 0;
//if (m.Captures.Count > 0)
// int.TryParse(m.Groups["t"].ToString(), out gotoTime);
var song = new SongInfo
{ {
try Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
{ Provider = "YouTube",
await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize)); Uri = await video.GetUriAsync().ConfigureAwait(false),
} Query = link,
catch ProviderType = MusicType.Normal,
{ QueuerName = queuerName
// ignored };
} return song;
throw;
}
if (!silent)
{
try
{
//var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
.WithDescription($"{resolvedSong.PrettyName}\n{GetText("queue")} ")
.WithThumbnailUrl(resolvedSong.Thumbnail)
.WithFooter(ef => ef.WithText(resolvedSong.PrettyProvider)))
.ConfigureAwait(false);
queuedMessage?.DeleteAfter(10);
}
catch
{
// ignored
} // if queued message sending fails, don't attempt to delete it
}
} }
public void DestroyPlayer(ulong id) public void DestroyPlayer(ulong id)
{ {
if (MusicPlayers.TryRemove(id, out var mp)) if (MusicPlayers.TryRemove(id, out var mp))
mp.Destroy(); mp.Dispose();
} }
// public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
// {
// string GetText(string text, params object[] replacements) =>
// _strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
public async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal) //if (string.IsNullOrWhiteSpace(query) || query.Length< 3)
{ // throw new ArgumentException("Invalid song query.", nameof(query));
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
if (musicType != MusicType.Local && IsRadioLink(query)) // var musicPlayer = GetOrCreatePlayer(textCh.Guild.Id, voiceCh, textCh);
{ // Song resolvedSong;
musicType = MusicType.Radio; // try
query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query; // {
} // musicPlayer.ThrowIfQueueFull();
// resolvedSong = await ResolveSong(query, musicType).ConfigureAwait(false);
try // if (resolvedSong == null)
{ // throw new SongNotFoundException();
switch (musicType)
{
case MusicType.Local:
return new Song(new SongInfo
{
Uri = "\"" + Path.GetFullPath(query) + "\"",
Title = Path.GetFileNameWithoutExtension(query),
Provider = "Local File",
ProviderType = musicType,
Query = query,
});
case MusicType.Radio:
return new Song(new SongInfo
{
Uri = query,
Title = $"{query}",
Provider = "Radio Stream",
ProviderType = musicType,
Query = query
})
{ TotalTime = TimeSpan.MaxValue };
}
if (_sc.IsSoundCloudLink(query))
{
var svideo = await _sc.ResolveVideoAsync(query).ConfigureAwait(false);
return new Song(new SongInfo
{
Title = svideo.FullName,
Provider = "SoundCloud",
Uri = await svideo.StreamLink(),
ProviderType = musicType,
Query = svideo.TrackLink,
AlbumArt = svideo.artwork_url,
})
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
}
if (musicType == MusicType.Soundcloud) // musicPlayer.AddSong(resolvedSong, queuer.Username);
{ // }
var svideo = await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false); // catch (PlaylistFullException)
return new Song(new SongInfo // {
{ // try
Title = svideo.FullName, // {
Provider = "SoundCloud", // await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize));
Uri = await svideo.StreamLink(), // }
ProviderType = MusicType.Soundcloud, // catch
Query = svideo.TrackLink, // {
AlbumArt = svideo.artwork_url, // // ignored
}) // }
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; // throw;
} // }
// if (!silent)
// {
// try
// {
// //var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
// var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
// .WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
// .WithDescription($"{resolvedSong.PrettyName}\n{GetText("queue")} ")
// .WithThumbnailUrl(resolvedSong.Thumbnail)
// .WithFooter(ef => ef.WithText(resolvedSong.PrettyProvider)))
// .ConfigureAwait(false);
// queuedMessage?.DeleteAfter(10);
// }
// catch
// {
// // ignored
// } // if queued message sending fails, don't attempt to delete it
// }
// }
var link = (await _google.GetVideoLinksByKeywordAsync(query).ConfigureAwait(false)).FirstOrDefault();
if (string.IsNullOrWhiteSpace(link))
throw new OperationCanceledException("Not a valid youtube query.");
var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
var video = videos
.Where(v => v.AudioBitrate < 256)
.OrderByDescending(v => v.AudioBitrate)
.FirstOrDefault();
if (video == null) // do something with this error
throw new Exception("Could not load any video elements based on the query.");
var m = Regex.Match(query, @"\?t=(?<t>\d*)");
int gotoTime = 0;
if (m.Captures.Count > 0)
int.TryParse(m.Groups["t"].ToString(), out gotoTime);
var song = new Song(new SongInfo
{
Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
Provider = "YouTube",
Uri = await video.GetUriAsync().ConfigureAwait(false),
Query = link,
ProviderType = musicType,
});
song.SkipTo = gotoTime;
return song;
}
catch (Exception ex)
{
_log.Warn($"Failed resolving the link.{ex.Message}");
_log.Warn(ex);
return null;
}
}
private async Task<string> HandleStreamContainers(string query)
{
string file = null;
try
{
using (var http = new HttpClient())
{
file = await http.GetStringAsync(query).ConfigureAwait(false);
}
}
catch
{
return query;
}
if (query.Contains(".pls"))
{
//File1=http://armitunes.com:8000/
//Regex.Match(query)
try
{
var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .pls:\n{file}");
return null;
}
}
if (query.Contains(".m3u"))
{
/*
# This is a comment
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
*/
try
{
var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .m3u:\n{file}");
return null;
}
}
if (query.Contains(".asx"))
{
//<ref href="http://armitunes.com:8000"/>
try
{
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .asx:\n{file}");
return null;
}
}
if (query.Contains(".xspf"))
{
/*
<?xml version="1.0" encoding="UTF-8"?>
<playlist version="1" xmlns="http://xspf.org/ns/0/">
<trackList>
<track><location>file:///mp3s/song_1.mp3</location></track>
*/
try
{
var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .xspf:\n{file}");
return null;
}
}
return query;
}
private bool IsRadioLink(string query) => // private async Task<string> HandleStreamContainers(string query)
(query.StartsWith("http") || // {
query.StartsWith("ww")) // string file = null;
&& // try
(query.Contains(".pls") || // {
query.Contains(".m3u") || // using (var http = new HttpClient())
query.Contains(".asx") || // {
query.Contains(".xspf")); // file = await http.GetStringAsync(query).ConfigureAwait(false);
// }
// }
// catch
// {
// return query;
// }
// if (query.Contains(".pls"))
// {
// //File1=http://armitunes.com:8000/
// //Regex.Match(query)
// try
// {
// var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
// var res = m.Groups["url"]?.ToString();
// return res?.Trim();
// }
// catch
// {
// _log.Warn($"Failed reading .pls:\n{file}");
// return null;
// }
// }
// if (query.Contains(".m3u"))
// {
// /*
//# This is a comment
// C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
// C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
// */
// try
// {
// var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
// var res = m.Groups["url"]?.ToString();
// return res?.Trim();
// }
// catch
// {
// _log.Warn($"Failed reading .m3u:\n{file}");
// return null;
// }
// }
// if (query.Contains(".asx"))
// {
// //<ref href="http://armitunes.com:8000"/>
// try
// {
// var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
// var res = m.Groups["url"]?.ToString();
// return res?.Trim();
// }
// catch
// {
// _log.Warn($"Failed reading .asx:\n{file}");
// return null;
// }
// }
// if (query.Contains(".xspf"))
// {
// /*
// <?xml version="1.0" encoding="UTF-8"?>
// <playlist version="1" xmlns="http://xspf.org/ns/0/">
// <trackList>
// <track><location>file:///mp3s/song_1.mp3</location></track>
// */
// try
// {
// var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
// var res = m.Groups["url"]?.ToString();
// return res?.Trim();
// }
// catch
// {
// _log.Warn($"Failed reading .xspf:\n{file}");
// return null;
// }
// }
// return query;
// }
// private bool IsRadioLink(string query) =>
// (query.StartsWith("http") ||
// query.StartsWith("ww"))
// &&
// (query.Contains(".pls") ||
// query.Contains(".m3u") ||
// query.Contains(".asx") ||
// query.Contains(".xspf"));
} }
} }

View File

@ -1,296 +1,246 @@
using Discord.Audio; using NadekoBot.Extensions;
using NadekoBot.Extensions;
using NLog;
using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Net; using System.Net;
using Discord; using Discord;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System;
namespace NadekoBot.Services.Music namespace NadekoBot.Services.Music
{ {
public class SongInfo //public class Song
{ //{
public string Provider { get; set; } // public SongInfo SongInfo { get; }
public MusicType ProviderType { get; set; } // public MusicPlayer MusicPlayer { get; set; }
public string Query { get; set; }
public string Title { get; set; }
public string Uri { get; set; }
public string AlbumArt { get; set; }
}
public class Song // private string _queuerName;
{ // public string QueuerName { get{
public SongInfo SongInfo { get; } // return Format.Sanitize(_queuerName);
public MusicPlayer MusicPlayer { get; set; } // } set { _queuerName = value; } }
private string _queuerName; // public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
public string QueuerName { get{ // public TimeSpan CurrentTime => TimeSpan.FromSeconds(BytesSent / (float)_frameBytes / (1000 / (float)_milliseconds));
return Format.Sanitize(_queuerName);
} set { _queuerName = value; } }
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; // private const int _milliseconds = 20;
public TimeSpan CurrentTime => TimeSpan.FromSeconds(BytesSent / (float)_frameBytes / (1000 / (float)_milliseconds)); // private const int _samplesPerFrame = (48000 / 1000) * _milliseconds;
// private const int _frameBytes = 3840; //16-bit, 2 channels
private const int _milliseconds = 20; // private ulong BytesSent { get; set; }
private const int _samplesPerFrame = (48000 / 1000) * _milliseconds;
private const int _frameBytes = 3840; //16-bit, 2 channels
private ulong BytesSent { get; set; } // //pwetty
//pwetty // public string PrettyProvider =>
// $"{(SongInfo.Provider ?? "???")}";
public string PrettyProvider => // public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
$"{(SongInfo.Provider ?? "???")}";
public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime; // public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**";
public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**"; // public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {PrettyTotalTime} | {PrettyProvider} | {QueuerName}";
public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {PrettyTotalTime} | {PrettyProvider} | {QueuerName}"; // public string PrettyFullName => $"{PrettyName}\n\t\t`{PrettyTotalTime} | {PrettyProvider} | {QueuerName}`";
public string PrettyFullName => $"{PrettyName}\n\t\t`{PrettyTotalTime} | {PrettyProvider} | {QueuerName}`"; // public string PrettyCurrentTime {
// get {
// var time = CurrentTime.ToString(@"mm\:ss");
// var hrs = (int)CurrentTime.TotalHours;
public string PrettyCurrentTime { // if (hrs > 0)
get { // return hrs + ":" + time;
var time = CurrentTime.ToString(@"mm\:ss"); // else
var hrs = (int)CurrentTime.TotalHours; // return time;
// }
// }
if (hrs > 0) // public string PrettyTotalTime {
return hrs + ":" + time; // get
else // {
return time; // if (TotalTime == TimeSpan.Zero)
} // return "(?)";
} // if (TotalTime == TimeSpan.MaxValue)
// return "∞";
// var time = TotalTime.ToString(@"mm\:ss");
// var hrs = (int)TotalTime.TotalHours;
public string PrettyTotalTime { // if (hrs > 0)
get // return hrs + ":" + time;
{ // return time;
if (TotalTime == TimeSpan.Zero) // }
return "(?)"; // }
if (TotalTime == TimeSpan.MaxValue)
return "∞";
var time = TotalTime.ToString(@"mm\:ss");
var hrs = (int)TotalTime.TotalHours;
if (hrs > 0) // public string Thumbnail {
return hrs + ":" + time; // get {
return time; // switch (SongInfo.ProviderType)
} // {
} // case MusicType.Radio:
// return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links
// case MusicType.Normal:
// //todo 50 have videoid in songinfo from the start
// var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+");
// return $"https://img.youtube.com/vi/{ videoId }/0.jpg";
// case MusicType.Local:
// return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links
// case MusicType.Soundcloud:
// return SongInfo.AlbumArt;
// default:
// return "";
// }
// }
// }
public string Thumbnail { // public string SongUrl {
get { // get {
switch (SongInfo.ProviderType) // switch (SongInfo.ProviderType)
{ // {
case MusicType.Radio: // case MusicType.Normal:
return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links // return SongInfo.Query;
case MusicType.Normal: // case MusicType.Soundcloud:
//todo 50 have videoid in songinfo from the start // return SongInfo.Query;
var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+"); // case MusicType.Local:
return $"https://img.youtube.com/vi/{ videoId }/0.jpg"; // return $"https://google.com/search?q={ WebUtility.UrlEncode(SongInfo.Title).Replace(' ', '+') }";
case MusicType.Local: // case MusicType.Radio:
return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links // return $"https://google.com/search?q={SongInfo.Title}";
case MusicType.Soundcloud: // default:
return SongInfo.AlbumArt; // return "";
default: // }
return ""; // }
} // }
}
}
public string SongUrl { // private readonly Logger _log;
get {
switch (SongInfo.ProviderType)
{
case MusicType.Normal:
return SongInfo.Query;
case MusicType.Soundcloud:
return SongInfo.Query;
case MusicType.Local:
return $"https://google.com/search?q={ WebUtility.UrlEncode(SongInfo.Title).Replace(' ', '+') }";
case MusicType.Radio:
return $"https://google.com/search?q={SongInfo.Title}";
default:
return "";
}
}
}
public int SkipTo { get; set; } // public Song(SongInfo songInfo)
// {
// SongInfo = songInfo;
// _log = LogManager.GetCurrentClassLogger();
// }
private readonly Logger _log; // public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
// {
// BytesSent = (ulong) SkipTo * 3840 * 50;
// var filename = Path.Combine(MusicService.MusicDataPath, DateTime.UtcNow.UnixTimestamp().ToString());
public Song(SongInfo songInfo) // var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
{ // var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
SongInfo = songInfo;
_log = LogManager.GetCurrentClassLogger();
}
public Song Clone() // try
{ // {
var s = new Song(SongInfo) // var attempt = 0;
{
MusicPlayer = MusicPlayer,
QueuerName = QueuerName
};
return s;
}
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) // var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy
{ // var finished = false;
BytesSent = (ulong) SkipTo * 3840 * 50; // var count = 0;
var filename = Path.Combine(MusicService.MusicDataPath, DateTime.UtcNow.UnixTimestamp().ToString()); // var sw = new Stopwatch();
// var slowconnection = false;
// sw.Start();
// while (!finished)
// {
// var t = await Task.WhenAny(prebufferingTask, Task.Delay(2000, cancelToken));
// if (t != prebufferingTask)
// {
// count++;
// if (count == 10)
// {
// slowconnection = true;
// prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 20.MiB());
// _log.Warn("Slow connection buffering more to ensure no disruption, consider hosting in cloud");
// continue;
// }
var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100); // if (inStream.BufferingCompleted && count == 1)
var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false); // {
// _log.Debug("Prebuffering canceled. Cannot get any data from the stream.");
// return;
// }
// else
// {
// continue;
// }
// }
// else if (prebufferingTask.IsCanceled)
// {
// _log.Debug("Prebuffering canceled. Cannot get any data from the stream.");
// return;
// }
// finished = true;
// }
// sw.Stop();
// _log.Debug("Prebuffering successfully completed in " + sw.Elapsed);
try // var outStream = voiceClient.CreatePCMStream(AudioApplication.Music);
{
var attempt = 0;
var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy // int nextTime = Environment.TickCount + _milliseconds;
var finished = false;
var count = 0;
var sw = new Stopwatch();
var slowconnection = false;
sw.Start();
while (!finished)
{
var t = await Task.WhenAny(prebufferingTask, Task.Delay(2000, cancelToken));
if (t != prebufferingTask)
{
count++;
if (count == 10)
{
slowconnection = true;
prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 20.MiB());
_log.Warn("Slow connection buffering more to ensure no disruption, consider hosting in cloud");
continue;
}
if (inStream.BufferingCompleted && count == 1) // byte[] buffer = new byte[_frameBytes];
{ // while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason
_log.Debug("Prebuffering canceled. Cannot get any data from the stream."); // !(MusicPlayer.MaxPlaytimeSeconds != 0 && CurrentTime.TotalSeconds >= MusicPlayer.MaxPlaytimeSeconds)) // or exceedded max playtime
return; // {
} // //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
else // var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
{ // //await inStream.CopyToAsync(voiceClient.OutputStream);
continue; // if (read < _frameBytes)
} // _log.Debug("read {0}", read);
} // unchecked
else if (prebufferingTask.IsCanceled) // {
{ // BytesSent += (ulong)read;
_log.Debug("Prebuffering canceled. Cannot get any data from the stream."); // }
return; // if (read < _frameBytes)
} // {
finished = true; // if (read == 0)
} // {
sw.Stop(); // if (inStream.BufferingCompleted)
_log.Debug("Prebuffering successfully completed in " + sw.Elapsed); // break;
// if (attempt++ == 20)
// {
// MusicPlayer.SongCancelSource.Cancel();
// break;
// }
// if (slowconnection)
// {
// _log.Warn("Slow connection has disrupted music, waiting a bit for buffer");
var outStream = voiceClient.CreatePCMStream(AudioApplication.Music); // await Task.Delay(1000, cancelToken).ConfigureAwait(false);
// nextTime = Environment.TickCount + _milliseconds;
// }
// else
// {
// await Task.Delay(100, cancelToken).ConfigureAwait(false);
// nextTime = Environment.TickCount + _milliseconds;
// }
// }
// else
// attempt = 0;
// }
// else
// attempt = 0;
int nextTime = Environment.TickCount + _milliseconds; // while (MusicPlayer.Paused)
// {
byte[] buffer = new byte[_frameBytes]; // await Task.Delay(200, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason // nextTime = Environment.TickCount + _milliseconds;
!(MusicPlayer.MaxPlaytimeSeconds != 0 && CurrentTime.TotalSeconds >= MusicPlayer.MaxPlaytimeSeconds)) // or exceedded max playtime // }
{
//Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//await inStream.CopyToAsync(voiceClient.OutputStream);
if (read < _frameBytes)
_log.Debug("read {0}", read);
unchecked
{
BytesSent += (ulong)read;
}
if (read < _frameBytes)
{
if (read == 0)
{
if (inStream.BufferingCompleted)
break;
if (attempt++ == 20)
{
MusicPlayer.SongCancelSource.Cancel();
break;
}
if (slowconnection)
{
_log.Warn("Slow connection has disrupted music, waiting a bit for buffer");
await Task.Delay(1000, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + _milliseconds;
}
else
{
await Task.Delay(100, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + _milliseconds;
}
}
else
attempt = 0;
}
else
attempt = 0;
while (MusicPlayer.Paused)
{
await Task.Delay(200, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + _milliseconds;
}
buffer = AdjustVolume(buffer, MusicPlayer.Volume); // buffer = AdjustVolume(buffer, MusicPlayer.Volume);
if (read != _frameBytes) continue; // if (read != _frameBytes) continue;
nextTime = unchecked(nextTime + _milliseconds); // nextTime = unchecked(nextTime + _milliseconds);
int delayMillis = unchecked(nextTime - Environment.TickCount); // int delayMillis = unchecked(nextTime - Environment.TickCount);
if (delayMillis > 0) // if (delayMillis > 0)
await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false); // await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false);
await outStream.WriteAsync(buffer, 0, read).ConfigureAwait(false); // await outStream.WriteAsync(buffer, 0, read).ConfigureAwait(false);
} // }
} // }
finally // finally
{ // {
await bufferTask; // await bufferTask;
inStream.Dispose(); // inStream.Dispose();
} // }
} // }
private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken, long size) // private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken, long size)
{ // {
while (!inStream.BufferingCompleted && inStream.Length < size) // while (!inStream.BufferingCompleted && inStream.Length < size)
{ // {
await Task.Delay(100, cancelToken); // await Task.Delay(100, cancelToken);
} // }
_log.Debug("Buffering successfull"); // _log.Debug("Buffering successfull");
} // }
//}
//aidiakapi ftw
public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume)
{
if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples;
// 16-bit precision for the multiplication
var volumeFixed = (int)Math.Round(volume * 65536d);
var count = audioSamples.Length / 2;
fixed (byte* srcBytes = audioSamples)
{
var src = (short*)srcBytes;
for (var i = count; i != 0; i--, src++)
*src = (short)(((*src) * volumeFixed) >> 16);
}
return audioSamples;
}
}
} }

View File

@ -1,219 +1,219 @@
using NadekoBot.Extensions; //using NadekoBot.Extensions;
using NLog; //using NLog;
using System; //using System;
using System.Diagnostics; //using System.Diagnostics;
using System.IO; //using System.IO;
using System.Threading; //using System.Threading;
using System.Threading.Tasks; //using System.Threading.Tasks;
namespace NadekoBot.Services.Music //namespace NadekoBot.Services.Music
{ //{
/// <summary> // /// <summary>
/// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. // /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space.
/// It also help for large music by deleting files that are already seen. // /// It also help for large music by deleting files that are already seen.
/// </summary> // /// </summary>
class SongBuffer : Stream // class SongBuffer : Stream
{ // {
public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize) // public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize)
{ // {
MusicPlayer = musicPlayer; // MusicPlayer = musicPlayer;
Basename = basename; // Basename = basename;
SongInfo = songInfo; // SongInfo = songInfo;
SkipTo = skipTo; // SkipTo = skipTo;
MaxFileSize = maxFileSize; // MaxFileSize = maxFileSize;
CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); // CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
_log = LogManager.GetCurrentClassLogger(); // _log = LogManager.GetCurrentClassLogger();
} // }
MusicPlayer MusicPlayer { get; } // MusicPlayer MusicPlayer { get; }
private string Basename { get; } // private string Basename { get; }
private SongInfo SongInfo { get; } // private SongInfo SongInfo { get; }
private int SkipTo { get; } // private int SkipTo { get; }
private int MaxFileSize { get; } = 2.MiB(); // private int MaxFileSize { get; } = 2.MiB();
private long FileNumber = -1; // private long FileNumber = -1;
private long NextFileToRead = 0; // private long NextFileToRead = 0;
public bool BufferingCompleted { get; private set; } = false; // public bool BufferingCompleted { get; private set; } = false;
private ulong CurrentBufferSize = 0; // private ulong CurrentBufferSize = 0;
private FileStream CurrentFileStream; // private FileStream CurrentFileStream;
private Logger _log; // private Logger _log;
public Task BufferSong(CancellationToken cancelToken) => // public Task BufferSong(CancellationToken cancelToken) =>
Task.Run(async () => // Task.Run(async () =>
{ // {
Process p = null; // Process p = null;
FileStream outStream = null; // FileStream outStream = null;
try // try
{ // {
p = Process.Start(new ProcessStartInfo // p = Process.Start(new ProcessStartInfo
{ // {
FileName = "ffmpeg", // FileName = "ffmpeg",
Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet", // Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false, // UseShellExecute = false,
RedirectStandardOutput = true, // RedirectStandardOutput = true,
RedirectStandardError = false, // RedirectStandardError = false,
CreateNoWindow = true, // CreateNoWindow = true,
}); // });
byte[] buffer = new byte[81920]; // byte[] buffer = new byte[81920];
int currentFileSize = 0; // int currentFileSize = 0;
ulong prebufferSize = 100ul.MiB(); // ulong prebufferSize = 100ul.MiB();
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); // outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
while (!p.HasExited) //Also fix low bandwidth // while (!p.HasExited) //Also fix low bandwidth
{ // {
int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); // int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false);
if (currentFileSize >= MaxFileSize) // if (currentFileSize >= MaxFileSize)
{ // {
try // try
{ // {
outStream.Dispose(); // outStream.Dispose();
} // }
catch { } // catch { }
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); // outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
currentFileSize = bytesRead; // currentFileSize = bytesRead;
} // }
else // else
{ // {
currentFileSize += bytesRead; // currentFileSize += bytesRead;
} // }
CurrentBufferSize += Convert.ToUInt64(bytesRead); // CurrentBufferSize += Convert.ToUInt64(bytesRead);
await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); // await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
while (CurrentBufferSize > prebufferSize) // while (CurrentBufferSize > prebufferSize)
await Task.Delay(100, cancelToken); // await Task.Delay(100, cancelToken);
} // }
BufferingCompleted = true; // BufferingCompleted = true;
} // }
catch (System.ComponentModel.Win32Exception) // catch (System.ComponentModel.Win32Exception)
{ // {
var oldclr = Console.ForegroundColor; // var oldclr = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red; // Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(@"You have not properly installed or configured FFMPEG. // Console.WriteLine(@"You have not properly installed or configured FFMPEG.
Please install and configure FFMPEG to play music. //Please install and configure FFMPEG to play music.
Check the guides for your platform on how to setup ffmpeg correctly: //Check the guides for your platform on how to setup ffmpeg correctly:
Windows Guide: https://goo.gl/OjKk8F // Windows Guide: https://goo.gl/OjKk8F
Linux Guide: https://goo.gl/ShjCUo"); // Linux Guide: https://goo.gl/ShjCUo");
Console.ForegroundColor = oldclr; // Console.ForegroundColor = oldclr;
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
Console.WriteLine($"Buffering stopped: {ex.Message}"); // Console.WriteLine($"Buffering stopped: {ex.Message}");
} // }
finally // finally
{ // {
if (outStream != null) // if (outStream != null)
outStream.Dispose(); // outStream.Dispose();
Console.WriteLine($"Buffering done."); // Console.WriteLine($"Buffering done.");
if (p != null) // if (p != null)
{ // {
try // try
{ // {
p.Kill(); // p.Kill();
} // }
catch { } // catch { }
p.Dispose(); // p.Dispose();
} // }
} // }
}); // });
/// <summary> // /// <summary>
/// Return the next file to read, and delete the old one // /// Return the next file to read, and delete the old one
/// </summary> // /// </summary>
/// <returns>Name of the file to read</returns> // /// <returns>Name of the file to read</returns>
private string GetNextFile() // private string GetNextFile()
{ // {
string filename = Basename + "-" + NextFileToRead; // string filename = Basename + "-" + NextFileToRead;
if (NextFileToRead != 0) // if (NextFileToRead != 0)
{ // {
try // try
{ // {
CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length); // CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length);
File.Delete(Basename + "-" + (NextFileToRead - 1)); // File.Delete(Basename + "-" + (NextFileToRead - 1));
} // }
catch { } // catch { }
} // }
NextFileToRead++; // NextFileToRead++;
return filename; // return filename;
} // }
private bool IsNextFileReady() // private bool IsNextFileReady()
{ // {
return NextFileToRead <= FileNumber; // return NextFileToRead <= FileNumber;
} // }
private void CleanFiles() // private void CleanFiles()
{ // {
for (long i = NextFileToRead - 1; i <= FileNumber; i++) // for (long i = NextFileToRead - 1; i <= FileNumber; i++)
{ // {
try // try
{ // {
File.Delete(Basename + "-" + i); // File.Delete(Basename + "-" + i);
} // }
catch { } // catch { }
} // }
} // }
//Stream part // //Stream part
public override bool CanRead => true; // public override bool CanRead => true;
public override bool CanSeek => false; // public override bool CanSeek => false;
public override bool CanWrite => false; // public override bool CanWrite => false;
public override long Length => (long)CurrentBufferSize; // public override long Length => (long)CurrentBufferSize;
public override long Position { get; set; } = 0; // public override long Position { get; set; } = 0;
public override void Flush() { } // public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count) // public override int Read(byte[] buffer, int offset, int count)
{ // {
int read = CurrentFileStream.Read(buffer, offset, count); // int read = CurrentFileStream.Read(buffer, offset, count);
if (read < count) // if (read < count)
{ // {
if (!BufferingCompleted || IsNextFileReady()) // if (!BufferingCompleted || IsNextFileReady())
{ // {
CurrentFileStream.Dispose(); // CurrentFileStream.Dispose();
CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); // CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
read += CurrentFileStream.Read(buffer, read + offset, count - read); // read += CurrentFileStream.Read(buffer, read + offset, count - read);
} // }
if (read < count) // if (read < count)
Array.Clear(buffer, read, count - read); // Array.Clear(buffer, read, count - read);
} // }
return read; // return read;
} // }
public override long Seek(long offset, SeekOrigin origin) // public override long Seek(long offset, SeekOrigin origin)
{ // {
throw new NotImplementedException(); // throw new NotImplementedException();
} // }
public override void SetLength(long value) // public override void SetLength(long value)
{ // {
throw new NotImplementedException(); // throw new NotImplementedException();
} // }
public override void Write(byte[] buffer, int offset, int count) // public override void Write(byte[] buffer, int offset, int count)
{ // {
throw new NotImplementedException(); // throw new NotImplementedException();
} // }
public new void Dispose() // public new void Dispose()
{ // {
CurrentFileStream.Dispose(); // CurrentFileStream.Dispose();
MusicPlayer.SongCancelSource.Cancel(); // MusicPlayer.SongCancelSource.Cancel();
CleanFiles(); // CleanFiles();
base.Dispose(); // base.Dispose();
} // }
} // }
} //}

View File

@ -0,0 +1,85 @@
using Discord;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using System;
using System.Net;
using System.Text.RegularExpressions;
namespace NadekoBot.Services.Music
{
public class SongInfo
{
public string Provider { get; set; }
public MusicType ProviderType { get; set; }
public string Query { get; set; }
public string Title { get; set; }
public string Uri { get; set; }
public string AlbumArt { get; set; }
public string QueuerName { get; set; }
public TimeSpan TotalTime = TimeSpan.Zero;
public string PrettyProvider => (Provider ?? "???");
//public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
public string PrettyName => $"**[{Title.TrimTo(65)}]({SongUrl})**";
public string PrettyInfo => $"{PrettyTotalTime} | {PrettyProvider} | {QueuerName}";
public string PrettyFullName => $"{PrettyName}\n\t\t`{PrettyTotalTime} | {PrettyProvider} | {Format.Sanitize(QueuerName.TrimTo(15))}`";
public string PrettyTotalTime
{
get
{
if (TotalTime == TimeSpan.Zero)
return "(?)";
if (TotalTime == TimeSpan.MaxValue)
return "∞";
var time = TotalTime.ToString(@"mm\:ss");
var hrs = (int)TotalTime.TotalHours;
if (hrs > 0)
return hrs + ":" + time;
return time;
}
}
public string SongUrl
{
get
{
switch (ProviderType)
{
case MusicType.Normal:
return Query;
case MusicType.Soundcloud:
return Query;
case MusicType.Local:
return $"https://google.com/search?q={ WebUtility.UrlEncode(Title).Replace(' ', '+') }";
case MusicType.Radio:
return $"https://google.com/search?q={Title}";
default:
return "";
}
}
}
private readonly Regex videoIdRegex = new Regex("<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+", RegexOptions.Compiled);
public string Thumbnail
{
get
{
switch (ProviderType)
{
case MusicType.Radio:
return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links
case MusicType.Normal:
//todo have videoid in songinfo from the start
var videoId = videoIdRegex.Match(Query);
return $"https://img.youtube.com/vi/{ videoId }/0.jpg";
case MusicType.Local:
return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links
case MusicType.Soundcloud:
return AlbumArt;
default:
return "";
}
}
}
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Music
{
public class SongResolver
{
// public async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
// {
// if (string.IsNullOrWhiteSpace(query))
// throw new ArgumentNullException(nameof(query));
// if (musicType != MusicType.Local && IsRadioLink(query))
// {
// musicType = MusicType.Radio;
// query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query;
// }
// try
// {
// switch (musicType)
// {
// case MusicType.Local:
// return new Song(new SongInfo
// {
// Uri = "\"" + Path.GetFullPath(query) + "\"",
// Title = Path.GetFileNameWithoutExtension(query),
// Provider = "Local File",
// ProviderType = musicType,
// Query = query,
// });
// case MusicType.Radio:
// return new Song(new SongInfo
// {
// Uri = query,
// Title = $"{query}",
// Provider = "Radio Stream",
// ProviderType = musicType,
// Query = query
// })
// { TotalTime = TimeSpan.MaxValue };
// }
// if (_sc.IsSoundCloudLink(query))
// {
// var svideo = await _sc.ResolveVideoAsync(query).ConfigureAwait(false);
// return new Song(new SongInfo
// {
// Title = svideo.FullName,
// Provider = "SoundCloud",
// Uri = await svideo.StreamLink(),
// ProviderType = musicType,
// Query = svideo.TrackLink,
// AlbumArt = svideo.artwork_url,
// })
// { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
// }
// if (musicType == MusicType.Soundcloud)
// {
// var svideo = await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false);
// return new Song(new SongInfo
// {
// Title = svideo.FullName,
// Provider = "SoundCloud",
// Uri = await svideo.StreamLink(),
// ProviderType = MusicType.Soundcloud,
// Query = svideo.TrackLink,
// AlbumArt = svideo.artwork_url,
// })
// { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
// }
// var link = (await _google.GetVideoLinksByKeywordAsync(query).ConfigureAwait(false)).FirstOrDefault();
// if (string.IsNullOrWhiteSpace(link))
// throw new OperationCanceledException("Not a valid youtube query.");
// var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
// var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
// var video = videos
// .Where(v => v.AudioBitrate < 256)
// .OrderByDescending(v => v.AudioBitrate)
// .FirstOrDefault();
// if (video == null) // do something with this error
// throw new Exception("Could not load any video elements based on the query.");
// var m = Regex.Match(query, @"\?t=(?<t>\d*)");
// int gotoTime = 0;
// if (m.Captures.Count > 0)
// int.TryParse(m.Groups["t"].ToString(), out gotoTime);
// var song = new Song(new SongInfo
// {
// Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
// Provider = "YouTube",
// Uri = await video.GetUriAsync().ConfigureAwait(false),
// Query = link,
// ProviderType = musicType,
// });
// song.SkipTo = gotoTime;
// return song;
// }
// catch (Exception ex)
// {
// _log.Warn($"Failed resolving the link.{ex.Message}");
// _log.Warn(ex);
// return null;
// }
// }
}
}