This commit is contained in:
Tim Müller
2022-03-29 22:12:22 +02:00
commit 147eed234f
76 changed files with 6489 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TomatenMusic.Services;
using System.Linq;
using SpotifyAPI.Web;
using Lavalink4NET.Player;
using Microsoft.Extensions.DependencyInjection;
using Lavalink4NET;
using TomatenMusicCore.Music;
using TomatenMusicCore.Music.Entities;
namespace TomatenMusic.Music.Entitites
{
public class FullTrackContext
{
public bool IsFile { get; set; }
public string YoutubeDescription { get; set; }
public IEnumerable<string> YoutubeTags { get; set; }
public ulong YoutubeViews { get; set; }
public ulong YoutubeLikes { get; set; }
public Uri YoutubeThumbnail { get; set; }
public DateTime YoutubeUploadDate { get; set; }
//
// Summary:
// Gets the author of the track.
public Uri YoutubeAuthorThumbnail { get; set; }
public ulong YoutubeAuthorSubs { get; set; }
public Uri YoutubeAuthorUri { get; set; }
public ulong? YoutubeCommentCount { get; set; }
public string SpotifyIdentifier { get; set; }
public SimpleAlbum SpotifyAlbum { get; set; }
public List<SimpleArtist> SpotifyArtists { get; set; }
public int SpotifyPopularity { get; set; }
public Uri SpotifyUri { get; set; }
public static async Task<TomatenMusicTrack> PopulateAsync(TomatenMusicTrack track, FullTrack spotifyTrack = null, string spotifyId = null)
{
FullTrackContext context = (FullTrackContext)track.Context;
if (context == null)
context = new FullTrackContext();
var spotifyService = TomatenMusicBot.ServiceProvider.GetRequiredService<ISpotifyService>();
var youtubeService = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
if (spotifyId != null)
context.SpotifyIdentifier = spotifyId;
else if (spotifyTrack != null)
context.SpotifyIdentifier = spotifyTrack.Id;
track.Context = context;
await youtubeService.PopulateTrackInfoAsync(track);
await spotifyService.PopulateTrackAsync(track, spotifyTrack);
return track;
}
public static async Task<TrackList> PopulateTracksAsync(TrackList tracks)
{
foreach (var trackItem in tracks)
{
await PopulateAsync(trackItem);
}
return tracks;
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using TomatenMusic.Util;
using DSharpPlus.Entities;
using Lavalink4NET.Player;
using TomatenMusicCore.Music;
using TomatenMusicCore.Music.Entities;
namespace TomatenMusic.Music.Entitites
{
public interface ILavalinkPlaylist : IPlayableItem
{
public string Title { get; }
public TrackList Tracks { get; }
public Uri Url { get; }
public string AuthorName { get; set; }
public Uri AuthorUri { get; set; }
public string Description { get; set; }
public string Identifier { get; }
public Uri AuthorThumbnail { get; set; }
public TimeSpan GetLength()
{
TimeSpan timeSpan = TimeSpan.FromTicks(0);
foreach (var track in Tracks)
{
timeSpan = timeSpan.Add(track.Duration);
}
return timeSpan;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TomatenMusic.Music;
namespace TomatenMusicCore.Music.Entities
{
public interface IPlayableItem
{
public string Title { get; }
Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true);
Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false);
}
}

View File

@@ -0,0 +1,63 @@
using Lavalink4NET.Player;
using System;
using System.Collections.Generic;
using System.Text;
using TomatenMusicCore.Music;
using TomatenMusicCore.Music.Entities;
namespace TomatenMusic.Music.Entitites
{
public class SpotifyPlaylist : ILavalinkPlaylist
{
public string Title { get; }
public TrackList Tracks { get; }
public Uri Url { get; set; }
public string AuthorName { get; set; }
public Uri AuthorUri { get; set; }
public string Description { get; set; }
public int Followers { get; set; }
public string Identifier { get; }
public Uri AuthorThumbnail { get; set; }
public SpotifyPlaylist(string name, string id, TrackList tracks, Uri uri)
{
Title = name;
Identifier = id;
Tracks = tracks;
Url = uri;
}
public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
{
await player.PlayerQueue.QueuePlaylistAsync(this);
if (player.State == PlayerState.NotPlaying)
{
LavalinkTrack nextTrack = player.PlayerQueue.NextTrack().Track;
await player.PlayAsync(nextTrack);
}
}
public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
{
if (!player.PlayerQueue.Queue.Any())
player.PlayerQueue.CurrentPlaylist = this;
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(Tracks);
TomatenMusicTrack track = reversedTracks.Dequeue();
player.PlayerQueue.LastTrack = track;
await player.PlayAsync(track);
reversedTracks.Reverse();
foreach (var item in reversedTracks)
{
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(item));
}
}
}
}

View File

@@ -0,0 +1,50 @@
using Lavalink4NET.Player;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TomatenMusic.Music;
using TomatenMusic.Prompt.Implementation;
namespace TomatenMusicCore.Music.Entities
{
public class TomatenMusicTrack : LavalinkTrack, IPlayableItem
{
public override TimeSpan Position { get; }
public TomatenMusicTrack
(LavalinkTrack track)
: base(track.Identifier, track.Author, track.Duration, track.IsLiveStream, track.IsSeekable, track.Source, track.Title, track.TrackIdentifier, track.Provider)
{
Context = track.Context;
Position = track.Position;
}
public string Title => base.Title;
public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
{
if (player.State == PlayerState.NotPlaying)
{
player.PlayerQueue.LastTrack = this;
await player.PlayAsync(this, startTime, endTime, noReplace);
}
else
player.PlayerQueue.QueueTrack(this);
}
public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
{
if (!withoutQueuePrepend)
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
player.PlayerQueue.LastTrack = this;
await player.PlayAsync(this, startTime, endTime);
}
}
}

View File

@@ -0,0 +1,567 @@

using Lavalink4NET.Player;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TomatenMusic.Music;
namespace TomatenMusicCore.Music.Entities
{
//
// Summary:
// A thread-safe queue for Lavalink4NET.Player.LavalinkTrack.
public sealed class TrackList : IList<TomatenMusicTrack>, ICollection<TomatenMusicTrack>, IEnumerable<TomatenMusicTrack>, IEnumerable, IPlayableItem
{
private readonly List<TomatenMusicTrack> _list;
private readonly object _syncRoot;
//
// Summary:
// Gets the number of queued tracks.
//
// Remarks:
// This property is thread-safe, so it can be used from multiple threads at once
// safely.
public int Count
{
get
{
lock (_syncRoot)
{
return _list.Count;
}
}
}
//
// Summary:
// Gets a value indicating whether the queue is empty.
//
// Remarks:
// This property is thread-safe, so it can be used from multiple threads at once
// safely.
public bool IsEmpty => Count == 0;
//
// Summary:
// Gets a value indicating whether the queue is read-only.
//
// Remarks:
// This property is thread-safe, so it can be used from multiple threads at once
// safely.
public bool IsReadOnly => false;
//
// Summary:
// Gets or sets the enqueued tracks.
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public IReadOnlyList<TomatenMusicTrack> Tracks
{
get
{
lock (_syncRoot)
{
return _list.ToArray();
}
}
set
{
lock (_syncRoot)
{
_list.Clear();
_list.AddRange(value);
}
}
}
public string Title => $"Track List with {Count} Tracks";
//
// Summary:
// Gets or sets the track at the specified index.
//
// Parameters:
// index:
// the zero-based position
//
// Returns:
// the track at the specified index
//
// Remarks:
// This indexer property is thread-safe, so it can be used from multiple threads
// at once safely.
public TomatenMusicTrack this[int index]
{
get
{
lock (_syncRoot)
{
return _list[index];
}
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
lock (_syncRoot)
{
_list[index] = value;
}
}
}
public TrackList()
{
_list = new List<TomatenMusicTrack>();
_syncRoot = new object();
}
public TrackList(IEnumerable<LavalinkTrack> tracks)
{
_list = new List<TomatenMusicTrack>();
_syncRoot = new object();
foreach (var track in tracks)
Add(new TomatenMusicTrack(track));
}
//
// Summary:
// Adds a track at the end of the queue.
//
// Parameters:
// track:
// the track to add
//
// Exceptions:
// T:System.ArgumentNullException:
// thrown if the specified track is null.
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void Add(TomatenMusicTrack track)
{
if (track == null)
{
throw new ArgumentNullException("track");
}
lock (_syncRoot)
{
_list.Add(track);
}
}
//
// Summary:
// Adds all specified tracks to the queue.
//
// Parameters:
// tracks:
// the tracks to enqueue
//
// Exceptions:
// T:System.ArgumentNullException:
// thrown if the specified tracks enumerable is null.
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void AddRange(IEnumerable<TomatenMusicTrack> tracks)
{
if (tracks == null)
{
throw new ArgumentNullException("tracks");
}
lock (_syncRoot)
{
_list.AddRange(tracks);
}
}
//
// Summary:
// Clears all tracks from the queue.
//
// Returns:
// the number of tracks removed
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public int Clear()
{
lock (_syncRoot)
{
int count = _list.Count;
_list.Clear();
return count;
}
}
//
// Summary:
// Gets a value indicating whether the specified track is in the queue.
//
// Parameters:
// track:
// the track to find
//
// Returns:
// a value indicating whether the specified track is in the queue
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public bool Contains(TomatenMusicTrack track)
{
if (track == null)
{
throw new ArgumentNullException("track");
}
lock (_syncRoot)
{
return _list.Contains(track);
}
}
//
// Summary:
// Copies all tracks to the specified array at the specified index.
//
// Parameters:
// array:
// the array to the tracks to
//
// index:
// the zero-based writing start index
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void CopyTo(TomatenMusicTrack[] array, int index)
{
lock (_syncRoot)
{
_list.CopyTo(array, index);
}
}
//
// Summary:
// Dequeues a track using the FIFO method.
//
// Returns:
// the dequeued track
//
// Exceptions:
// T:System.InvalidOperationException:
// thrown if no tracks were in the queue
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public TomatenMusicTrack Dequeue()
{
lock (_syncRoot)
{
if (_list.Count <= 0)
{
throw new InvalidOperationException("No tracks in to dequeue.");
}
TomatenMusicTrack result = _list[0];
_list.RemoveAt(0);
return result;
}
}
//
// Summary:
// Deletes all duplicate tracks from the queue.
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void Distinct()
{
lock (_syncRoot)
{
if (_list.Count > 1)
{
TomatenMusicTrack[] collection = (from track in _list
group track by track.Identifier into s
select s.First()).ToArray();
_list.Clear();
_list.AddRange(collection);
}
}
}
//
// Summary:
// Gets the track enumerator.
//
// Returns:
// the track enumerator
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public IEnumerator<TomatenMusicTrack> GetEnumerator()
{
lock (_syncRoot)
{
return _list.ToList().GetEnumerator();
}
}
//
// Summary:
// Gets the zero-based index of the specified track.
//
// Parameters:
// track:
// the track to locate
//
// Returns:
// the zero-based index of the specified track
//
// Exceptions:
// T:System.ArgumentNullException:
// thrown if the specified track is null.
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public int IndexOf(TomatenMusicTrack track)
{
if (track == null)
{
throw new ArgumentNullException("track");
}
lock (_syncRoot)
{
return _list.IndexOf(track);
}
}
//
// Summary:
// Inserts the specified track at the specified index.
//
// Parameters:
// index:
// the zero-based index to insert (e.g. 0 = top)
//
// track:
// the track to insert
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void Insert(int index, TomatenMusicTrack track)
{
lock (_syncRoot)
{
_list.Insert(index, track);
}
}
//
// Summary:
// Tries to remove the specified track from the queue.
//
// Parameters:
// track:
// the track to remove
//
// Returns:
// a value indicating whether the track was found and removed from the queue
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public bool Remove(TomatenMusicTrack track)
{
lock (_syncRoot)
{
return _list.Remove(track);
}
}
//
// Summary:
// Removes all tracks that matches the specified predicate.
//
// Parameters:
// predicate:
// the track predicate
//
// Returns:
// the number of tracks removed
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public int RemoveAll(Predicate<TomatenMusicTrack> predicate)
{
lock (_syncRoot)
{
return _list.RemoveAll(predicate);
}
}
//
// Summary:
// Removes a track at the specified index.
//
// Parameters:
// index:
// the index to remove the track
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void RemoveAt(int index)
{
lock (_syncRoot)
{
_list.RemoveAt(index);
}
}
//
// Summary:
// Removes all count tracks from the specified index.
//
// Parameters:
// index:
// the start index (zero-based)
//
// count:
// the number of tracks to remove
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void RemoveRange(int index, int count)
{
lock (_syncRoot)
{
_list.RemoveRange(index, count);
}
}
//
// Summary:
// Shuffles / mixes all tracks in the queue.
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public void Shuffle()
{
lock (_syncRoot)
{
if (_list.Count > 2)
{
TomatenMusicTrack[] collection = _list.OrderBy((TomatenMusicTrack s) => Guid.NewGuid()).ToArray();
_list.Clear();
_list.AddRange(collection);
}
}
}
//
// Summary:
// Tries to dequeue a track using the FIFO method.
//
// Parameters:
// track:
// the dequeued track; or default is the result is false.
//
// Returns:
// a value indicating whether a track was dequeued.
//
// Exceptions:
// T:System.InvalidOperationException:
// thrown if no tracks were in the queue
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
public bool TryDequeue(out TomatenMusicTrack? track)
{
lock (_syncRoot)
{
if (_list.Count <= 0)
{
track = null;
return false;
}
track = _list[0];
_list.RemoveAt(0);
return true;
}
}
//
// Summary:
// Clears the queue.
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
void ICollection<TomatenMusicTrack>.Clear()
{
lock (_syncRoot)
{
_list.Clear();
}
}
//
// Summary:
// Gets the track enumerator.
//
// Returns:
// the track enumerator
//
// Remarks:
// This method is thread-safe, so it can be used from multiple threads at once safely.
IEnumerator IEnumerable.GetEnumerator()
{
lock (_syncRoot)
{
return _list.ToArray().GetEnumerator();
}
}
public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
{
await player.PlayerQueue.QueueTracksAsync(this);
if (player.State == PlayerState.NotPlaying)
{
LavalinkTrack nextTrack = player.PlayerQueue.NextTrack().Track;
await player.PlayAsync(nextTrack, startTime, endTime, noReplace);
}
}
public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
{
Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(this);
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
TomatenMusicTrack track = reversedTracks.Dequeue();
player.PlayerQueue.LastTrack = track;
await player.PlayAsync(track, startTime, endTime);
reversedTracks.Reverse();
foreach (var item in reversedTracks)
{
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(item));
}
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Google.Apis.YouTube.v3.Data;
using Lavalink4NET.Player;
using Microsoft.Extensions.DependencyInjection;
using TomatenMusic.Services;
using TomatenMusicCore.Music;
using TomatenMusicCore.Music.Entities;
namespace TomatenMusic.Music.Entitites
{
public class YoutubePlaylist : ILavalinkPlaylist
{
public string Title { get; }
public TrackList Tracks { get; }
public int TrackCount { get; }
public Uri Url { get; }
public string AuthorName { get; set; }
public Uri AuthorUri { get; set; }
public string Description { get; set; }
public Uri Thumbnail { get; set; }
public DateTime CreationTime { get; set; }
public string Identifier { get; }
public Playlist YoutubeItem { get; set; }
public Uri AuthorThumbnail { get; set; }
public YoutubePlaylist(string name, TrackList tracks, string id)
{
Identifier = id;
Title = name;
Tracks = tracks;
Url = new Uri($"https://youtube.com/playlist?list={id}");
TrackCount = tracks.Count();
}
public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
{
await player.PlayerQueue.QueuePlaylistAsync(this);
if (player.State == PlayerState.NotPlaying)
{
LavalinkTrack nextTrack = player.PlayerQueue.NextTrack().Track;
await player.PlayAsync(nextTrack);
}
}
public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
{
if (!player.PlayerQueue.Queue.Any())
player.PlayerQueue.CurrentPlaylist = this;
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(Tracks);
TomatenMusicTrack track = reversedTracks.Dequeue();
player.PlayerQueue.LastTrack = track;
await player.PlayAsync(track);
reversedTracks.Reverse();
foreach (var item in reversedTracks)
{
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(item));
}
}
}
}

View File

@@ -0,0 +1,290 @@
using DSharpPlus;
using System;
using System.Collections.Generic;
using System.Text;
using DSharpPlus.Entities;
using System.Threading.Tasks;
using System.Linq;
using TomatenMusic.Music.Entitites;
using Microsoft.Extensions.Logging;
using TomatenMusic.Services;
using TomatenMusic.Prompt.Implementation;
using Lavalink4NET.Player;
using Lavalink4NET.Events;
using Lavalink4NET;
using Lavalink4NET.Rest;
using Microsoft.Extensions.DependencyInjection;
using Lavalink4NET.Decoding;
using TomatenMusicCore.Music;
using TomatenMusicCore.Music.Entities;
namespace TomatenMusic.Music
{
public class GuildPlayer : LavalinkPlayer
{
ILogger<GuildPlayer> _logger { get; set; }
public PlayerQueue PlayerQueue { get;} = new PlayerQueue();
public DiscordClient _client { get; set; }
public ISpotifyService _spotify { get; set; }
public IAudioService _audioService { get; set; }
public bool Autoplay { get; set; } = false;
public GuildPlayer()
{
IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
_logger = serviceProvider.GetRequiredService<ILogger<GuildPlayer>>();
var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
_client = client.GetShard(GuildId);
_spotify = serviceProvider.GetRequiredService<ISpotifyService>();
_audioService = serviceProvider.GetRequiredService<IAudioService>();
}
public async Task PlayItemAsync(IPlayableItem item, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
{
EnsureConnected();
EnsureNotDestroyed();
_ = item.Play(this, startTime, endTime, noReplace);
_logger.LogInformation("Started playing Item {0} on Guild {1}", item.Title, (await GetGuildAsync()).Name);
QueuePrompt.UpdateFor(GuildId);
}
public async Task PlayNowAsync(IPlayableItem item, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
{
EnsureConnected();
EnsureNotDestroyed();
_ = item.PlayNow(this, startTime, endTime, withoutQueuePrepend);
_logger.LogInformation("Started playing Item {0} now on Guild {1}", item.Title, (await GetGuildAsync()).Name);
QueuePrompt.UpdateFor(GuildId);
}
public async Task PlayPlaylistAsync(ILavalinkPlaylist playlist)
{
EnsureNotDestroyed();
EnsureConnected();
_logger.LogInformation("Started playing Playlist {0} on Guild {1}", playlist.Title, (await GetGuildAsync()).Name);
QueuePrompt.UpdateFor(GuildId);
}
public async Task PlayPlaylistNowAsync(ILavalinkPlaylist playlist)
{
EnsureConnected();
EnsureNotDestroyed();
QueuePrompt.UpdateFor(GuildId);
}
public async Task RewindAsync()
{
EnsureNotDestroyed();
EnsureConnected();
if (Position.Position.Seconds > 5)
{
await ReplayAsync();
return;
}
MusicActionResponse response = PlayerQueue.Rewind();
_logger.LogInformation($"Rewinded Track {CurrentTrack.Title} for Track {response.Track.Title}");
await base.PlayAsync(response.Track);
QueuePrompt.UpdateFor(GuildId);
}
public async Task SkipAsync()
{
EnsureNotDestroyed();
EnsureConnected();
MusicActionResponse response;
try
{
response = PlayerQueue.NextTrack(true);
}catch (Exception ex)
{
if (Autoplay)
{
YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
LavalinkTrack newTrack = await youtube.GetRelatedTrackAsync(CurrentTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
_logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Autoplayed Track {newTrack.Title}");
await PlayAsync(newTrack);
QueuePrompt.UpdateFor(GuildId);
return;
}
throw ex;
}
_logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Track {response.Track.Title}");
await base.PlayAsync(response.Track);
QueuePrompt.UpdateFor(GuildId);
}
public async Task TogglePauseAsync()
{
EnsureNotDestroyed();
EnsureConnected();
if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant pause Song! Nothing is Playing.");
if (State == PlayerState.Paused)
await ResumeAsync();
else
await PauseAsync();
QueuePrompt.UpdateFor(GuildId);
}
public async Task SetLoopAsync(LoopType type)
{
EnsureNotDestroyed();
EnsureConnected();
if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");
_ = PlayerQueue.SetLoopAsync(type);
QueuePrompt.UpdateFor(GuildId);
}
public async Task ShuffleAsync()
{
EnsureNotDestroyed();
EnsureConnected();
await PlayerQueue.ShuffleAsync();
QueuePrompt.UpdateFor(GuildId);
}
public async override Task ConnectAsync(ulong voiceChannelId, bool selfDeaf = true, bool selfMute = false)
{
EnsureNotDestroyed();
DiscordChannel channel = await _client.GetChannelAsync(voiceChannelId);
if (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage) throw new ArgumentException("The channel Id provided was not a voice channel");
if (State != PlayerState.NotConnected)
throw new InvalidOperationException("The Bot is already connected.");
await base.ConnectAsync(voiceChannelId, selfDeaf, selfMute);
if (channel.Type == ChannelType.Stage)
{
DiscordStageInstance stageInstance = await channel.GetStageInstanceAsync();
if (stageInstance == null)
stageInstance = await channel.CreateStageInstanceAsync("Music");
await stageInstance.Channel.UpdateCurrentUserVoiceStateAsync(false);
}
_logger.LogInformation("Connected to Channel {0} on Guild {1}", channel.Name, channel.Guild.Name);
}
public override Task DisconnectAsync()
{
_logger.LogInformation("Disconnected from Channel {0} on Guild {1}", VoiceChannelId, GuildId);
QueuePrompt.InvalidateFor(GuildId);
return base.DisconnectAsync();
}
public override async Task SeekPositionAsync(TimeSpan timeSpan)
{
EnsureNotDestroyed();
EnsureConnected();
if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");
if (timeSpan.CompareTo(CurrentTrack.Duration) == 1) throw new ArgumentException("Please specify a TimeSpan shorter than the Track");
await base.SeekPositionAsync(timeSpan);
QueuePrompt.UpdateFor(GuildId);
}
protected override void Dispose(bool disposing)
{
QueuePrompt.InvalidateFor(GuildId);
base.Dispose(disposing);
}
public async override Task OnTrackEndAsync(TrackEndEventArgs eventArgs)
{
DisconnectOnStop = false;
YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
var oldTrack = CurrentTrack;
if (eventArgs.Reason != TrackEndReason.Finished)
return;
if (eventArgs.MayStartNext)
{
try
{
MusicActionResponse response = PlayerQueue.NextTrack();
_ = PlayNowAsync(response.Track, withoutQueuePrepend: true);
}
catch (Exception ex)
{
if (!Autoplay)
{
_logger.LogInformation("Track has ended and Queue was Empty... Idling");
QueuePrompt.UpdateFor(GuildId);
await base.OnTrackEndAsync(eventArgs);
return;
}
TomatenMusicTrack newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
_logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
await base.OnTrackEndAsync(eventArgs);
await newTrack.Play(this);
QueuePrompt.UpdateFor(GuildId);
}
}
}
public async Task<DiscordChannel> GetChannelAsync()
{
EnsureConnected();
EnsureNotDestroyed();
DiscordGuild guild = await GetGuildAsync();
return guild.GetChannel((ulong) VoiceChannelId);
}
public async Task<DiscordGuild> GetGuildAsync()
{
return await _client.GetGuildAsync(GuildId);
}
public async Task<bool> AreActionsAllowedAsync(DiscordMember member)
{
if (member.VoiceState == null || member.VoiceState.Channel == null)
{
return false;
}
if (await GetChannelAsync() != null && await GetChannelAsync() != member.VoiceState.Channel)
{
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using DSharpPlus.SlashCommands;
namespace TomatenMusic.Music
{
public enum LoopType
{
[ChoiceName("Track")]
TRACK,
[ChoiceName("Queue")]
QUEUE,
[ChoiceName("None")]
NONE
}
}

View File

@@ -0,0 +1,31 @@
using Lavalink4NET.Player;
using System;
using System.Collections.Generic;
using System.Text;
using TomatenMusic.Music.Entitites;
using TomatenMusicCore.Music;
using TomatenMusicCore.Music.Entities;
namespace TomatenMusic.Music
{
public class MusicActionResponse
{
public ILavalinkPlaylist Playlist { get; }
public TomatenMusicTrack Track { get; }
public TrackList Tracks { get; }
public bool IsPlaylist { get; }
public MusicActionResponse(TomatenMusicTrack track = null, ILavalinkPlaylist playlist = null, TrackList tracks = null)
{
Playlist = playlist;
Track = track;
IsPlaylist = playlist != null;
Tracks = tracks;
if (track != null)
{
var list = new TrackList();
list.Add(track);
Tracks = list;
}
}
}
}

View File

@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Text;
using DSharpPlus;
using TomatenMusic.Music.Entitites;
using System.Threading.Tasks;
using System.Linq;
using TomatenMusic.Util;
using Microsoft.Extensions.Logging;
using Lavalink4NET.Player;
using Microsoft.Extensions.DependencyInjection;
using TomatenMusicCore.Music;
using TomatenMusicCore.Music.Entities;
namespace TomatenMusic.Music
{
public class PlayerQueue
{
public Queue<TomatenMusicTrack> Queue { get; set; } = new Queue<TomatenMusicTrack>();
public Queue<TomatenMusicTrack> PlayedTracks { get; set; } = new Queue<TomatenMusicTrack>();
public ILogger<PlayerQueue> _logger { get; set; } = TomatenMusicBot.ServiceProvider.GetRequiredService<ILogger<PlayerQueue>>();
public ILavalinkPlaylist CurrentPlaylist { get; set; }
public LoopType LoopType { get; private set; } = LoopType.NONE;
public TomatenMusicTrack LastTrack { get; set; }
public List<TomatenMusicTrack> QueueLoopList { get; private set; }
public void QueueTrack(TomatenMusicTrack track)
{
CurrentPlaylist = null;
Queue.Enqueue(track);
_logger.LogInformation("Queued Track {0}", track.Title);
if (LoopType == LoopType.QUEUE)
QueueLoopList.Add(track);
}
public Task QueuePlaylistAsync(ILavalinkPlaylist playlist)
{
return Task.Run(() =>
{
if (CurrentPlaylist == null && Queue.Count == 0)
CurrentPlaylist = playlist;
else
CurrentPlaylist = null;
_logger.LogInformation("Queued Playlist {0}", playlist.Title);
foreach (var track in playlist.Tracks)
{
Queue.Enqueue(track);
}
if (LoopType == LoopType.QUEUE)
QueueLoopList.AddRange(playlist.Tracks);
});
}
public Task QueueTracksAsync(TrackList tracks)
{
return Task.Run(() =>
{
CurrentPlaylist = null;
_logger.LogInformation("Queued TrackList {0}", tracks.ToString());
foreach (var track in tracks)
{
Queue.Enqueue(track);
}
if (LoopType == LoopType.QUEUE)
QueueLoopList.AddRange(tracks);
});
}
public void Clear()
{
Queue.Clear();
PlayedTracks.Clear();
}
public void RemoveAt(int index)
{
if (Queue.Count == 0) throw new InvalidOperationException("Queue was Empty");
List<TomatenMusicTrack> tracks = Queue.ToList();
tracks.RemoveAt(index);
Queue = new Queue<TomatenMusicTrack>(tracks);
}
public MusicActionResponse NextTrack(bool ignoreLoop = false)
{
if (LastTrack != null)
PlayedTracks = new Queue<TomatenMusicTrack>(PlayedTracks.Prepend(LastTrack));
switch (LoopType)
{
case LoopType.NONE:
if (Queue.Count == 0) throw new InvalidOperationException("Queue was Empty");
LastTrack = Queue.Dequeue();
return new MusicActionResponse(LastTrack);
case LoopType.TRACK:
if (ignoreLoop)
{
LastTrack = Queue.Dequeue();
return new MusicActionResponse(LastTrack);
}
return new MusicActionResponse(LastTrack);
case LoopType.QUEUE:
if (!Queue.Any())
{
if (CurrentPlaylist != null)
Queue = new Queue<TomatenMusicTrack>(CurrentPlaylist.Tracks);
else
Queue = new Queue<TomatenMusicTrack>(QueueLoopList);
}
LastTrack = Queue.Dequeue();
return new MusicActionResponse(LastTrack);
default:
throw new NullReferenceException("LoopType was null");
}
}
public MusicActionResponse Rewind()
{
if (!PlayedTracks.Any()) throw new InvalidOperationException("There are no songs that could be rewinded to yet.");
Queue = new Queue<TomatenMusicTrack>(Queue.Prepend(LastTrack));
LastTrack = PlayedTracks.Dequeue();
return new MusicActionResponse(LastTrack);
}
public Task ShuffleAsync()
{
if (Queue.Count == 0) throw new InvalidOperationException("Queue is Empty");
List<TomatenMusicTrack> tracks = new List<TomatenMusicTrack>(Queue);
tracks.Shuffle();
Queue = new Queue<TomatenMusicTrack>(tracks);
return Task.CompletedTask;
}
public async Task SetLoopAsync(LoopType type)
{
LoopType = type;
if (type == LoopType.QUEUE)
{
QueueLoopList = new List<TomatenMusicTrack>(Queue);
QueueLoopList.Add(LastTrack);
}
}
}
}