/// <summary>
        ///     Updates the player volume asynchronously.
        /// </summary>
        /// <param name="volume">the player volume (0f - 10f)</param>
        /// <param name="normalize">
        ///     a value indicating whether if the <paramref name="volume"/> is out of range (0f -
        ///     10f) it should be normalized in its range. For example 11f will be mapped to 10f and
        ///     -20f to 0f.
        /// </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="ArgumentOutOfRangeException">
        ///     thrown if the specified <paramref name="volume"/> is out of range (0f - 10f)
        /// </exception>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual async Task SetVolumeAsync(float volume = 1f, bool normalize = false)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            if (volume > 10f || volume < 0f)
            {
                if (!normalize)
                {
                    throw new ArgumentOutOfRangeException(nameof(volume), volume, "Volume is out of range (0f - 10f)");
                }

                // bring the values into range (0f - 10f)
                volume = Math.Max(0f, volume);
                volume = Math.Min(10f, volume);
            }

            // check if the volume is already the same as wanted
            if (Volume == volume)
            {
                return;
            }

            var payload = new PlayerVolumePayload(GuildId, (int)(volume * 100));
            await LavalinkSocket.SendPayloadAsync(payload);

            Volume = volume;
        }
        /// <summary>
        ///     Destroys the player asynchronously.
        /// </summary>
        /// <returns>a task that represents the asynchronous operation</returns>
        public async Task DestroyAsync()
        {
            EnsureNotDestroyed();

            // destroy player
            State = PlayerState.Destroyed;

            // send destroy payload
            await LavalinkSocket.SendPayloadAsync(new PlayerDestroyPayload(GuildId));
        }
        /// <summary>
        ///     Updates the player equalizer asynchronously.
        /// </summary>
        /// <param name="bands">the bands</param>
        /// <param name="reset">
        ///     a value indicating whether the equalizer bands should be overridden ( <see
        ///     langword="false"/>) or replaced ( <see langword="true"/>).
        /// </param>
        /// <returns>a task that represents the asynchronous operation</returns>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual Task UpdateEqualizerAsync(IEnumerable <EqualizerBand> bands, bool reset = true)
        {
            EnsureNotDestroyed();

            if (reset)
            {
                bands = bands.Union(DefaultEqualizer, new EqualizerBandComparer());
            }

            return(LavalinkSocket.SendPayloadAsync(new PlayerEqualizerPayload(GuildId, bands.ToArray())));
        }
        /// <summary>
        ///     Stops playing the current track asynchronously.
        /// </summary>
        /// <param name="disconnect">
        ///     a value indicating whether the connection to the voice server should be closed
        /// </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 StopAsync(bool disconnect = false)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            await LavalinkSocket.SendPayloadAsync(new PlayerStopPayload(GuildId));

            if (disconnect)
            {
                await DisconnectAsync(PlayerDisconnectCause.Stop);
            }
        }
        /// <summary>
        ///     Seeks the current playing track asynchronously.
        /// </summary>
        /// <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="NotSupportedException">
        ///     thrown if the current playing track does not support seeking.
        /// </exception>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual Task SeekPositionAsync(TimeSpan position)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            if (State != PlayerState.Paused && State != PlayerState.Playing)
            {
                throw new InvalidOperationException("There is no track paused or playing.");
            }

            return(LavalinkSocket.SendPayloadAsync(new PlayerSeekPayload(GuildId, position)));
        }
        /// <summary>
        ///     Resumes the current playing track asynchronously.
        /// </summary>
        /// <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 current playing track is not paused
        /// </exception>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual async Task ResumeAsync()
        {
            EnsureNotDestroyed();
            EnsureConnected();

            if (State != PlayerState.Paused)
            {
                throw new InvalidOperationException("There is no track paused.");
            }

            await LavalinkSocket.SendPayloadAsync(new PlayerPausePayload(GuildId, false));

            State = PlayerState.Playing;
        }
        /// <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>
        ///     Pauses the current playing track asynchronously.
        /// </summary>
        /// <returns>a task that represents the asynchronous operation</returns>
        /// <exception cref="InvalidOperationException">
        ///     thrown if the current playing track is already paused.
        /// </exception>
        /// <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 PauseAsync()
        {
            EnsureNotDestroyed();
            EnsureConnected();

            if (State != PlayerState.Playing)
            {
                throw new InvalidOperationException("The current playing track is not playing.");
            }

            await LavalinkSocket.SendPayloadAsync(new PlayerPausePayload(GuildId, true));

            State = PlayerState.Paused;
        }
        /// <summary>
        ///     Updates the player equalizer asynchronously.
        /// </summary>
        /// <param name="bands">the bands</param>
        /// <param name="reset">
        ///     a value indicating whether the equalizer bands should be overridden ( <see
        ///     langword="false"/>) or replaced ( <see langword="true"/>).
        /// </param>
        /// <param name="force">
        ///     a value indicating whether to send the update regardless of whether the current
        ///     equalizer bands ( <see cref="Bands"/>) are configured the same as the specified
        ///     <paramref name="bands"/>.
        /// </param>
        /// <returns>a task that represents the asynchronous operation</returns>
        /// <exception cref="InvalidOperationException">thrown if the player is destroyed</exception>
        public virtual Task UpdateEqualizerAsync(IEnumerable <EqualizerBand> bands, bool reset = true, bool force = false)
        {
            EnsureNotDestroyed();

            if (reset)
            {
                bands = bands.Union(DefaultEqualizer, EqualizerBandComparer.Instance);
            }

            if (!force && Bands.SequenceEqual(bands, EqualizerBandComparer.Instance))
            {
                // equalizer bands are the same
                return(Task.CompletedTask);
            }

            Bands = bands.ToArray();

            var payload = new PlayerEqualizerPayload(GuildId, Bands);

            return(LavalinkSocket.SendPayloadAsync(payload));
        }
        /// <summary>
        ///     Sends the voice state and server data to the Lavalink Node if both is provided.
        /// </summary>
        /// <returns>a task that represents the asynchronous operation</returns>
        internal async Task UpdateAsync()
        {
            if (_voiceServer is null || _voiceState is null)
            {
                // voice state or server is missing
                return;
            }

            // send voice update payload
            await LavalinkSocket.SendPayloadAsync(new VoiceUpdatePayload(_voiceState.GuildId,
                                                                         _voiceState.VoiceSessionId, new VoiceServerUpdateEvent(_voiceServer)));

            if (State == PlayerState.NotConnected || State == PlayerState.Destroyed)
            {
                // set initial player state to connected, if player was not connected or destroyed,
                // see: https://github.com/angelobreuer/Lavalink4NET/issues/28
                State = PlayerState.NotPlaying;
            }

            // trigger event
            await OnConnectedAsync(_voiceServer, _voiceState);
        }