Lot more work, fixes, addition, untested new implementations...

This commit is contained in:
Master Kwoth 2017-07-01 21:22:11 +02:00
parent 9889baf8bd
commit 3731994061
9 changed files with 229 additions and 111 deletions

View File

@ -48,7 +48,7 @@ namespace NadekoBot.DataStructures
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
public PoopyRingBuffer(int capacity = 38400) public PoopyRingBuffer(int capacity = 3640 * 200)
{ {
this.Capacity = capacity + 1; this.Capacity = capacity + 1;
this.buffer = new byte[this.Capacity]; this.buffer = new byte[this.Capacity];

View File

@ -111,11 +111,15 @@ namespace NadekoBot.Modules.Music
} }
} }
} }
//todo add play command. .play = .n, .play whatever = .q whatever
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Queue([Remainder] string query) public async Task Queue([Remainder] string query)
{ {
//todo add a notice that player is stopped if user queues a song while it is
var mp = await _music.GetOrCreatePlayer(Context); var mp = await _music.GetOrCreatePlayer(Context);
var songInfo = await _music.ResolveSong(query, Context.User.ToString()); var songInfo = await _music.ResolveSong(query, Context.User.ToString());
await InternalQueue(mp, songInfo, false); await InternalQueue(mp, songInfo, false);
@ -209,11 +213,9 @@ namespace NadekoBot.Modules.Music
if (mp.RepeatCurrentSong) if (mp.RepeatCurrentSong)
desc = "🔂 " + GetText("repeating_cur_song") + "\n\n" + desc; desc = "🔂 " + GetText("repeating_cur_song") + "\n\n" + desc;
//else if (musicPlayer.RepeatPlaylist) else if (mp.Shuffle)
// desc = "🔁 " + GetText("repeating_playlist") + "\n\n" + desc; desc = "🔀 " + GetText("shuffling_playlist") + "\n\n" + desc;
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName(GetText("player_queue", curPage + 1, lastPage + 1)) .WithAuthor(eab => eab.WithName(GetText("player_queue", curPage + 1, lastPage + 1))
.WithMusicIcon()) .WithMusicIcon())
@ -307,7 +309,7 @@ namespace NadekoBot.Modules.Music
{ {
var song = mp.RemoveAt(index - 1); var song = mp.RemoveAt(index - 1);
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon()) .WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index)).WithMusicIcon())
.WithDescription(song.PrettyName) .WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo)) .WithFooter(ef => ef.WithText(song.PrettyInfo))
.WithErrorColor(); .WithErrorColor();
@ -519,22 +521,18 @@ namespace NadekoBot.Modules.Music
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
//todo test shuffle
//[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
//public async Task ShufflePlaylist() public async Task ShufflePlaylist()
//{ {
// MusicPlayer musicPlayer; var mp = await _music.GetOrCreatePlayer(Context);
// if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) var val = mp.ToggleShuffle();
// return; if(val)
// if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel) await ReplyConfirmLocalized("songs_shuffle_enable").ConfigureAwait(false);
// return; else
// if (musicPlayer.Playlist.Count < 2) await ReplyConfirmLocalized("songs_shuffle_disable").ConfigureAwait(false);
// return; }
// musicPlayer.Shuffle();
// await ReplyConfirmLocalized("songs_shuffled").ConfigureAwait(false);
//}
//[NadekoCommand, Usage, Description, Aliases] //[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] //[RequireContext(ContextType.Guild)]
@ -687,17 +685,22 @@ namespace NadekoBot.Modules.Music
//} //}
//[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
//public async Task Move() public async Task Move()
//{ {
var vch = ((IGuildUser)Context.User).VoiceChannel;
// MusicPlayer musicPlayer; if (vch == null)
// var voiceChannel = ((IGuildUser)Context.User).VoiceChannel; return;
// if (voiceChannel == null || voiceChannel.Guild != Context.Guild || !MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
// return; var mp = _music.GetPlayerOrDefault(Context.Guild.Id);
// await musicPlayer.MoveToVoiceChannel(voiceChannel);
//} if (mp == null)
return;
//todo test move
mp.SetVoiceChannel(vch);
}
//[NadekoCommand, Usage, Description, Aliases] //[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] //[RequireContext(ContextType.Guild)]
@ -745,21 +748,21 @@ namespace NadekoBot.Modules.Music
//} //}
//[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
//public async Task SetMaxQueue(uint size = 0) public async Task SetMaxQueue(uint size = 0)
//{ {
// MusicPlayer musicPlayer; if (size < 0)
// if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) return;
// return; var mp = await _music.GetOrCreatePlayer(Context);
// musicPlayer.MaxQueueSize = size; mp.SetMaxQueueSize(size);
// if(size == 0) if (size == 0)
// await ReplyConfirmLocalized("max_queue_unlimited").ConfigureAwait(false); await ReplyConfirmLocalized("max_queue_unlimited").ConfigureAwait(false);
// else else
// await ReplyConfirmLocalized("max_queue_x", size).ConfigureAwait(false); await ReplyConfirmLocalized("max_queue_x", size).ConfigureAwait(false);
//} }
//[NadekoCommand, Usage, Description, Aliases] //[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] //[RequireContext(ContextType.Guild)]
@ -800,19 +803,17 @@ namespace NadekoBot.Modules.Music
.ConfigureAwait(false); .ConfigureAwait(false);
} }
//[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
//public async Task RepeatPl() public async Task RepeatPl()
//{ {
// MusicPlayer musicPlayer; var mp = await _music.GetOrCreatePlayer(Context);
// if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null) var currentValue = mp.ToggleRepeatPlaylist();
// return; if (currentValue)
// var currentValue = musicPlayer.ToggleRepeatPlaylist(); await ReplyConfirmLocalized("rpl_enabled").ConfigureAwait(false);
// if(currentValue) else
// await ReplyConfirmLocalized("rpl_enabled").ConfigureAwait(false); await ReplyConfirmLocalized("rpl_disabled").ConfigureAwait(false);
// else }
// await ReplyConfirmLocalized("rpl_disabled").ConfigureAwait(false);
//}
//[NadekoCommand, Usage, Description, Aliases] //[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] //[RequireContext(ContextType.Guild)]
@ -849,19 +850,17 @@ namespace NadekoBot.Modules.Music
// await ReplyConfirmLocalized("skipped_to", minutes, seconds).ConfigureAwait(false); // await ReplyConfirmLocalized("skipped_to", minutes, seconds).ConfigureAwait(false);
//} //}
//[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
//public async Task Autoplay() public async Task Autoplay()
//{ {
// MusicPlayer musicPlayer; var mp = await _music.GetOrCreatePlayer(Context);
// if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
// return;
// if (!musicPlayer.ToggleAutoplay()) if (!mp.ToggleAutoplay())
// await ReplyConfirmLocalized("autoplay_disabled").ConfigureAwait(false); await ReplyConfirmLocalized("autoplay_disabled").ConfigureAwait(false);
// else else
// await ReplyConfirmLocalized("autoplay_enabled").ConfigureAwait(false); await ReplyConfirmLocalized("autoplay_enabled").ConfigureAwait(false);
//} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]

