diff --git a/src/Lavalink4NET/Players/LavalinkPlayerHandle.cs b/src/Lavalink4NET/Players/LavalinkPlayerHandle.cs index bbbf1047..949b4213 100644 --- a/src/Lavalink4NET/Players/LavalinkPlayerHandle.cs +++ b/src/Lavalink4NET/Players/LavalinkPlayerHandle.cs @@ -141,12 +141,25 @@ private async ValueTask CompleteAsync(bool isVoiceServerUpdated, CancellationTok if (_value is TaskCompletionSource taskCompletionSource) { // _value is automatically set by CreatePlayerAsync - var player = await CreatePlayerAsync(cancellationToken).ConfigureAwait(false); - - taskCompletionSource.TrySetResult(player); - - Interlocked.Decrement(ref Diagnostics.PendingHandles); - Interlocked.Increment(ref Diagnostics.ActivePlayers); + try + { + // CreatePlayerAsync can throw if request to lavalink fails + // We should handle this to avoid never completed lavalink player handle + var player = await CreatePlayerAsync(cancellationToken).ConfigureAwait(false); + + taskCompletionSource.TrySetResult(player); + + Interlocked.Decrement(ref Diagnostics.PendingHandles); + Interlocked.Increment(ref Diagnostics.ActivePlayers); + } + catch (Exception e) + { + // Here we're passing CancellationToken.None to ensure what the player disposing event will properly + // clean up this handle from cache + await _playerContext.LifecycleNotifier!.NotifyDisposeAsync(_guildId, CancellationToken.None); + taskCompletionSource.TrySetException(e); + await DisposeAsync(); + } } else { @@ -198,7 +211,8 @@ private async ValueTask CreatePlayerAsync(CancellationToken cancellatio if (initialTrack.Reference.IsPresent) { - playerProperties = playerProperties with { TrackData = initialTrack.Track!.ToString(), }; + var playableTrack = await initialTrack.Reference.Track.GetPlayableTrackAsync(cancellationToken); + playerProperties = playerProperties with { TrackData = playableTrack.ToString()}; } else { @@ -210,6 +224,11 @@ private async ValueTask CreatePlayerAsync(CancellationToken cancellatio } } + if (_options.Value.InitialPosition is not null) + { + playerProperties = playerProperties with { Position = _options.Value.InitialPosition.Value }; + } + if (_options.Value.InitialVolume is not null) { playerProperties = playerProperties with { Volume = _options.Value.InitialVolume.Value, }; diff --git a/src/Lavalink4NET/Players/LavalinkPlayerOptions.cs b/src/Lavalink4NET/Players/LavalinkPlayerOptions.cs index 86ea56c9..5023b205 100644 --- a/src/Lavalink4NET/Players/LavalinkPlayerOptions.cs +++ b/src/Lavalink4NET/Players/LavalinkPlayerOptions.cs @@ -1,4 +1,6 @@ -namespace Lavalink4NET.Players; +using System; + +namespace Lavalink4NET.Players; using Lavalink4NET.Rest.Entities.Tracks; @@ -12,6 +14,8 @@ public record class LavalinkPlayerOptions public ITrackQueueItem? InitialTrack { get; set; } + public TimeSpan? InitialPosition { get; set; } + public TrackLoadOptions InitialLoadOptions { get; set; } public float? InitialVolume { get; set; } @@ -19,4 +23,4 @@ public record class LavalinkPlayerOptions public bool SelfDeaf { get; set; } public bool SelfMute { get; set; } -} +} \ No newline at end of file diff --git a/src/Lavalink4NET/Players/PlayerManager.cs b/src/Lavalink4NET/Players/PlayerManager.cs index 376c5bd3..7f7df6aa 100644 --- a/src/Lavalink4NET/Players/PlayerManager.cs +++ b/src/Lavalink4NET/Players/PlayerManager.cs @@ -312,11 +312,10 @@ async ValueTask IPlayerLifecycleNotifier.NotifyDisposeAsync(ulong guildId, Cance await using var _ = playerHandle.ConfigureAwait(false); - Debug.Assert(playerHandle.Player is not null); - Debug.Assert(playerHandle.Player is { State: PlayerState.Destroyed, }); - + // Player can be null if lavalink node will throw while creating player if (playerHandle.Player is not null) { + Debug.Assert(playerHandle.Player is { State: PlayerState.Destroyed, }); var eventArgs = new PlayerDestroyedEventArgs(playerHandle.Player); await PlayerDestroyed