diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index ab131a0f..51713c80 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -36,56 +36,62 @@ namespace NadekoBot.Modules.Music _db = db; _music = music; - //_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated; + _client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated; + _client.LeftGuild += _client_LeftGuild; } - //todo when someone drags nadeko from one voice channel to another + private Task _client_LeftGuild(SocketGuild arg) + { + var t = _music.DestroyPlayer(arg.Id); + return Task.CompletedTask; + } - //private Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) - //{ - // var usr = iusr as SocketGuildUser; - // if (usr == null || - // oldState.VoiceChannel == newState.VoiceChannel) - // return Task.CompletedTask; + private Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) + { + var t = Task.Run(() => + { + var usr = iusr as SocketGuildUser; + if (usr == null || + oldState.VoiceChannel == newState.VoiceChannel) + return; - // MusicPlayer player; - // if ((player = _music.GetPlayer(usr.Guild.Id)) == null) - // return Task.CompletedTask; + var player = _music.GetPlayerOrDefault(usr.Guild.Id); - // try - // { - // //if bot moved - // if ((player.PlaybackVoiceChannel == oldState.VoiceChannel) && - // usr.Id == _client.CurrentUser.Id) - // { - // if (player.Paused && newState.VoiceChannel.Users.Count > 1) //unpause if there are people in the new channel - // player.TogglePause(); - // else if (!player.Paused && newState.VoiceChannel.Users.Count <= 1) // pause if there are no users in the new channel - // player.TogglePause(); + try + { + //if bot moved + if ((player.VoiceChannel == oldState.VoiceChannel) && + usr.Id == _client.CurrentUser.Id) + { + if (player.Paused && newState.VoiceChannel.Users.Count > 1) //unpause if there are people in the new channel + player.TogglePause(); + else if (!player.Paused && newState.VoiceChannel.Users.Count <= 1) // pause if there are no users in the new channel + player.TogglePause(); - // return Task.CompletedTask; - // } + player.SetVoiceChannel(newState.VoiceChannel); + return; + } - // //if some other user moved - // if ((player.PlaybackVoiceChannel == newState.VoiceChannel && //if joined first, and player paused, unpause - // player.Paused && - // newState.VoiceChannel.Users.Count == 2) || // keep in mind bot is in the channel (+1) - // (player.PlaybackVoiceChannel == oldState.VoiceChannel && // if left last, and player unpaused, pause - // !player.Paused && - // oldState.VoiceChannel.Users.Count == 1)) - // { - // player.TogglePause(); - // return Task.CompletedTask; - // } - - // } - // catch - // { - // // ignored - // } - // return Task.CompletedTask; - //} + //if some other user moved + if ((player.VoiceChannel == newState.VoiceChannel && //if joined first, and player paused, unpause + player.Paused && + newState.VoiceChannel.Users.Count >= 2) || // keep in mind bot is in the channel (+1) + (player.VoiceChannel == oldState.VoiceChannel && // if left last, and player unpaused, pause + !player.Paused && + oldState.VoiceChannel.Users.Count == 1)) + { + player.TogglePause(); + return; + } + } + catch + { + // ignored + } + }); + return Task.CompletedTask; + } private async Task InternalQueue(MusicPlayer mp, SongInfo songInfo, bool silent) { @@ -96,25 +102,24 @@ namespace NadekoBot.Modules.Music return; } - (bool Success, int Index) qData; + int index; try { - qData = mp.Enqueue(songInfo); + index = mp.Enqueue(songInfo); } catch (QueueFullException) { await ReplyErrorLocalized("queue_full", mp.MaxQueueSize).ConfigureAwait(false); throw; } - if (qData.Success) + if (index != -1) { if (!silent) { try { - //var queuedMessage = await textCh.SendConfirmAsync($"🎡 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false); var queuedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (qData.Index)).WithMusicIcon()) + .WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (index)).WithMusicIcon()) .WithDescription($"{songInfo.PrettyName}\n{GetText("queue")} ") .WithThumbnailUrl(songInfo.Thumbnail) .WithFooter(ef => ef.WithText(songInfo.PrettyProvider))) @@ -135,12 +140,26 @@ namespace NadekoBot.Modules.Music [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public Task Play([Remainder]string query = null) + public async Task Play([Remainder] string query = null) { - if (!string.IsNullOrWhiteSpace(query)) - try { return Queue(query); } catch (QueueFullException) { return Task.CompletedTask; } + var mp = await _music.GetOrCreatePlayer(Context); + if (string.IsNullOrWhiteSpace(query)) + { + await Next(); + } + else if (int.TryParse(query, out var index)) + if (index >= 1) + mp.SetIndex(index - 1); + else + return; else - return Next(); + { + try + { + await Queue(query); + } + catch { } + } } [NadekoCommand, Usage, Description, Aliases] @@ -218,13 +237,12 @@ namespace NadekoBot.Modules.Music page = current / itemsPerPage; //if page is 0 (-1 after this decrement) that means default to the page current song is playing from - - //var total = musicPlayer.TotalPlaytime; - //var totalStr = total == TimeSpan.MaxValue ? "∞" : GetText("time_format", - // (int)total.TotalHours, - // total.Minutes, - // total.Seconds); - //var maxPlaytime = musicPlayer.MaxPlaytimeSeconds; + var total = mp.TotalPlaytime; + var totalStr = total == TimeSpan.MaxValue ? "∞" : GetText("time_format", + (int)total.TotalHours, + total.Minutes, + total.Seconds); + var maxPlaytime = mp.MaxPlaytimeSeconds; var lastPage = songs.Length / itemsPerPage; Func printAction = curPage => { @@ -255,10 +273,12 @@ namespace NadekoBot.Modules.Music add += "πŸ”€ " + GetText("shuffling_playlist") + "\n"; else { - if (mp.RepeatPlaylist) - add += "πŸ” " + GetText("repeating_playlist") + "\n"; if (mp.Autoplay) add += "β†ͺ " + GetText("autoplaying") + "\n"; + if (mp.FairPlay && !mp.Autoplay) + add += " " + GetText("fairplay") + "\n"; + else if (mp.RepeatPlaylist) + add += "πŸ” " + GetText("repeating_playlist") + "\n"; } if (!string.IsNullOrWhiteSpace(add)) @@ -268,12 +288,8 @@ namespace NadekoBot.Modules.Music .WithAuthor(eab => eab.WithName(GetText("player_queue", curPage + 1, lastPage + 1)) .WithMusicIcon()) .WithDescription(desc) - //.WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " + - // $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " + - // (musicPlayer.FairPlay - // ? "βœ”οΈ" + GetText("fairplay") - // : "βœ–οΈ" + GetText("fairplay")) + " | " + - // (maxPlaytime == 0 ? "unlimited" : GetText("play_limit", maxPlaytime)))) + .WithFooter(ef => ef.WithText($"{mp.PrettyVolume} | {songs.Length} " + + $"{("tracks".SnPl(songs.Length))} | {totalStr}")) .WithOkColor(); return embed; @@ -517,27 +533,22 @@ namespace NadekoBot.Modules.Music } } - //[NadekoCommand, Usage, Description, Aliases] - //[RequireContext(ContextType.Guild)] - //public async Task Fairplay() - //{ - // var channel = (ITextChannel)Context.Channel; - // MusicPlayer musicPlayer; - // if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) - // return; - // if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) - // return; - // var val = musicPlayer.FairPlay = !musicPlayer.FairPlay; + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Fairplay() + { + var mp = await _music.GetOrCreatePlayer(Context); + var val = mp.FairPlay = !mp.FairPlay; - // if (val) - // { - // await ReplyConfirmLocalized("fp_enabled").ConfigureAwait(false); - // } - // else - // { - // await ReplyConfirmLocalized("fp_disabled").ConfigureAwait(false); - // } - //} + if (val) + { + await ReplyConfirmLocalized("fp_enabled").ConfigureAwait(false); + } + else + { + await ReplyConfirmLocalized("fp_disabled").ConfigureAwait(false); + } + } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] @@ -613,58 +624,46 @@ namespace NadekoBot.Modules.Music await ReplyConfirmLocalized("songs_shuffle_disable").ConfigureAwait(false); } - //[NadekoCommand, Usage, Description, Aliases] - //[RequireContext(ContextType.Guild)] - //public async Task Playlist([Remainder] string playlist) - //{ + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Playlist([Remainder] string playlist) + { + if (string.IsNullOrWhiteSpace(playlist)) + return; - // var arg = playlist; - // if (string.IsNullOrWhiteSpace(arg)) - // return; - // if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild) - // { - // await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false); - // return; - // } - // var plId = (await _google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault(); - // if (plId == null) - // { - // await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); - // return; - // } - // var ids = await _google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false); - // if (!ids.Any()) - // { - // await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); - // return; - // } - // var count = ids.Count(); - // var msg = await Context.Channel.SendMessageAsync("🎡 " + GetText("attempting_to_queue", - // Format.Bold(count.ToString()))).ConfigureAwait(false); + var mp = await _music.GetOrCreatePlayer(Context); - // var cancelSource = new CancellationTokenSource(); + var plId = (await _google.GetPlaylistIdsByKeywordsAsync(playlist).ConfigureAwait(false)).FirstOrDefault(); + if (plId == null) + { + await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); + return; + } + var ids = await _google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false); + if (!ids.Any()) + { + await ReplyErrorLocalized("no_search_results").ConfigureAwait(false); + return; + } + var count = ids.Count(); + var msg = await Context.Channel.SendMessageAsync("🎡 " + GetText("attempting_to_queue", + Format.Bold(count.ToString()))).ConfigureAwait(false); + + foreach (var song in ids) + { + try + { + if (mp.Exited) + return; - // var gusr = (IGuildUser)Context.User; - // while (ids.Any() && !cancelSource.IsCancellationRequested) - // { - // var tasks = Task.WhenAll(ids.Take(5).Select(async id => - // { - // if (cancelSource.Token.IsCancellationRequested) - // return; - // try - // { - // await _music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, id, true).ConfigureAwait(false); - // } - // catch (SongNotFoundException) { } - // catch { try { cancelSource.Cancel(); } catch { } } - // })); + await Task.WhenAll(Task.Delay(100), InternalQueue(mp, await _music.ResolveSong(song, Context.User.ToString(), MusicType.YouTube), true)); + } + catch (SongNotFoundException) { } + catch { break; } + } - // await Task.WhenAny(tasks, Task.Delay(Timeout.Infinite, cancelSource.Token)); - // ids = ids.Skip(5); - // } - - // await msg.ModifyAsync(m => m.Content = "βœ… " + Format.Bold(GetText("playlist_queue_complete"))).ConfigureAwait(false); - //} + await msg.ModifyAsync(m => m.Content = "βœ… " + Format.Bold(GetText("playlist_queue_complete"))).ConfigureAwait(false); + } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 78297dd9..e3504acb 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -1198,7 +1198,7 @@ `{0}drawnew` or `{0}drawnew 5` - shuffle plsh + shuffle sh plsh Shuffles the current playlist. @@ -1471,10 +1471,10 @@ play start - If no arguments are specified, acts as `{0}next 1` command. If you specify a search query, acts as a `{0}q` command + If no arguments are specified, acts as `{0}next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `{0}q` command - `{0}play` or `{0}play Dream Of Venice` + `{0}play` or `{0}play 5` or `{0}play Dream Of Venice` stop s diff --git a/src/NadekoBot/Services/Administration/PlayingRotateService.cs b/src/NadekoBot/Services/Administration/PlayingRotateService.cs index d4e09ad1..aa9321c6 100644 --- a/src/NadekoBot/Services/Administration/PlayingRotateService.cs +++ b/src/NadekoBot/Services/Administration/PlayingRotateService.cs @@ -1,6 +1,5 @@ ο»Ώusing Discord.WebSocket; using NadekoBot.DataStructures.Replacements; -using NadekoBot.Services; using NadekoBot.Services.Database.Models; using NadekoBot.Services.Music; using NLog; diff --git a/src/NadekoBot/Services/Music/MusicPlayer.cs b/src/NadekoBot/Services/Music/MusicPlayer.cs index 10d4d2ac..c42d56da 100644 --- a/src/NadekoBot/Services/Music/MusicPlayer.cs +++ b/src/NadekoBot/Services/Music/MusicPlayer.cs @@ -4,7 +4,9 @@ using System; using System.Threading; using System.Threading.Tasks; using NLog; -using System.Diagnostics; +using System.Linq; +using System.Collections.Concurrent; +using NadekoBot.Extensions; namespace NadekoBot.Services.Music { @@ -18,7 +20,7 @@ namespace NadekoBot.Services.Music public class MusicPlayer { private readonly Task _player; - private IVoiceChannel VoiceChannel { get; set; } + public IVoiceChannel VoiceChannel { get; private set; } private readonly Logger _log; private MusicQueue Queue { get; } = new MusicQueue(); @@ -27,6 +29,7 @@ namespace NadekoBot.Services.Music public bool Stopped { get; private set; } = false; public float Volume { get; private set; } = 1.0f; public string PrettyVolume => $"πŸ”‰ {(int)(Volume * 100)}%"; + public bool Paused => pauseTaskSource != null; private TaskCompletionSource pauseTaskSource { get; set; } = null; private CancellationTokenSource SongCancelSource { get; set; } @@ -48,7 +51,27 @@ namespace NadekoBot.Services.Music public uint MaxQueueSize { get => Queue.MaxQueueSize; - set => Queue.MaxQueueSize = value; + set { lock (locker) Queue.MaxQueueSize = value; } + } + private bool _fairPlay; + public bool FairPlay + { + get => _fairPlay; + set + { + if (value) + { + var cur = Queue.Current; + if (cur.Song != null) + RecentlyPlayedUsers.Add(cur.Song.QueuerName); + } + else + { + RecentlyPlayedUsers.Clear(); + } + + _fairPlay = value; + } } public uint MaxPlaytimeSeconds { get; set; } @@ -56,6 +79,7 @@ namespace NadekoBot.Services.Music const int _frameBytes = 3840; const float _miliseconds = 20.0f; public TimeSpan CurrentTime => TimeSpan.FromSeconds(_bytesSent / (float)_frameBytes / (1000 / _miliseconds)); + private int _bytesSent = 0; private IAudioClient _audioClient; @@ -63,15 +87,19 @@ namespace NadekoBot.Services.Music private MusicService _musicService; #region events - public event Action OnStarted; + public event Action OnStarted; public event Action OnCompleted; public event Action OnPauseChanged; #endregion private bool manualSkip = false; + private bool manualIndex = false; private bool newVoiceChannel = false; + private ConcurrentHashSet RecentlyPlayedUsers { get; } = new ConcurrentHashSet(); + public TimeSpan TotalPlaytime => TimeSpan.MaxValue; + public MusicPlayer(MusicService musicService, IVoiceChannel vch, ITextChannel output, float volume) { _log = LogManager.GetCurrentClassLogger(); @@ -93,6 +121,7 @@ namespace NadekoBot.Services.Music data = Queue.Current; cancelToken = SongCancelSource.Token; manualSkip = false; + manualIndex = false; } try { @@ -123,19 +152,17 @@ namespace NadekoBot.Services.Music // i don't want to spam connection attempts continue; } - var pcm = ac.CreatePCMStream(AudioApplication.Music, bufferMillis: 200); - OnStarted?.Invoke(this, data.Song); + var pcm = ac.CreatePCMStream(AudioApplication.Music, bufferMillis: 500); + OnStarted?.Invoke(this, data); byte[] buffer = new byte[3840]; int bytesRead = 0; try { - while ((bytesRead = await b.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) > 0 + while ((bytesRead = await b.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) > 0 && (MaxPlaytimeSeconds <= 0 || MaxPlaytimeSeconds >= CurrentTime.TotalSeconds)) { - var vol = Volume; - if (vol != 1) - AdjustVolume(buffer, vol); + AdjustVolume(buffer, Volume); await pcm.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); unchecked { _bytesSent += bytesRead; } @@ -165,60 +192,118 @@ namespace NadekoBot.Services.Music } finally { - //if repeating current song, just ignore other settings, - // and play this song again (don't change the index) - // ignore rcs if song is manually skipped - if (!RepeatCurrentSong || manualSkip) + try { - if (Shuffle) + //if repeating current song, just ignore other settings, + // and play this song again (don't change the index) + // ignore rcs if song is manually skipped + + int queueCount; + lock (locker) + queueCount = Queue.Count; + + if (!manualIndex && (!RepeatCurrentSong || manualSkip)) { - _log.Info("Random song"); - Queue.Random(); //if shuffle is set, set current song index to a random number - } - else - { - //if last song, and autoplay is enabled, and if it's a youtube song - // do autplay magix - if (Queue.Count - 1 == data.Index && Autoplay && data.Song?.ProviderType == Database.Models.MusicType.YouTube) + if (Shuffle) { - try - { - _log.Info("Loading related song"); - await _musicService.TryQueueRelatedSongAsync(data.Song.Query, OutputTextChannel, VoiceChannel); - Queue.Next(); - } - catch - { - _log.Info("Loading related song failed."); - } - } - else if (Queue.Count - 1 == data.Index && !RepeatPlaylist && !manualSkip) - { - _log.Info("Stopping because repeatplaylist is disabled"); - Stop(); + _log.Info("Random song"); + Queue.Random(); //if shuffle is set, set current song index to a random number } else { - _log.Info("Next song"); - Queue.Next(); + //if last song, and autoplay is enabled, and if it's a youtube song + // do autplay magix + if (queueCount - 1 == data.Index && Autoplay && data.Song?.ProviderType == Database.Models.MusicType.YouTube) + { + try + { + _log.Info("Loading related song"); + await _musicService.TryQueueRelatedSongAsync(data.Song.Query, OutputTextChannel, VoiceChannel); + Queue.Next(); + } + catch + { + _log.Info("Loading related song failed."); + } + } + else if (FairPlay) + { + lock (locker) + { + _log.Info("Next fair song"); + var q = Queue.ToArray().Songs.Shuffle().ToArray(); + + bool found = false; + for (var i = 0; i < q.Length; i++) //first try to find a queuer who didn't have their song played recently + { + var item = q[i]; + if (RecentlyPlayedUsers.Add(item.QueuerName)) // if it's found, set current song to that index + { + Queue.CurrentIndex = i; + found = true; + break; + } + } + if (!found) //if it's not + { + RecentlyPlayedUsers.Clear(); //clear all recently played users (that means everyone from the playlist has had their song played) + Queue.Random(); //go to a random song (to prevent looping on the first few songs) + var cur = Current; + if (cur.Current != null) // add newely scheduled song's queuer to the recently played list + RecentlyPlayedUsers.Add(cur.Current.QueuerName); + } + } + } + else if (queueCount - 1 == data.Index && !RepeatPlaylist && !manualSkip) + { + _log.Info("Stopping because repeatplaylist is disabled"); + lock (locker) + { + Stop(); + } + } + else + { + _log.Info("Next song"); + lock (locker) + { + Queue.Next(); + } + } } } } + catch (Exception ex) + { + _log.Error(ex); + } do { await Task.Delay(500); } - while (Stopped && !Exited); + while ((Queue.Count == 0 || Stopped) && !Exited); } } }, SongCancelSource.Token); } + public void SetIndex(int index) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + lock (locker) + { + Queue.CurrentIndex = index; + manualIndex = true; + CancelCurrentSong(); + } + } + private async Task GetAudioClient(bool reconnect = false) { if (_audioClient == null || _audioClient.ConnectionState != ConnectionState.Connected || - reconnect || + reconnect || newVoiceChannel) try { @@ -240,17 +325,24 @@ namespace NadekoBot.Services.Music return _audioClient; } - public (bool Success, int Index) Enqueue(SongInfo song) + public int Enqueue(SongInfo song) { _log.Info("Adding song"); - Queue.Add(song); - return (true, Queue.Count); + lock (locker) + { + if (Exited) + return -1; + Queue.Add(song); + return Queue.Count; + } } public void Next(int skipCount = 1) { lock (locker) { + if (Exited) + return; manualSkip = true; // if player is stopped, and user uses .n, it should play current song. // It's a bit weird, but that's the least annoying solution @@ -277,10 +369,13 @@ namespace NadekoBot.Services.Music private void Unpause() { - if (pauseTaskSource != null) + lock (locker) { - pauseTaskSource.TrySetResult(true); - pauseTaskSource = null; + if (pauseTaskSource != null) + { + pauseTaskSource.TrySetResult(true); + pauseTaskSource = null; + } } } @@ -302,7 +397,10 @@ namespace NadekoBot.Services.Music { if (volume < 0 || volume > 100) throw new ArgumentOutOfRangeException(nameof(volume)); - Volume = ((float)volume) / 100; + lock (locker) + { + Volume = ((float)volume) / 100; + } } public SongInfo RemoveAt(int index) @@ -335,7 +433,10 @@ namespace NadekoBot.Services.Music } public (int CurrentIndex, SongInfo[] Songs) QueueArray() - => Queue.ToArray(); + { + lock (locker) + return Queue.ToArray(); + } //aidiakapi ftw public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume) @@ -410,366 +511,18 @@ namespace NadekoBot.Services.Music public void SetVoiceChannel(IVoiceChannel vch) { - VoiceChannel = vch; - newVoiceChannel = true; - Next(); + lock (locker) + { + if (Exited) + return; + VoiceChannel = vch; + } } - - //private IAudioClient AudioClient { get; set; } - - ///// - ///// Player will prioritize different queuer name - ///// over the song position in the playlist - ///// - //public bool FairPlay { get; set; } = false; - - ///// - ///// 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. - ///// - //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)); - - ///// - ///// Users who recently got their music wish - ///// - //private ConcurrentHashSet RecentlyPlayedUsers { get; } = new ConcurrentHashSet(); - - //private readonly List _playlist = new List(); - //private readonly Logger _log; - //private readonly IGoogleApiService _google; - - //public IReadOnlyCollection 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 OnCompleted = delegate { }; - //public event Action OnStarted = delegate { }; - //public event Action 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 ActionQueue { get; } = new ConcurrentQueue(); - - //public string PrettyVolume => $"πŸ”‰ {(int)(Volume * 100)}%"; - - //public event Action 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(); - //} + // new TimeSpan(_playlist.Sum(s => s.TotalTime.Ticks)); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Music/MusicQueue.cs b/src/NadekoBot/Services/Music/MusicQueue.cs index 9156d0a7..967a5197 100644 --- a/src/NadekoBot/Services/Music/MusicQueue.cs +++ b/src/NadekoBot/Services/Music/MusicQueue.cs @@ -11,7 +11,7 @@ namespace NadekoBot.Services.Music { private LinkedList Songs { get; } = new LinkedList(); private int _currentIndex = 0; - private int CurrentIndex + public int CurrentIndex { get { @@ -124,7 +124,7 @@ namespace NadekoBot.Services.Music } } - public (int, SongInfo[]) ToArray() + public (int CurrentIndex, SongInfo[] Songs) ToArray() { lock (locker) { diff --git a/src/NadekoBot/Services/Music/MusicService.cs b/src/NadekoBot/Services/Music/MusicService.cs index a7f9ef46..2ed203ac 100644 --- a/src/NadekoBot/Services/Music/MusicService.cs +++ b/src/NadekoBot/Services/Music/MusicService.cs @@ -131,9 +131,9 @@ namespace NadekoBot.Services.Music playingMessage?.DeleteAfter(0); playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon()) - .WithDescription(song.PrettyName) - .WithFooter(ef => ef.WithText(mp.PrettyVolume + " | " + song.PrettyInfo))) + .WithAuthor(eab => eab.WithName(GetText("playing_song", song.Index + 1)).WithMusicIcon()) + .WithDescription(song.Song.PrettyName) + .WithFooter(ef => ef.WithText(mp.PrettyVolume + " | " + song.Song.PrettyInfo))) .ConfigureAwait(false); } catch @@ -288,11 +288,15 @@ namespace NadekoBot.Services.Music .FirstOrDefault(); if (video == null) // do something with this error + { _log.Info("Could not load any video elements based on the query."); + return null; + } //var m = Regex.Match(query, @"\?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" diff --git a/src/NadekoBot/_strings/ResponseStrings.en-US.json b/src/NadekoBot/_strings/ResponseStrings.en-US.json index 2703d2fd..fa4bea1a 100644 --- a/src/NadekoBot/_strings/ResponseStrings.en-US.json +++ b/src/NadekoBot/_strings/ResponseStrings.en-US.json @@ -407,7 +407,7 @@ "music_autoplaying": "Auto-playing.", "music_defvol_set": "Default volume set to {0}%", "music_dir_queue_complete": "Directory queue complete.", - "music_fairplay": "fairplay", + "music_fairplay": "Fairplay", "music_finished_song": "Finished song", "music_fp_disabled": "Fair play disabled.", "music_fp_enabled": "Fair play enabled.", @@ -425,7 +425,7 @@ "music_no_search_results": "No search results.", "music_paused": "Music playback paused.", "music_player_queue": "Player queue - Page {0}/{1}", - "music_playing_song": "Playing song", + "music_playing_song": "Playing song #{0}", "music_playlists": "`#{0}` - **{1}** by *{2}* ({3} songs)", "music_playlists_page": "Page {0} of saved playlists", "music_playlist_deleted": "Playlist deleted.",