View File

@ -183,7 +183,7 @@ namespace NadekoBot
#endregion #endregion
var clashService = new ClashOfClansService(Client, Db, Localization, Strings, uow, startingGuildIdList); var clashService = new ClashOfClansService(Client, Db, Localization, Strings, uow, startingGuildIdList);
var musicService = new MusicService(GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs); var musicService = new MusicService(Client, GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs);
var crService = new CustomReactionsService(permissionsService, Db, Strings, Client, CommandHandler, BotConfig, uow); var crService = new CustomReactionsService(permissionsService, Db, Strings, Client, CommandHandler, BotConfig, uow);
#region Games #region Games

View File

@ -18,7 +18,7 @@ namespace NadekoBot.Services.Music
public class MusicPlayer public class MusicPlayer
{ {
private readonly Task _player; private readonly Task _player;
private readonly IVoiceChannel VoiceChannel; private IVoiceChannel VoiceChannel { get; set; }
private readonly Logger _log; private readonly Logger _log;
private MusicQueue Queue { get; } = new MusicQueue(); private MusicQueue Queue { get; } = new MusicQueue();
@ -42,9 +42,13 @@ namespace NadekoBot.Services.Music
} }
public bool RepeatCurrentSong { get; private set; } public bool RepeatCurrentSong { get; private set; }
public bool Shuffle { get; private set; }
public bool Autoplay { get; private set; }
public bool RepeatPlaylist { get; private set; } = true;
private IAudioClient _audioClient; private IAudioClient _audioClient;
private readonly object locker = new object(); private readonly object locker = new object();
private MusicService _musicService;
#region events #region events
public event Action<MusicPlayer, SongInfo> OnStarted; public event Action<MusicPlayer, SongInfo> OnStarted;
@ -59,6 +63,7 @@ namespace NadekoBot.Services.Music
this.VoiceChannel = vch; this.VoiceChannel = vch;
this.SongCancelSource = new CancellationTokenSource(); this.SongCancelSource = new CancellationTokenSource();
this.OutputTextChannel = output; this.OutputTextChannel = output;
this._musicService = musicService;
_player = Task.Run(async () => _player = Task.Run(async () =>
{ {
@ -96,42 +101,43 @@ namespace NadekoBot.Services.Music
// i don't want to spam connection attempts // i don't want to spam connection attempts
continue; continue;
} }
var pcm = ac.CreatePCMStream(AudioApplication.Music); using (var pcm = ac.CreatePCMStream(AudioApplication.Music))
OnStarted?.Invoke(this, data.Song);
byte[] buffer = new byte[3840];
int bytesRead = 0;
try
{ {
while ((bytesRead = await b.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) > 0) OnStarted?.Invoke(this, data.Song);
byte[] buffer = new byte[3840];
int bytesRead = 0;
try
{ {
var vol = Volume; while ((bytesRead = await b.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) > 0)
if (vol != 1) {
AdjustVolume(buffer, vol); var vol = Volume;
await Task.WhenAll(Task.Delay(10), pcm.WriteAsync(buffer, 0, bytesRead, cancelToken)).ConfigureAwait(false); if (vol != 1)
AdjustVolume(buffer, vol);
await Task.WhenAll(Task.Delay(10), pcm.WriteAsync(buffer, 0, bytesRead, cancelToken)).ConfigureAwait(false);
await (pauseTaskSource?.Task ?? Task.CompletedTask); await (pauseTaskSource?.Task ?? Task.CompletedTask);
}
} }
} catch (OperationCanceledException)
catch (OperationCanceledException) {
{ _log.Info("Song Canceled");
_log.Info("Song Canceled"); }
} catch (Exception ex)
catch (Exception ex) {
{ _log.Warn(ex);
_log.Warn(ex); }
} finally
finally {
{ //flush is known to get stuck from time to time, just cancel it if it takes more than 1 second
//flush is known to get stuck from time to time, just cancel it if it takes more than 1 second var flushCancel = new CancellationTokenSource();
var flushCancel = new CancellationTokenSource(); var flushToken = flushCancel.Token;
var flushToken = flushCancel.Token; var flushDelay = Task.Delay(1000, flushToken);
var flushDelay = Task.Delay(1000, flushToken); await Task.WhenAny(flushDelay, pcm.FlushAsync(flushToken));
await Task.WhenAny(flushDelay, pcm.FlushAsync(flushToken)); flushCancel.Cancel();
flushCancel.Cancel();
OnCompleted?.Invoke(this, data.Song); OnCompleted?.Invoke(this, data.Song);
}
} }
} }
} }
@ -143,8 +149,35 @@ namespace NadekoBot.Services.Music
await Task.Delay(500); await Task.Delay(500);
} }
while (Stopped && !Exited); while (Stopped && !Exited);
if(!RepeatCurrentSong) if (!RepeatCurrentSong) //if repeating current song, just ignore other settings, and play this song again (don't change the index)
Queue.Next(); {
if (Shuffle)
Queue.Random(); //if shuffle is set, set current song index to a random number
else
{
//if last song, and autoplay is enabled, and if it's a youtube song
// do autplay magix
if (Queue.Count == data.Index && Autoplay && data.Song?.Provider == "YouTube")
{
try
{
//todo test autoplay
await _musicService.TryQueueRelatedSongAsync(data.Song.Query, OutputTextChannel, VoiceChannel);
Queue.Next();
}
catch { }
}
else if (Queue.Count == data.Index && !RepeatPlaylist)
{
//todo test repeatplaylist
Stop();
}
else
{
Queue.Next();
}
}
}
} }
} }
}, SongCancelSource.Token); }, SongCancelSource.Token);
@ -313,6 +346,41 @@ namespace NadekoBot.Services.Music
await ac.StopAsync(); await ac.StopAsync();
} }
public bool ToggleShuffle()
{
lock (locker)
{
return Shuffle = !Shuffle;
}
}
public bool ToggleAutoplay()
{
lock (locker)
{
return Autoplay = !Autoplay;
}
}
public bool ToggleRepeatPlaylist()
{
lock (locker)
{
return RepeatPlaylist = !RepeatPlaylist;
}
}
public void SetMaxQueueSize(uint size)
{
Queue.SetMaxQueueSize(size);
}
public void SetVoiceChannel(IVoiceChannel vch)
{
VoiceChannel = vch;
Next();
}
//private IAudioClient AudioClient { get; set; } //private IAudioClient AudioClient { get; set; }

