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;
}
public ReplacementBuilder WithMusic(MusicService ms)
{
_reps.TryAdd("%playing%", () =>
{
var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
if (cnt != 1) return cnt.ToString();
try
{
var mp = ms.MusicPlayers.FirstOrDefault();
return mp.Value.CurrentSong.SongInfo.Title;
}
catch
{
return "No songs";
}
});
_reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString());
return this;
}
//public ReplacementBuilder WithMusic(MusicService ms)
//{
// _reps.TryAdd("%playing%", () =>
// {
// var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
// if (cnt != 1) return cnt.ToString();
// try
// {
// var mp = ms.MusicPlayers.FirstOrDefault();
// return mp.Value.CurrentSong.SongInfo.Title;
// }
// catch
// {
// return "No songs";
// }
// });
// _reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString());
// return this;
//}
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()
.WithClient(client)
.WithStats(client)
.WithMusic(music)
//.WithMusic(music)
.Build();
_t = new Timer(async (objState) =>

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Services.Impl
private readonly IBotCredentials _creds;
private readonly DateTime _started;
public const string BotVersion = "1.52";
public const string BotVersion = "1.53";
public string Author => "Kwoth#2560";
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
{
class PlaylistFullException : Exception
public class PlaylistFullException : Exception
{
public PlaylistFullException(string message) : base(message)
{
@ -10,11 +10,19 @@ namespace NadekoBot.Services.Music
public PlaylistFullException() : base("Queue is full.") { }
}
class SongNotFoundException : Exception
public class SongNotFoundException : Exception
{
public SongNotFoundException(string message) : base(message)
{
}
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 System.Net.Http;
using System.Collections.Generic;
using Discord.Commands;
namespace NadekoBot.Services.Music
{
@ -48,28 +49,49 @@ namespace NadekoBot.Services.Music
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 player;
}
public MusicPlayer GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh)
{
string GetText(string text, params object[] replacements) =>
_strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
return MusicPlayers.GetOrAdd(guildId, server =>
{
var vol = _defaultVolumes.GetOrAdd(guildId, (id) =>
return _defaultVolumes.GetOrAdd(guildId, (id) =>
{
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume;
}
});
}
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) =>
_strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
if (voiceCh == null || voiceCh.Guild != textCh.Guild)
{
if (textCh != null)
{
await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
}
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 lastFinishedMessage = null;
mp.OnCompleted += async (s, song) =>
@ -91,30 +113,30 @@ namespace NadekoBot.Services.Music
// ignored
}
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)
await QueueSong(await textCh.Guild.GetCurrentUserAsync(),
textCh,
voiceCh,
relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)],
true).ConfigureAwait(false);
}
//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)
// await QueueSong(await textCh.Guild.GetCurrentUserAsync(),
// textCh,
// voiceCh,
// relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)],
// true).ConfigureAwait(false);
//}
}
catch
{
// ignored
}
};
mp.OnStarted += async (player, song) =>
{
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
catch
{
// ignored
}
//try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
//catch
//{
// // ignored
//}
var sender = player;
if (sender == null)
return;
@ -125,7 +147,7 @@ namespace NadekoBot.Services.Music
playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
.WithFooter(ef => ef.WithText(mp.PrettyVolume + " | " + song.PrettyInfo)))
.ConfigureAwait(false);
}
catch
@ -133,7 +155,7 @@ namespace NadekoBot.Services.Music
// ignored
}
};
mp.OnPauseChanged += async (paused) =>
mp.OnPauseChanged += async (player, paused) =>
{
try
{
@ -150,160 +172,41 @@ namespace NadekoBot.Services.Music
// 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) =>
{
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);
await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch
{
// ignored
}
};
// }
// catch
// {
// // ignored
// }
//};
return mp;
});
}
public async Task<SongInfo> ResolveSong(string query, string queuerName, MusicType musicType = MusicType.Normal)
{
query.ThrowIfNull(nameof(query));
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);
SongInfo sinfo;
if (voiceCh == null || voiceCh.Guild != textCh.Guild)
{
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));
sinfo = await ResolveYoutubeSong(query, queuerName).ConfigureAwait(false);
var musicPlayer = GetOrCreatePlayer(textCh.Guild.Id, voiceCh, textCh);
Song resolvedSong;
try
{
musicPlayer.ThrowIfQueueFull();
resolvedSong = await ResolveSong(query, musicType).ConfigureAwait(false);
if (resolvedSong == null)
throw new SongNotFoundException();
musicPlayer.AddSong(resolvedSong, queuer.Username);
}
catch (PlaylistFullException)
{
try
{
await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize));
}
catch
{
// ignored
}
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
}
return sinfo;
}
public void DestroyPlayer(ulong id)
public async Task<SongInfo> ResolveYoutubeSong(string query, string queuerName)
{
if (MusicPlayers.TryRemove(id, out var mp))
mp.Destroy();
}
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.");
@ -316,125 +219,181 @@ namespace NadekoBot.Services.Music
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
//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
{
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;
ProviderType = MusicType.Normal,
QueuerName = queuerName
};
return song;
}
catch (Exception ex)
public void DestroyPlayer(ulong id)
{
_log.Warn($"Failed resolving the link.{ex.Message}");
_log.Warn(ex);
return null;
}
if (MusicPlayers.TryRemove(id, out var mp))
mp.Dispose();
}
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;
}
// 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);
}
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;
}
}
//if (string.IsNullOrWhiteSpace(query) || query.Length< 3)
// throw new ArgumentException("Invalid song query.", nameof(query));
return query;
}
// var musicPlayer = GetOrCreatePlayer(textCh.Guild.Id, voiceCh, textCh);
// Song resolvedSong;
// try
// {
// musicPlayer.ThrowIfQueueFull();
// resolvedSong = await ResolveSong(query, musicType).ConfigureAwait(false);
private bool IsRadioLink(string query) =>
(query.StartsWith("http") ||
query.StartsWith("ww"))
&&
(query.Contains(".pls") ||
query.Contains(".m3u") ||
query.Contains(".asx") ||
query.Contains(".xspf"));
// if (resolvedSong == null)
// throw new SongNotFoundException();
// musicPlayer.AddSong(resolvedSong, queuer.Username);
// }
// catch (PlaylistFullException)
// {
// try
// {
// await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize));
// }
// catch
// {
// // ignored
// }
// 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
// }
// }
// 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) =>
// (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 NLog;
using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Extensions;
using System.Net;
using Discord;
using NadekoBot.Services.Database.Models;
using System;
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 class Song
{
public SongInfo SongInfo { get; }
public MusicPlayer MusicPlayer { get; set; }
private string _queuerName;
public string QueuerName { get{
return Format.Sanitize(_queuerName);
} set { _queuerName = value; } }
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
public TimeSpan CurrentTime => TimeSpan.FromSeconds(BytesSent / (float)_frameBytes / (1000 / (float)_milliseconds));
private const int _milliseconds = 20;
private const int _samplesPerFrame = (48000 / 1000) * _milliseconds;
private const int _frameBytes = 3840; //16-bit, 2 channels
private ulong BytesSent { get; set; }
//pwetty
public string PrettyProvider =>
$"{(SongInfo.Provider ?? "???")}";
public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**";
public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {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;
if (hrs > 0)
return hrs + ":" + time;
else
return time;
}
}
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 Thumbnail {
get {
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 SongUrl {
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; }
private readonly Logger _log;
public Song(SongInfo songInfo)
{
SongInfo = songInfo;
_log = LogManager.GetCurrentClassLogger();
}
public Song Clone()
{
var s = new Song(SongInfo)
{
MusicPlayer = MusicPlayer,
QueuerName = QueuerName
};
return s;
}
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{
BytesSent = (ulong) SkipTo * 3840 * 50;
var filename = Path.Combine(MusicService.MusicDataPath, DateTime.UtcNow.UnixTimestamp().ToString());
var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
try
{
var attempt = 0;
var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy
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)
{
_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);
var outStream = voiceClient.CreatePCMStream(AudioApplication.Music);
int nextTime = Environment.TickCount + _milliseconds;
byte[] buffer = new byte[_frameBytes];
while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason
!(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);
if (read != _frameBytes) continue;
nextTime = unchecked(nextTime + _milliseconds);
int delayMillis = unchecked(nextTime - Environment.TickCount);
if (delayMillis > 0)
await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false);
await outStream.WriteAsync(buffer, 0, read).ConfigureAwait(false);
}
}
finally
{
await bufferTask;
inStream.Dispose();
}
}
private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken, long size)
{
while (!inStream.BufferingCompleted && inStream.Length < size)
{
await Task.Delay(100, cancelToken);
}
_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;
}
}
//public class Song
//{
// public SongInfo SongInfo { get; }
// public MusicPlayer MusicPlayer { get; set; }
// private string _queuerName;
// public string QueuerName { get{
// return Format.Sanitize(_queuerName);
// } set { _queuerName = value; } }
// public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
// public TimeSpan CurrentTime => TimeSpan.FromSeconds(BytesSent / (float)_frameBytes / (1000 / (float)_milliseconds));
// private const int _milliseconds = 20;
// private const int _samplesPerFrame = (48000 / 1000) * _milliseconds;
// private const int _frameBytes = 3840; //16-bit, 2 channels
// private ulong BytesSent { get; set; }
// //pwetty
// public string PrettyProvider =>
// $"{(SongInfo.Provider ?? "???")}";
// public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
// public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**";
// public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {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;
// if (hrs > 0)
// return hrs + ":" + time;
// else
// return time;
// }
// }
// 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 Thumbnail {
// get {
// 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 SongUrl {
// 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 "";
// }
// }
// }
// private readonly Logger _log;
// public Song(SongInfo songInfo)
// {
// SongInfo = songInfo;
// _log = LogManager.GetCurrentClassLogger();
// }
// public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
// {
// BytesSent = (ulong) SkipTo * 3840 * 50;
// var filename = Path.Combine(MusicService.MusicDataPath, DateTime.UtcNow.UnixTimestamp().ToString());
// var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
// var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
// try
// {
// var attempt = 0;
// var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy
// 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)
// {
// _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);
// var outStream = voiceClient.CreatePCMStream(AudioApplication.Music);
// int nextTime = Environment.TickCount + _milliseconds;
// byte[] buffer = new byte[_frameBytes];
// while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason
// !(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);
// if (read != _frameBytes) continue;
// nextTime = unchecked(nextTime + _milliseconds);
// int delayMillis = unchecked(nextTime - Environment.TickCount);
// if (delayMillis > 0)
// await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false);
// await outStream.WriteAsync(buffer, 0, read).ConfigureAwait(false);
// }
// }
// finally
// {
// await bufferTask;
// inStream.Dispose();
// }
// }
// private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken, long size)
// {
// while (!inStream.BufferingCompleted && inStream.Length < size)
// {
// await Task.Delay(100, cancelToken);
// }
// _log.Debug("Buffering successfull");
// }
//}
}

View File

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