using Discord; using Discord.Audio; using Discord.Commands; using Kasbot.Extensions; using System.Diagnostics; namespace Kasbot.Services { public class PlayerService { public Dictionary Clients { get; set; } public YoutubeService YoutubeService { get; set; } public PlayerService(YoutubeService youtubeService) { this.YoutubeService = youtubeService; Clients = new Dictionary(); } private async Task CreateConnection(SocketCommandContext Context) { IVoiceChannel channel = (Context.User as IVoiceState).VoiceChannel; var conn = new Connection(); IAudioClient audioClient = await channel.ConnectAsync(); audioClient.Disconnected += (ex) => Stop(Context.Guild.Id); conn.AudioClient = audioClient; if (Clients.ContainsKey(Context.Guild.Id)) Clients.Remove(Context.Guild.Id); Clients.Add(Context.Guild.Id, conn); return conn; } public async Task Play(SocketCommandContext Context, string arguments) { var media = new Media() { Message = Context.Message, Search = arguments, Name = "" }; if (Clients.TryGetValue(Context.Guild.Id, out var conn)) { await Enqueue(Context.Guild.Id, conn, media); return; } conn = await CreateConnection(Context); await Enqueue(Context.Guild.Id, conn, media); } private async Task Enqueue(ulong guildId, Connection conn, Media media) { var startPlay = conn.Queue.Count == 0; switch (YoutubeService.GetSearchType(media.Search)) { case SearchType.StringSearch: case SearchType.VideoURL: media = await YoutubeService.DownloadMetadataFromYoutube(media); if (media.VideoId == null) { await media.Channel.SendTemporaryMessageAsync($"No video found for \"{media.Search}\"."); return; } conn.Queue.Enqueue(media); if (startPlay) await PlayNext(guildId); else { var message = $"Queued **{media.Name}** *({media.Length.TotalMinutes:00}:{media.Length.Seconds:00})*"; media.QueueMessage = await media.Channel.SendMessageAsync(message); } break; case SearchType.ChannelURL: case SearchType.PlaylistURL: var collection = await YoutubeService.DownloadPlaylistMetadataFromYoutube(media.Message, media.Search); collection.Medias.ForEach(m => conn.Queue.Enqueue(m)); await media.Channel.SendMessageAsync($"Queued **{collection.Medias.Count}** items from *{collection.CollectionName}* playlist."); if (startPlay) await PlayNext(guildId); break; case SearchType.None: default: break; } } private async Task PlayNext(ulong guildId) { if (!Clients.ContainsKey(guildId) || Clients[guildId].Queue.Count == 0) { return; } var nextMedia = Clients[guildId].Queue.FirstOrDefault(); if (nextMedia == null) { await Stop(guildId); return; } var mp3Stream = await YoutubeService.DownloadAudioFromYoutube(nextMedia); if (mp3Stream == null) { await Stop(guildId); return; } var audioClient = Clients[guildId].AudioClient; var ffmpeg = CreateStream(); var message = $"⏯ Playing: **{nextMedia.Name}** *({nextMedia.Length.TotalMinutes:00}:{nextMedia.Length.Seconds:00})*"; nextMedia.PlayMessage = await nextMedia.Channel.SendMessageAsync(message); if (nextMedia.QueueMessage != null) { await nextMedia.QueueMessage.TryDeleteAsync(); } Task stdin = new Task(() => { using (var input = mp3Stream) { try { input.CopyTo(ffmpeg.StandardInput.BaseStream); ffmpeg.StandardInput.Close(); } catch { } finally { input.Flush(); input.Dispose(); } } }); Task stdout = new Task(() => { using (var output = ffmpeg.StandardOutput.BaseStream) using (var discord = audioClient.CreatePCMStream(AudioApplication.Music)) { try { Clients[guildId].CurrentAudioStream = output; output.CopyTo(discord); } catch { } finally { discord.Flush(); output.Close(); } } }); stdin.Start(); stdout.Start(); await stdin.ContinueWith(async ac => { if (ac.Exception != null) { await nextMedia.Channel.SendTemporaryMessageAsync("Error in input stream: " + ac.Exception.ToString()); } }); await stdout.ContinueWith(async ac => { if (ac.Exception!= null) { await nextMedia.Channel.SendTemporaryMessageAsync("Error while playing: " + ac.Exception.ToString()); } }); Task.WaitAll(stdin, stdout); ffmpeg.Close(); await nextMedia.PlayMessage.TryDeleteAsync(); if (Clients[guildId].Queue.Count > 0) Clients[guildId].Queue.Dequeue(); await PlayNext(guildId); } private Process CreateStream() { var process = Process.Start(new ProcessStartInfo { FileName = "ffmpeg", Arguments = $"-hide_banner -loglevel panic -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1", UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true }); if (process == null || process.HasExited) { throw new Exception("Sorry, ffmpeg killed itself in a tragic accident!"); } return process; } public Task Skip(ulong guildId) { if (!Clients.ContainsKey(guildId)) return Task.CompletedTask; var media = Clients[guildId]; if (media.CurrentAudioStream == null) return Task.CompletedTask; media.CurrentAudioStream.Close(); return Task.CompletedTask; } public async Task Stop(ulong guildId) { if (!Clients.ContainsKey(guildId)) return; var media = Clients[guildId]; foreach (var v in media.Queue.Skip(1)) { await v.PlayMessage.TryDeleteAsync(); } media.Queue.Clear(); if (media.CurrentAudioStream != null) media.CurrentAudioStream.Close(); } public async Task Leave(ulong guildId) { if (!Clients.ContainsKey(guildId)) return; await Stop(guildId); var media = Clients[guildId]; if (media.AudioClient != null) await media.AudioClient.StopAsync(); Clients.Remove(guildId); } } public class Connection { public IAudioClient AudioClient { get; set; } public Stream? CurrentAudioStream { get; set; } public Queue Queue { get; set; } = new Queue(); } }