View File

@ -1,4 +1,5 @@
using System; using NadekoBot.Extensions;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -49,17 +50,23 @@ namespace NadekoBot.Services.Music
} }
} }
public uint maxQueueSize { get; private set; }
public void Add(SongInfo song) public void Add(SongInfo song)
{ {
song.ThrowIfNull(nameof(song));
lock (locker) lock (locker)
{ {
if(CurrentIndex >= maxQueueSize)
throw new PlaylistFullException();
Songs.AddLast(song); Songs.AddLast(song);
} }
} }
public void Next() public void Next()
{ {
CurrentIndex++; lock(locker)
CurrentIndex++;
} }
public void Dispose() public void Dispose()
@ -118,5 +125,24 @@ namespace NadekoBot.Services.Music
CurrentIndex = 0; CurrentIndex = 0;
} }
} }
public void Random()
{
lock (locker)
{
CurrentIndex = new NadekoRandom().Next(Songs.Count);
}
}
public void SetMaxQueueSize(uint size)
{
if (size < 0)
throw new ArgumentOutOfRangeException(nameof(size));
lock (locker)
{
maxQueueSize = size;
}
}
} }
} }

View File

@ -10,6 +10,7 @@ using System.IO;
using VideoLibrary; using VideoLibrary;
using System.Collections.Generic; using System.Collections.Generic;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
namespace NadekoBot.Services.Music namespace NadekoBot.Services.Music
{ {
@ -25,13 +26,15 @@ namespace NadekoBot.Services.Music
private readonly SoundCloudApiService _sc; private readonly SoundCloudApiService _sc;
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly ConcurrentDictionary<ulong, float> _defaultVolumes; private readonly ConcurrentDictionary<ulong, float> _defaultVolumes;
private readonly DiscordSocketClient _client;
public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>(); public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
public MusicService(IGoogleApiService google, public MusicService(DiscordSocketClient client, IGoogleApiService google,
NadekoStrings strings, ILocalization localization, DbService db, NadekoStrings strings, ILocalization localization, DbService db,
SoundCloudApiService sc, IBotCredentials creds, IEnumerable<GuildConfig> gcs) SoundCloudApiService sc, IBotCredentials creds, IEnumerable<GuildConfig> gcs)
{ {
_client = client;
_google = google; _google = google;
_strings = strings; _strings = strings;
_localization = localization; _localization = localization;
@ -187,6 +190,25 @@ namespace NadekoBot.Services.Music
}); });
} }
public MusicPlayer GetPlayerOrDefault(ulong guildId)
{
if (MusicPlayers.TryGetValue(guildId, out var mp))
return mp;
else
return null;
}
public async Task TryQueueRelatedSongAsync(string query, ITextChannel txtCh, IVoiceChannel vch)
{
var related = (await _google.GetRelatedVideosAsync(query, 4)).ToArray();
if (!related.Any())
return;
var si = await ResolveSong(related[new NadekoRandom().Next(related.Length)], _client.CurrentUser.ToString(), MusicType.Normal);
var mp = await GetOrCreatePlayer(txtCh.GuildId, vch, txtCh);
mp.Enqueue(si);
}
public async Task<SongInfo> ResolveSong(string query, string queuerName, MusicType musicType = MusicType.Normal) public async Task<SongInfo> ResolveSong(string query, string queuerName, MusicType musicType = MusicType.Normal)
{ {
query.ThrowIfNull(nameof(query)); query.ThrowIfNull(nameof(query));

View File

@ -24,7 +24,7 @@ namespace NadekoBot.Services.Music
this.p = Process.Start(new ProcessStartInfo this.p = Process.Start(new ProcessStartInfo
{ {
FileName = "ffmpeg", FileName = "ffmpeg",
Arguments = $"-i {songUri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet", Arguments = $"-i {songUri} -f s16le -ar 48000 -vn -b:a 128k -ac 2 pipe:1 -loglevel quiet -nostdin",
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = false, RedirectStandardError = false,

View File

@ -4,6 +4,7 @@ using NadekoBot.Services.Database.Models;
using System; using System;
using System.Net; using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NadekoBot.Services.Music namespace NadekoBot.Services.Music
{ {

View File

@ -442,12 +442,14 @@
"music_repeating_playlist": "Repeating playlist", "music_repeating_playlist": "Repeating playlist",
"music_repeating_track": "Repeating track", "music_repeating_track": "Repeating track",
"music_repeating_track_stopped": "Current track repeat stopped.", "music_repeating_track_stopped": "Current track repeat stopped.",
"music_shuffling_playlist": "Shuffling songs",
"music_resumed": "Music playback resumed.", "music_resumed": "Music playback resumed.",
"music_rpl_disabled": "Repeat playlist disabled.", "music_rpl_disabled": "Repeat playlist disabled.",
"music_rpl_enabled": "Repeat playlist enabled.", "music_rpl_enabled": "Repeat playlist enabled.",
"music_set_music_channel": "I will now output playing, finished, paused and removed songs in this channel.", "music_set_music_channel": "I will now output playing, finished, paused and removed songs in this channel.",
"music_skipped_to": "Skipped to `{0}:{1}`", "music_skipped_to": "Skipped to `{0}:{1}`",
"music_songs_shuffled": "Songs shuffled", "music_songs_shuffle_enable": "Songs will shuffle from now on.",
"music_songs_shuffle_disable": "Songs will no longer shuffle.",
"music_song_moved": "Song moved", "music_song_moved": "Song moved",
"music_song_not_found": "No song found.", "music_song_not_found": "No song found.",
"music_time_format": "{0}h {1}m {2}s", "music_time_format": "{0}h {1}m {2}s",