.play help update, readded pausing when nobody is in voice channel, and also cleaning up music players if bot is kicked or leaves the server
This commit is contained in:
		@@ -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<int, EmbedBuilder> 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]
 | 
			
		||||
 
 | 
			
		||||
@@ -1198,7 +1198,7 @@
 | 
			
		||||
    <value>`{0}drawnew` or `{0}drawnew 5`</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="shuffleplaylist_cmd" xml:space="preserve">
 | 
			
		||||
    <value>shuffle plsh</value>
 | 
			
		||||
    <value>shuffle sh plsh</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="shuffleplaylist_desc" xml:space="preserve">
 | 
			
		||||
    <value>Shuffles the current playlist.</value>
 | 
			
		||||
@@ -1471,10 +1471,10 @@
 | 
			
		||||
    <value>play start</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="play_desc" xml:space="preserve">
 | 
			
		||||
    <value>If no arguments are specified, acts as `{0}next 1` command. If you specify a search query, acts as a `{0}q` command</value>
 | 
			
		||||
    <value>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</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="play_usage" xml:space="preserve">
 | 
			
		||||
    <value>`{0}play` or `{0}play Dream Of Venice`</value>
 | 
			
		||||
    <value>`{0}play` or `{0}play 5` or `{0}play Dream Of Venice`</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="stop_cmd" xml:space="preserve">
 | 
			
		||||
    <value>stop s</value>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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<bool> 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<MusicPlayer, SongInfo> OnStarted;
 | 
			
		||||
        public event Action<MusicPlayer, (int Index, SongInfo Song)> OnStarted;
 | 
			
		||||
        public event Action<MusicPlayer, SongInfo> OnCompleted;
 | 
			
		||||
        public event Action<MusicPlayer, bool> OnPauseChanged;
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        private bool manualSkip = false;
 | 
			
		||||
        private bool manualIndex = false;
 | 
			
		||||
        private bool newVoiceChannel = false;
 | 
			
		||||
 | 
			
		||||
        private ConcurrentHashSet<string> RecentlyPlayedUsers { get; } = new ConcurrentHashSet<string>();
 | 
			
		||||
        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<IAudioClient> 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; }
 | 
			
		||||
 | 
			
		||||
        ///// <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();
 | 
			
		||||
        //}
 | 
			
		||||
        //    new TimeSpan(_playlist.Sum(s => s.TotalTime.Ticks));        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -11,7 +11,7 @@ namespace NadekoBot.Services.Music
 | 
			
		||||
    {
 | 
			
		||||
        private LinkedList<SongInfo> Songs { get; } = new LinkedList<SongInfo>();
 | 
			
		||||
        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)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -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=(?<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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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.",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user