280 lines
8.3 KiB
C#
280 lines
8.3 KiB
C#
using Discord;
|
|
using Discord.Audio;
|
|
using NadekoBot.Extensions;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
namespace NadekoBot.Modules.Music.Classes
|
|
{
|
|
|
|
public enum MusicType
|
|
{
|
|
Radio,
|
|
Normal,
|
|
Local,
|
|
Soundcloud
|
|
}
|
|
|
|
public enum StreamState
|
|
{
|
|
Resolving,
|
|
Queued,
|
|
Buffering, //not using it atm
|
|
Playing,
|
|
Completed
|
|
}
|
|
|
|
public class MusicPlayer
|
|
{
|
|
private IAudioClient audioClient { get; set; }
|
|
|
|
private readonly List<Song> playlist = new List<Song>();
|
|
public IReadOnlyCollection<Song> Playlist => playlist;
|
|
private readonly object playlistLock = new object();
|
|
|
|
public Song CurrentSong { get; set; } = default(Song);
|
|
private CancellationTokenSource SongCancelSource { get; set; }
|
|
private CancellationToken cancelToken { get; set; }
|
|
|
|
public bool Paused { get; set; }
|
|
|
|
public float Volume { get; private set; }
|
|
|
|
public event EventHandler<Song> OnCompleted = delegate { };
|
|
public event EventHandler<Song> OnStarted = delegate { };
|
|
|
|
public Channel PlaybackVoiceChannel { get; private set; }
|
|
|
|
private bool Destroyed { get; set; } = false;
|
|
public bool RepeatSong { get; private set; } = false;
|
|
public bool RepeatPlaylist { get; private set; } = false;
|
|
public bool Autoplay { get; set; } = false;
|
|
public uint MaxQueueSize { get; set; } = 0;
|
|
|
|
private ConcurrentQueue<Action> actionQueue { get; set; } = new ConcurrentQueue<Action>();
|
|
|
|
public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume)
|
|
{
|
|
if (startingVoiceChannel == null)
|
|
throw new ArgumentNullException(nameof(startingVoiceChannel));
|
|
if (startingVoiceChannel.Type != ChannelType.Voice)
|
|
throw new ArgumentException("Channel must be of type voice");
|
|
Volume = defaultVolume ?? 1.0f;
|
|
|
|
PlaybackVoiceChannel = startingVoiceChannel;
|
|
SongCancelSource = new CancellationTokenSource();
|
|
cancelToken = SongCancelSource.Token;
|
|
|
|
Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
while (!Destroyed)
|
|
{
|
|
try
|
|
{
|
|
Action action;
|
|
if (actionQueue.TryDequeue(out action))
|
|
{
|
|
action();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
await Task.Delay(100).ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("Action queue crashed");
|
|
Console.WriteLine(ex);
|
|
}
|
|
}).ConfigureAwait(false);
|
|
|
|
var t = new Thread(new ThreadStart(async () =>
|
|
{
|
|
try
|
|
{
|
|
while (!Destroyed)
|
|
{
|
|
try
|
|
{
|
|
if (audioClient.State != ConnectionState.Connected)
|
|
{
|
|
audioClient = await PlaybackVoiceChannel.JoinAudio();
|
|
continue;
|
|
}
|
|
|
|
var song = CurrentSong;
|
|
|
|
if (song == null)
|
|
continue;
|
|
|
|
try
|
|
{
|
|
await song.Play(audioClient, cancelToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Console.WriteLine("Song canceled");
|
|
SongCancelSource = new CancellationTokenSource();
|
|
cancelToken = SongCancelSource.Token;
|
|
}
|
|
OnCompleted(this, song);
|
|
|
|
if (RepeatPlaylist)
|
|
AddSong(song, song.QueuerName);
|
|
|
|
if (RepeatSong)
|
|
AddSong(song, 0);
|
|
}
|
|
finally
|
|
{
|
|
await Task.Delay(300).ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex) {
|
|
Console.WriteLine("Music thread crashed.");
|
|
Console.WriteLine(ex);
|
|
}
|
|
}));
|
|
|
|
t.Start();
|
|
}
|
|
|
|
public void Next()
|
|
{
|
|
Paused = false;
|
|
SongCancelSource.Cancel();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
actionQueue.Enqueue(() =>
|
|
{
|
|
playlist.Clear();
|
|
CurrentSong = null;
|
|
RepeatPlaylist = false;
|
|
RepeatSong = false;
|
|
if (!SongCancelSource.IsCancellationRequested)
|
|
SongCancelSource.Cancel();
|
|
});
|
|
}
|
|
|
|
public void TogglePause() => 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() =>
|
|
playlist.FirstOrDefault();
|
|
|
|
public void Shuffle()
|
|
{
|
|
actionQueue.Enqueue(() =>
|
|
{
|
|
playlist.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)
|
|
{
|
|
actionQueue.Enqueue(() =>
|
|
{
|
|
if (index < 0 || index >= playlist.Count)
|
|
return;
|
|
playlist.RemoveAt(index);
|
|
});
|
|
}
|
|
|
|
internal void ClearQueue()
|
|
{
|
|
actionQueue.Enqueue(() =>
|
|
{
|
|
playlist.Clear();
|
|
});
|
|
}
|
|
|
|
public void Destroy()
|
|
{
|
|
actionQueue.Enqueue(() =>
|
|
{
|
|
playlist.Clear();
|
|
Destroyed = true;
|
|
CurrentSong = null;
|
|
if (!SongCancelSource.IsCancellationRequested)
|
|
SongCancelSource.Cancel();
|
|
audioClient.Disconnect();
|
|
});
|
|
}
|
|
|
|
internal Task MoveToVoiceChannel(Channel voiceChannel)
|
|
{
|
|
if (audioClient?.State != ConnectionState.Connected)
|
|
throw new InvalidOperationException("Can't move while bot is not connected to voice channel.");
|
|
PlaybackVoiceChannel = voiceChannel;
|
|
return PlaybackVoiceChannel.JoinAudio();
|
|
}
|
|
|
|
internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong;
|
|
|
|
internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist;
|
|
|
|
internal bool ToggleAutoplay() => this.Autoplay = !this.Autoplay;
|
|
|
|
internal void ThrowIfQueueFull()
|
|
{
|
|
if (MaxQueueSize == 0)
|
|
return;
|
|
if (playlist.Count >= MaxQueueSize)
|
|
throw new PlaylistFullException();
|
|
}
|
|
}
|
|
}
|