/// <summary>
        ///     Pushes a track between the current asynchronously.
        /// </summary>
        /// <param name="track">the track to push between the current</param>
        /// <param name="push">
        ///     a value indicating whether the track should only played when a track is playing currently.
        /// </param>
        /// <remarks>
        ///     Note: This feature is experimental. This will stop playing the current track and
        ///     start playing the specified <paramref name="track"/> after the track is finished the
        ///     track will restart at the stopped position. This can be useful for example
        ///     soundboards (playing an air-horn or something).
        /// </remarks>
        /// <returns>
        ///     a task that represents the asynchronous operation. The task result is a value
        ///     indicating whether the track was pushed between the current ( <see
        ///     langword="true"/>) or the specified track was simply started ( <see
        ///     langword="false"/>), because there is no track playing.
        /// </returns>
        public virtual async Task <bool> PushTrackAsync(LavalinkTrack track, bool push = false)
        {
            // star track immediately
            if (State == PlayerState.NotPlaying)
            {
                if (push)
                {
                    return(false);
                }

                await PlayAsync(track, enqueue : false);

                return(false);
            }

            // create clone and set starting position
            var oldTrack = CurrentTrack.WithPosition(TrackPosition);

            // enqueue old track with starting position
            Queue.Add(oldTrack);

            // push track
            await PlayAsync(track, enqueue : false);

            return(true);
        }
        /// <summary>
        ///     Plays the specified <paramref name="track"/> asynchronously.
        /// </summary>
        /// <param name="track">the track to play</param>
        /// <param name="startTime">the track start position</param>
        /// <param name="endTime">the track end position</param>
        /// <param name="noReplace">
        ///     a value indicating whether the track play should be ignored if the same track is
        ///     currently playing
        /// </param>
        /// <returns>a task that represents the asynchronous operation</returns>
        /// <exception cref="InvalidOperationException">
        ///     thrown if the player is not connected to a voice channel
        /// </exception>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual async Task PlayAsync(LavalinkTrack track, TimeSpan?startTime = null,
                                            TimeSpan?endTime = null, bool noReplace = false)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            CurrentTrack = track ?? throw new ArgumentNullException(nameof(track));
            startTime    = startTime ?? track.Position;

            await LavalinkSocket.SendPayloadAsync(new PlayerPlayPayload(GuildId, track.Identifier,
                                                                        startTime, endTime, noReplace));

            State = PlayerState.Playing;
        }
        /// <summary>
        ///     Skips the current track asynchronously.
        /// </summary>
        /// <param name="count">the number of tracks to skip</param>
        /// <returns>a task that represents the asynchronous operation</returns>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual Task SkipAsync(int count = 1)
        {
            // no tracks to skip
            if (count <= 0)
            {
                return(Task.CompletedTask);
            }

            EnsureNotDestroyed();
            EnsureConnected();

            // the looping option is enabled, repeat current track, does not matter how often we skip
            if (IsLooping && CurrentTrack != null)
            {
                return(PlayAsync(CurrentTrack, false));
            }
            // tracks are enqueued
            else if (!Queue.IsEmpty)
            {
                LavalinkTrack track = null;

                while (count-- > 0)
                {
                    // no more tracks in queue
                    if (Queue.Count < 1)
                    {
                        // no tracks found
                        return(DisconnectAsync());
                    }

                    // dequeue track
                    track = Queue.Dequeue();
                }

                // a track to play was found, dequeue and play
                return(PlayAsync(track, false));
            }
            // no tracks queued, disconnect if wanted
            else if (_disconnectOnStop)
            {
                return(DisconnectAsync());
            }

            return(Task.CompletedTask);
        }
        /// <summary>
        ///     Plays the specified <paramref name="track"/> at the top of the queue asynchronously.
        /// </summary>
        /// <param name="track">the track to play</param>
        /// <returns>a task that represents the asynchronous operation</returns>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual async Task PlayTopAsync(LavalinkTrack track)
        {
            EnsureNotDestroyed();

            if (track is null)
            {
                throw new ArgumentNullException(nameof(track));
            }

            // play track if none is playing
            if (State == PlayerState.NotPlaying)
            {
                await PlayAsync(track, enqueue : false);
            }
            // the player is currently playing a track, enqueue the track at top
            else
            {
                Queue.Insert(0, track);
            }
        }
        /// <summary>
        ///     Plays the specified <paramref name="track"/> asynchronously.
        /// </summary>
        /// <param name="track">the track to play</param>
        /// <param name="enqueue">
        ///     a value indicating whether the track should be enqueued in the track queue
        /// </param>
        /// <param name="startTime">the track start position</param>
        /// <param name="endTime">the track end position</param>
        /// <param name="noReplace">
        ///     a value indicating whether the track play should be ignored if the same track is
        ///     currently playing
        /// </param>
        /// <returns>
        ///     a task that represents the asynchronous operation
        ///     <para>the position in the track queue ( <c>0</c> = now playing)</para>
        /// </returns>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual async Task <int> PlayAsync(LavalinkTrack track, bool enqueue,
                                                  TimeSpan?startTime = null, TimeSpan?endTime = null, bool noReplace = false)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            // check if the track should be enqueued (if a track is already playing)
            if (enqueue && State == PlayerState.Playing)
            {
                // add the track to the queue
                Queue.Add(track);

                // return track queue position
                return(Queue.Count);
            }

            // play the track immediately
            await base.PlayAsync(track, startTime, endTime, noReplace);

            // 0 = now playing
            return(0);
        }
        /// <summary>
        ///     Plays the specified <paramref name="track"/> asynchronously.
        /// </summary>
        /// <param name="track">the track to play</param>
        /// <param name="enqueue">
        ///     a value indicating whether the track should be enqueued in the track queue
        /// </param>
        /// <param name="startTime">the track start position</param>
        /// <param name="endTime">the track end position</param>
        /// <param name="noReplace">
        ///     a value indicating whether the track play should be ignored if the same track is
        ///     currently playing
        /// </param>
        /// <returns>
        ///     a task that represents the asynchronous operation
        ///     <para>the position in the track queue ( <c>0</c> = now playing)</para>
        /// </returns>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual async Task <int> PlayAsync(LavalinkTrack track, bool enqueue,
                                                  TimeSpan?startTime = null, TimeSpan?endTime = null, bool noReplace = false)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            if (enqueue && State == PlayerState.Playing)
            {
                Queue.Add(track);

                if (State == PlayerState.NotPlaying)
                {
                    await SkipAsync();
                }

                return(Queue.Count);
            }

            await base.PlayAsync(track, startTime, endTime, noReplace);

            return(0);
        }
 /// <summary>
 ///     Plays the specified <paramref name="track"/> asynchronously.
 /// </summary>
 /// <param name="track">the track to play</param>
 /// <param name="startTime">the track start position</param>
 /// <param name="endTime">the track end position</param>
 /// <param name="noReplace">
 ///     a value indicating whether the track play should be ignored if the same track is
 ///     currently playing
 /// </param>
 /// <returns>
 ///     a task that represents the asynchronous operation
 ///     <para>the position in the track queue ( <c>0</c> = now playing)</para>
 /// </returns>
 public new virtual Task <int> PlayAsync(LavalinkTrack track, TimeSpan?startTime = null,
                                         TimeSpan?endTime = null, bool noReplace = false)
 => PlayAsync(track, true, startTime, endTime, noReplace);