Moved a part of music logic to a music service
This commit is contained in:
@@ -76,10 +76,10 @@ namespace NadekoBot.Modules.Administration
|
||||
{ "%servers%", () => NadekoBot.Client.Guilds.Count.ToString()},
|
||||
{ "%users%", () => NadekoBot.Client.Guilds.Sum(s => s.Users.Count).ToString()},
|
||||
{ "%playing%", () => {
|
||||
var cnt = Music.Music.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
|
||||
var cnt = NadekoBot.MusicService.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
|
||||
if (cnt != 1) return cnt.ToString();
|
||||
try {
|
||||
var mp = Music.Music.MusicPlayers.FirstOrDefault();
|
||||
var mp = NadekoBot.MusicService.MusicPlayers.FirstOrDefault();
|
||||
return mp.Value.CurrentSong.SongInfo.Title;
|
||||
}
|
||||
catch {
|
||||
@@ -87,7 +87,7 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
|
||||
{ "%queued%", () => NadekoBot.MusicService.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
|
||||
{ "%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
|
||||
{ "%shardcount%", () => NadekoBot.Client.Shards.Count.ToString() },
|
||||
};
|
||||
|
@@ -1,382 +0,0 @@
|
||||
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;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
|
||||
public enum MusicType
|
||||
{
|
||||
Radio,
|
||||
Normal,
|
||||
Local,
|
||||
Soundcloud
|
||||
}
|
||||
|
||||
public enum StreamState
|
||||
{
|
||||
Resolving,
|
||||
Queued,
|
||||
Playing,
|
||||
Completed
|
||||
}
|
||||
|
||||
public class MusicPlayer
|
||||
{
|
||||
private IAudioClient AudioClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Player will prioritize different queuer name
|
||||
/// over the song position in the playlist
|
||||
/// </summary>
|
||||
public bool FairPlay { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Song will stop playing after this amount of time.
|
||||
/// To prevent people queueing radio or looped songs
|
||||
/// while other people want to listen to other songs too.
|
||||
/// </summary>
|
||||
public uint MaxPlaytimeSeconds { get; set; } = 0;
|
||||
|
||||
|
||||
// this should be written better
|
||||
public TimeSpan TotalPlaytime =>
|
||||
_playlist.Any(s => s.TotalTime == TimeSpan.MaxValue) ?
|
||||
TimeSpan.MaxValue :
|
||||
new TimeSpan(_playlist.Sum(s => s.TotalTime.Ticks));
|
||||
|
||||
/// <summary>
|
||||
/// Users who recently got their music wish
|
||||
/// </summary>
|
||||
private ConcurrentHashSet<string> RecentlyPlayedUsers { get; } = new ConcurrentHashSet<string>();
|
||||
|
||||
private readonly List<Song> _playlist = new List<Song>();
|
||||
private readonly Logger _log;
|
||||
|
||||
public IReadOnlyCollection<Song> Playlist => _playlist;
|
||||
|
||||
public Song CurrentSong { get; private set; }
|
||||
public CancellationTokenSource SongCancelSource { get; private set; }
|
||||
private CancellationToken CancelToken { get; set; }
|
||||
|
||||
public bool Paused { get; set; }
|
||||
|
||||
public float Volume { get; private set; }
|
||||
|
||||
public event Action<MusicPlayer, Song> OnCompleted = delegate { };
|
||||
public event Action<MusicPlayer, Song> OnStarted = delegate { };
|
||||
public event Action<bool> OnPauseChanged = delegate { };
|
||||
|
||||
public IVoiceChannel PlaybackVoiceChannel { get; private set; }
|
||||
public ITextChannel OutputTextChannel { get; set; }
|
||||
|
||||
private bool Destroyed { get; set; }
|
||||
public bool RepeatSong { get; private set; }
|
||||
public bool RepeatPlaylist { get; private set; }
|
||||
public bool Autoplay { get; set; }
|
||||
public uint MaxQueueSize { get; set; } = 0;
|
||||
|
||||
private ConcurrentQueue<Action> ActionQueue { get; } = new ConcurrentQueue<Action>();
|
||||
|
||||
public string PrettyVolume => $"🔉 {(int)(Volume * 100)}%";
|
||||
|
||||
public event Action<Song, int> SongRemoved = delegate { };
|
||||
|
||||
public MusicPlayer(IVoiceChannel startingVoiceChannel, ITextChannel outputChannel, float? defaultVolume)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
OutputTextChannel = outputChannel;
|
||||
Volume = defaultVolume ?? 1.0f;
|
||||
|
||||
PlaybackVoiceChannel = startingVoiceChannel ?? throw new ArgumentNullException(nameof(startingVoiceChannel));
|
||||
SongCancelSource = new CancellationTokenSource();
|
||||
CancelToken = SongCancelSource.Token;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!Destroyed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ActionQueue.TryDequeue(out Action action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Action queue crashed");
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var t = new Thread(async () =>
|
||||
{
|
||||
while (!Destroyed)
|
||||
{
|
||||
try
|
||||
{
|
||||
CurrentSong = GetNextSong();
|
||||
|
||||
if (CurrentSong == null)
|
||||
continue;
|
||||
|
||||
if (AudioClient != null)
|
||||
try { await AudioClient.StopAsync().ConfigureAwait(false); } catch { }
|
||||
AudioClient = await PlaybackVoiceChannel.ConnectAsync().ConfigureAwait(false);
|
||||
|
||||
var index = _playlist.IndexOf(CurrentSong);
|
||||
if (index != -1)
|
||||
RemoveSongAt(index, true);
|
||||
|
||||
OnStarted(this, CurrentSong);
|
||||
try
|
||||
{
|
||||
await CurrentSong.Play(AudioClient, CancelToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnCompleted(this, CurrentSong);
|
||||
}
|
||||
|
||||
|
||||
if (RepeatPlaylist & !RepeatSong)
|
||||
AddSong(CurrentSong, CurrentSong.QueuerName);
|
||||
|
||||
if (RepeatSong)
|
||||
AddSong(CurrentSong, 0);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Music thread almost crashed.");
|
||||
_log.Warn(ex);
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!CancelToken.IsCancellationRequested)
|
||||
{
|
||||
SongCancelSource.Cancel();
|
||||
}
|
||||
SongCancelSource = new CancellationTokenSource();
|
||||
CancelToken = SongCancelSource.Token;
|
||||
CurrentSong = null;
|
||||
await Task.Delay(300).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
t.Start();
|
||||
}
|
||||
|
||||
public void Next()
|
||||
{
|
||||
ActionQueue.Enqueue(() =>
|
||||
{
|
||||
Paused = false;
|
||||
SongCancelSource.Cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
ActionQueue.Enqueue(() =>
|
||||
{
|
||||
RepeatPlaylist = false;
|
||||
RepeatSong = false;
|
||||
Autoplay = false;
|
||||
_playlist.Clear();
|
||||
if (!SongCancelSource.IsCancellationRequested)
|
||||
SongCancelSource.Cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public void TogglePause() => OnPauseChanged(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()
|
||||
{
|
||||
if (!FairPlay)
|
||||
{
|
||||
return _playlist.FirstOrDefault();
|
||||
}
|
||||
var song = _playlist.FirstOrDefault(c => !RecentlyPlayedUsers.Contains(c.QueuerName))
|
||||
?? _playlist.FirstOrDefault();
|
||||
|
||||
if (song == null)
|
||||
return null;
|
||||
|
||||
if (RecentlyPlayedUsers.Contains(song.QueuerName))
|
||||
{
|
||||
RecentlyPlayedUsers.Clear();
|
||||
}
|
||||
|
||||
RecentlyPlayedUsers.Add(song.QueuerName);
|
||||
return song;
|
||||
}
|
||||
|
||||
public void Shuffle()
|
||||
{
|
||||
ActionQueue.Enqueue(() =>
|
||||
{
|
||||
var oldPlaylist = _playlist.ToArray();
|
||||
_playlist.Clear();
|
||||
_playlist.AddRange(oldPlaylist.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, bool silent = false)
|
||||
{
|
||||
ActionQueue.Enqueue(() =>
|
||||
{
|
||||
if (index < 0 || index >= _playlist.Count)
|
||||
return;
|
||||
var song = _playlist.ElementAtOrDefault(index);
|
||||
if (_playlist.Remove(song) && !silent)
|
||||
{
|
||||
SongRemoved(song, index);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearQueue()
|
||||
{
|
||||
ActionQueue.Enqueue(() =>
|
||||
{
|
||||
_playlist.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task UpdateSongDurationsAsync()
|
||||
{
|
||||
var curSong = CurrentSong;
|
||||
var toUpdate = _playlist.Where(s => s.SongInfo.ProviderType == MusicType.Normal &&
|
||||
s.TotalTime == TimeSpan.Zero)
|
||||
.ToArray();
|
||||
if (curSong != null)
|
||||
{
|
||||
Array.Resize(ref toUpdate, toUpdate.Length + 1);
|
||||
toUpdate[toUpdate.Length - 1] = curSong;
|
||||
}
|
||||
var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3))
|
||||
.Distinct();
|
||||
|
||||
var durations = await NadekoBot.Google.GetVideoDurationsAsync(ids);
|
||||
|
||||
toUpdate.ForEach(s =>
|
||||
{
|
||||
foreach (var kvp in durations)
|
||||
{
|
||||
if (s.SongInfo.Query.EndsWith(kvp.Key))
|
||||
{
|
||||
s.TotalTime = kvp.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
ActionQueue.Enqueue(async () =>
|
||||
{
|
||||
RepeatPlaylist = false;
|
||||
RepeatSong = false;
|
||||
Autoplay = false;
|
||||
Destroyed = true;
|
||||
_playlist.Clear();
|
||||
|
||||
try { await AudioClient.StopAsync(); } catch { }
|
||||
if (!SongCancelSource.IsCancellationRequested)
|
||||
SongCancelSource.Cancel();
|
||||
});
|
||||
}
|
||||
|
||||
//public async Task MoveToVoiceChannel(IVoiceChannel voiceChannel)
|
||||
//{
|
||||
// if (audioClient?.ConnectionState != ConnectionState.Connected)
|
||||
// throw new InvalidOperationException("Can't move while bot is not connected to voice channel.");
|
||||
// PlaybackVoiceChannel = voiceChannel;
|
||||
// audioClient = await voiceChannel.ConnectAsync().ConfigureAwait(false);
|
||||
//}
|
||||
|
||||
public bool ToggleRepeatSong() => RepeatSong = !RepeatSong;
|
||||
|
||||
public bool ToggleRepeatPlaylist() => RepeatPlaylist = !RepeatPlaylist;
|
||||
|
||||
public bool ToggleAutoplay() => Autoplay = !Autoplay;
|
||||
|
||||
public void ThrowIfQueueFull()
|
||||
{
|
||||
if (MaxQueueSize == 0)
|
||||
return;
|
||||
if (_playlist.Count >= MaxQueueSize)
|
||||
throw new PlaylistFullException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Threading;
|
||||
using NadekoBot.Services.Music;
|
||||
|
||||
namespace NadekoBot.Modules.Music
|
||||
{
|
||||
@@ -22,10 +23,10 @@ namespace NadekoBot.Modules.Music
|
||||
[DontAutoLoad]
|
||||
public class Music : NadekoTopLevelModule
|
||||
{
|
||||
public static ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
|
||||
|
||||
public const string MusicDataPath = "data/musicdata";
|
||||
|
||||
private static MusicService music;
|
||||
|
||||
static Music()
|
||||
{
|
||||
//it can fail if its currenctly opened or doesn't exist. Either way i don't care
|
||||
@@ -34,6 +35,9 @@ namespace NadekoBot.Modules.Music
|
||||
NadekoBot.Client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
|
||||
|
||||
Directory.CreateDirectory(MusicDataPath);
|
||||
|
||||
//todo move to service
|
||||
music = NadekoBot.MusicService;
|
||||
}
|
||||
|
||||
private static Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState)
|
||||
@@ -44,13 +48,11 @@ namespace NadekoBot.Modules.Music
|
||||
return Task.CompletedTask;
|
||||
|
||||
MusicPlayer player;
|
||||
if (!MusicPlayers.TryGetValue(usr.Guild.Id, out player))
|
||||
if ((player = music.GetPlayer(usr.Guild.Id)) == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
//if bot moved
|
||||
if ((player.PlaybackVoiceChannel == oldState.VoiceChannel) &&
|
||||
usr.Id == NadekoBot.Client.CurrentUser.Id)
|
||||
@@ -92,7 +94,8 @@ namespace NadekoBot.Modules.Music
|
||||
return Task.CompletedTask;
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return Task.CompletedTask;
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return Task.CompletedTask;
|
||||
if (musicPlayer.PlaybackVoiceChannel == ((IGuildUser)Context.User).VoiceChannel)
|
||||
{
|
||||
while (--skipCount > 0)
|
||||
@@ -109,7 +112,8 @@ namespace NadekoBot.Modules.Music
|
||||
public Task Stop()
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return Task.CompletedTask;
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return Task.CompletedTask;
|
||||
if (((IGuildUser)Context.User).VoiceChannel == musicPlayer.PlaybackVoiceChannel)
|
||||
{
|
||||
musicPlayer.Autoplay = false;
|
||||
@@ -123,10 +127,10 @@ namespace NadekoBot.Modules.Music
|
||||
public Task Destroy()
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return Task.CompletedTask;
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return Task.CompletedTask;
|
||||
if (((IGuildUser)Context.User).VoiceChannel == musicPlayer.PlaybackVoiceChannel)
|
||||
if (MusicPlayers.TryRemove(Context.Guild.Id, out musicPlayer))
|
||||
musicPlayer.Destroy();
|
||||
music.DestroyPlayer(Context.Guild.Id);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -137,7 +141,8 @@ namespace NadekoBot.Modules.Music
|
||||
public Task Pause()
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return Task.CompletedTask;
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return Task.CompletedTask;
|
||||
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
|
||||
return Task.CompletedTask;
|
||||
musicPlayer.TogglePause();
|
||||
@@ -150,7 +155,8 @@ namespace NadekoBot.Modules.Music
|
||||
{
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return;
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
|
||||
return;
|
||||
var val = musicPlayer.FairPlay = !musicPlayer.FairPlay;
|
||||
@@ -169,7 +175,7 @@ namespace NadekoBot.Modules.Music
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Queue([Remainder] string query)
|
||||
{
|
||||
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query).ConfigureAwait(false);
|
||||
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query).ConfigureAwait(false);
|
||||
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
|
||||
{
|
||||
Context.Message.DeleteAfter(10);
|
||||
@@ -180,7 +186,7 @@ namespace NadekoBot.Modules.Music
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SoundCloudQueue([Remainder] string query)
|
||||
{
|
||||
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query, musicType: MusicType.Soundcloud).ConfigureAwait(false);
|
||||
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query, musicType: MusicType.Soundcloud).ConfigureAwait(false);
|
||||
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
|
||||
{
|
||||
Context.Message.DeleteAfter(10);
|
||||
@@ -193,8 +199,9 @@ namespace NadekoBot.Modules.Music
|
||||
{
|
||||
Song currentSong;
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer) ||
|
||||
(currentSong = musicPlayer?.CurrentSong) == null)
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
if ((currentSong = musicPlayer?.CurrentSong) == null)
|
||||
{
|
||||
await ReplyErrorLocalized("no_player").ConfigureAwait(false);
|
||||
return;
|
||||
@@ -253,7 +260,7 @@ namespace NadekoBot.Modules.Music
|
||||
public async Task NowPlaying()
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
var currentSong = musicPlayer.CurrentSong;
|
||||
if (currentSong == null)
|
||||
@@ -274,7 +281,7 @@ namespace NadekoBot.Modules.Music
|
||||
public async Task Volume(int val)
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
|
||||
return;
|
||||
@@ -308,9 +315,8 @@ namespace NadekoBot.Modules.Music
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ShufflePlaylist()
|
||||
{
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
|
||||
return;
|
||||
@@ -362,7 +368,7 @@ namespace NadekoBot.Modules.Music
|
||||
return;
|
||||
try
|
||||
{
|
||||
await QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, id, true).ConfigureAwait(false);
|
||||
await music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, id, true).ConfigureAwait(false);
|
||||
}
|
||||
catch (SongNotFoundException) { }
|
||||
catch { try { cancelSource.Cancel(); } catch { } }
|
||||
@@ -388,17 +394,17 @@ namespace NadekoBot.Modules.Music
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var scvids = JObject.Parse(await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false))["tracks"].ToObject<SoundCloudVideo[]>();
|
||||
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false);
|
||||
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false);
|
||||
|
||||
MusicPlayer mp;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out mp))
|
||||
MusicPlayer musicPlayer;
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
|
||||
foreach (var svideo in scvids.Skip(1))
|
||||
{
|
||||
try
|
||||
{
|
||||
mp.AddSong(new Song(new SongInfo
|
||||
musicPlayer.AddSong(new Song(new SongInfo
|
||||
{
|
||||
Title = svideo.FullName,
|
||||
Provider = "SoundCloud",
|
||||
@@ -429,7 +435,7 @@ namespace NadekoBot.Modules.Music
|
||||
{
|
||||
try
|
||||
{
|
||||
await QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false);
|
||||
await music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false);
|
||||
}
|
||||
catch (PlaylistFullException)
|
||||
{
|
||||
@@ -453,7 +459,7 @@ namespace NadekoBot.Modules.Music
|
||||
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radioLink, musicType: MusicType.Radio).ConfigureAwait(false);
|
||||
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radioLink, musicType: MusicType.Radio).ConfigureAwait(false);
|
||||
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
|
||||
{
|
||||
Context.Message.DeleteAfter(10);
|
||||
@@ -469,7 +475,7 @@ namespace NadekoBot.Modules.Music
|
||||
var arg = path;
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return;
|
||||
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, path, musicType: MusicType.Local).ConfigureAwait(false);
|
||||
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, path, musicType: MusicType.Local).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
@@ -491,7 +497,7 @@ namespace NadekoBot.Modules.Music
|
||||
public Task Remove(int num)
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return Task.CompletedTask;
|
||||
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
|
||||
return Task.CompletedTask;
|
||||
@@ -508,7 +514,8 @@ namespace NadekoBot.Modules.Music
|
||||
if (all.Trim().ToUpperInvariant() != "ALL")
|
||||
return;
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return;
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
musicPlayer.ClearQueue();
|
||||
await ReplyConfirmLocalized("queue_cleared").ConfigureAwait(false);
|
||||
}
|
||||
@@ -521,9 +528,9 @@ namespace NadekoBot.Modules.Music
|
||||
return;
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
|
||||
|
||||
fromto = fromto?.Trim();
|
||||
var fromtoArr = fromto.Split('>');
|
||||
|
||||
@@ -564,7 +571,7 @@ namespace NadekoBot.Modules.Music
|
||||
public async Task SetMaxQueue(uint size = 0)
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
|
||||
musicPlayer.MaxQueueSize = size;
|
||||
@@ -584,7 +591,7 @@ namespace NadekoBot.Modules.Music
|
||||
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
musicPlayer.MaxPlaytimeSeconds = seconds;
|
||||
if (seconds == 0)
|
||||
@@ -597,9 +604,8 @@ namespace NadekoBot.Modules.Music
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ReptCurSong()
|
||||
{
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
var currentSong = musicPlayer.CurrentSong;
|
||||
if (currentSong == null)
|
||||
@@ -621,9 +627,8 @@ namespace NadekoBot.Modules.Music
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RepeatPl()
|
||||
{
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
var currentValue = musicPlayer.ToggleRepeatPlaylist();
|
||||
if(currentValue)
|
||||
@@ -636,9 +641,8 @@ namespace NadekoBot.Modules.Music
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Save([Remainder] string name)
|
||||
{
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
|
||||
var curSong = musicPlayer.CurrentSong;
|
||||
@@ -694,7 +698,7 @@ namespace NadekoBot.Modules.Music
|
||||
var usr = (IGuildUser)Context.User;
|
||||
try
|
||||
{
|
||||
await QueueSong(usr, (ITextChannel)Context.Channel, usr.VoiceChannel, item.Query, true, item.ProviderType).ConfigureAwait(false);
|
||||
await music.QueueSong(usr, (ITextChannel)Context.Channel, usr.VoiceChannel, item.Query, true, item.ProviderType).ConfigureAwait(false);
|
||||
}
|
||||
catch (SongNotFoundException) { }
|
||||
catch { break; }
|
||||
@@ -764,7 +768,7 @@ namespace NadekoBot.Modules.Music
|
||||
public async Task Goto(int time)
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
|
||||
return;
|
||||
@@ -799,7 +803,7 @@ namespace NadekoBot.Modules.Music
|
||||
public async Task Autoplay()
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
return;
|
||||
|
||||
if (!musicPlayer.ToggleAutoplay())
|
||||
@@ -814,7 +818,7 @@ namespace NadekoBot.Modules.Music
|
||||
public async Task SetMusicChannel()
|
||||
{
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
|
||||
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
|
||||
{
|
||||
await ReplyErrorLocalized("no_player").ConfigureAwait(false);
|
||||
return;
|
||||
@@ -825,167 +829,5 @@ namespace NadekoBot.Modules.Music
|
||||
await ReplyConfirmLocalized("set_music_channel").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
|
||||
{
|
||||
if (voiceCh == null || voiceCh.Guild != textCh.Guild)
|
||||
{
|
||||
if (!silent)
|
||||
await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
|
||||
throw new ArgumentNullException(nameof(voiceCh));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
|
||||
throw new ArgumentException("Invalid song query.", nameof(query));
|
||||
|
||||
var musicPlayer = MusicPlayers.GetOrAdd(textCh.Guild.Id, server =>
|
||||
{
|
||||
float vol;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
vol = uow.GuildConfigs.For(textCh.Guild.Id, set => set).DefaultMusicVolume;
|
||||
}
|
||||
var mp = new MusicPlayer(voiceCh, textCh, vol);
|
||||
IUserMessage playingMessage = null;
|
||||
IUserMessage lastFinishedMessage = null;
|
||||
mp.OnCompleted += async (s, song) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
lastFinishedMessage?.DeleteAfter(0);
|
||||
|
||||
try
|
||||
{
|
||||
lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName(GetText("finished_song")).WithMusicIcon())
|
||||
.WithDescription(song.PrettyName)
|
||||
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal)
|
||||
{
|
||||
var relatedVideos = (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList();
|
||||
if(relatedVideos.Count > 0)
|
||||
await QueueSong(await queuer.Guild.GetCurrentUserAsync(),
|
||||
textCh,
|
||||
voiceCh,
|
||||
relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)],
|
||||
true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
};
|
||||
|
||||
mp.OnStarted += async (player, song) =>
|
||||
{
|
||||
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
var sender = player;
|
||||
if (sender == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
playingMessage?.DeleteAfter(0);
|
||||
|
||||
playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon())
|
||||
.WithDescription(song.PrettyName)
|
||||
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
};
|
||||
mp.OnPauseChanged += async (paused) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
IUserMessage msg;
|
||||
if (paused)
|
||||
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("paused")).ConfigureAwait(false);
|
||||
else
|
||||
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("resumed")).ConfigureAwait(false);
|
||||
|
||||
msg?.DeleteAfter(10);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
};
|
||||
|
||||
mp.SongRemoved += async (song, index) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon())
|
||||
.WithDescription(song.PrettyName)
|
||||
.WithFooter(ef => ef.WithText(song.PrettyInfo))
|
||||
.WithErrorColor();
|
||||
|
||||
await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
};
|
||||
return mp;
|
||||
});
|
||||
Song resolvedSong;
|
||||
try
|
||||
{
|
||||
musicPlayer.ThrowIfQueueFull();
|
||||
resolvedSong = await SongHandler.ResolveSong(query, musicType).ConfigureAwait(false);
|
||||
|
||||
if (resolvedSong == null)
|
||||
throw new SongNotFoundException();
|
||||
|
||||
musicPlayer.AddSong(resolvedSong, queuer.Username);
|
||||
}
|
||||
catch (PlaylistFullException)
|
||||
{
|
||||
try
|
||||
{
|
||||
await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
throw;
|
||||
}
|
||||
if (!silent)
|
||||
{
|
||||
try
|
||||
{
|
||||
//var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
|
||||
var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
|
||||
.WithDescription($"{resolvedSong.PrettyName}\n{GetText("queue")} ")
|
||||
.WithThumbnailUrl(resolvedSong.Thumbnail)
|
||||
.WithFooter(ef => ef.WithText(resolvedSong.PrettyProvider)))
|
||||
.ConfigureAwait(false);
|
||||
queuedMessage?.DeleteAfter(10);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
} // if queued message sending fails, don't attempt to delete it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -418,8 +418,8 @@ namespace NadekoBot.Modules.Utility
|
||||
NadekoBot.Client.Guilds.Count, stats.TextChannels, stats.VoiceChannels)).WithIsInline(true))
|
||||
#if !GLOBAL_NADEKO
|
||||
.WithFooter(efb => efb.WithText(GetText("stats_songs",
|
||||
Music.Music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
|
||||
Music.Music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
|
||||
NadekoBot.MusicService.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
|
||||
NadekoBot.MusicService.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user