public void PlayFile(string filePath) { var OutFormat = new WaveFormat(48000, 16, 1); // Create a new Output Format, using the spec that Discord will accept, and with the number of channels that our Client supports. AudioOutStream currentStream = AudioClient.CreatePCMStream(AudioApplication.Mixed); using (var MP3Reader = new Mp3FileReader(filePath)) // Create a new Disposable MP3FileReader, to read audio from the filePath parameter using (var resampler = new MediaFoundationResampler(MP3Reader, OutFormat)) // Create a Disposable Resampler, which will convert the read MP3 data to PCM, using our Output Format { resampler.ResamplerQuality = 60; // Set the quality of the resampler to 60, the highest quality int blockSize = OutFormat.AverageBytesPerSecond / 50; // Establish the size of our AudioBuffer byte[] buffer = new byte[blockSize]; byte[] silence = new byte[blockSize]; int byteCount = resampler.Read(buffer, 0, blockSize); while (byteCount > 0) // Read audio into our buffer, and keep a loop open while data is present { if (RequestStop) { RequestStop = false; FinishedSong(); return; } if (byteCount < blockSize) { // Incomplete Frame for (int i = byteCount; i < blockSize; i++) { buffer[i] = 0; } } for (int i = 0; i < buffer.Length; i += 2) { short sample = (short)(buffer[i] | (buffer[i + 1] << 8)); short result = (short)(sample * Volume); buffer[i] = (byte)(result & 0xFF); buffer[i + 1] = (byte)(result >> 8); } try { currentStream.WriteAsync(Paused ? silence : buffer, 0, blockSize).GetAwaiter(); //Send buffer to Discord } catch (Exception e) { Console.WriteLine(e.Message); AudioClient = null; CurrentState = AudioState.Stopped; break; } if (!Paused) { byteCount = resampler.Read(buffer, 0, blockSize); } } FinishedSong(); } }
private async Task StreamAudio(IGuild Guild, String Filename) { Console.WriteLine("Streaming"); IAudioClient AudioClient; await JukeBot.DiscordClient.SetGameAsync(Filename); if (this.ConnectedChannels.TryGetValue(Guild.Id, out AudioClient)) { var Output = this.CreateStream("4chan/" + Filename).StandardOutput.BaseStream; this.AudioData = new MemoryStream(); await Output.CopyToAsync(this.AudioData); await Output.FlushAsync(); Output.Dispose(); int read_length = 0; bool flipflop = false; int buffer_size = 2048; var buffer = new[] { new byte[buffer_size], new byte[buffer_size] }; this.AudioData.Seek(0x0, SeekOrigin.Begin); var DiscordStream = AudioClient.CreatePCMStream(AudioApplication.Music, 2880); Task writer; Task <int> reader; while (this.AudioData.Position < this.AudioData.Length) { if (!this.Pause) { writer = DiscordStream.WriteAsync(buffer[flipflop ? 0 : 1], 0, read_length); flipflop = !flipflop; reader = this.AudioData.ReadAsync(buffer[flipflop ? 0 : 1], 0, buffer_size); read_length = await reader; await writer; } else { await DiscordStream.WriteAsync(new byte[512], 0, 512); read_length = 0; } } await this.AudioData.FlushAsync(); //await Output.CopyToAsync(DiscordStream); await DiscordStream.FlushAsync(); await JukeBot.DiscordClient.SetGameAsync(""); } }
/// <summary> /// Creates a new stream when a user joins the voice channel /// repeats all their audio /// </summary> /// <param name="arg1"></param> /// <param name="arg2"></param> /// <returns></returns> private async Task StreamCreated(ulong arg1, AudioInStream arg2) { try { using (var stream = AudioClient.CreatePCMStream(AudioApplication.Mixed)) { if (Program.DEBUG) { Console.WriteLine(arg1); // User ID //await arg2.CopyToAsync(stream); } } } catch (Exception e) { Console.WriteLine(e); } }
/// <summary> /// Stream youtube audio data to discord voice channel. Works by youtube-dl piping video data to ffmpeg, /// which then extracts the audio and outputs it, which is then read by a stream, which is then forced into the user's ear /// </summary> /// <param name="url">Url of the video</param> /// <returns></returns> private async Task StreamAudio(string url, CancellationToken cancelToken) { Console.WriteLine("Youtube requested"); using (var stream = AudioClient.CreatePCMStream(application: AudioApplication.Mixed)) { try { if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) { #if DEBUG Console.WriteLine("Windows Detected"); #endif _process = Process.Start(new ProcessStartInfo { // 'Direct' method using only ffmpeg and a music link FileName = "Binaries\\ffmpeg", Arguments = $"-i \"{url}\" " + " -ac 2 -f s16le -ar 48000 pipe:1", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = false // 'indirect' method using both youtube-dl and ffmpeg /* * FileName = "cmd", * Arguments = $"/C youtube-dl.exe --hls-prefer-native -q -o - {url} | ffmpeg.exe -i - -f s16le -ar 48000 -ac 2 -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 10 pipe:1 -b:a 96K ", * UseShellExecute = false, * RedirectStandardOutput = true, * RedirectStandardError = false, */ }); } else { #if DEBUG Console.WriteLine("Linux Detected"); #endif _process = Process.Start(new ProcessStartInfo { /* * FileName = "/bin/bash", * Arguments = * $"-c \"ffmpeg -i \'{url}\' " + * " -ac 2 -f s16le -ar 48000 -loglevel panic pipe:1 \" ", * UseShellExecute = false, * RedirectStandardOutput = true, * RedirectStandardError = false */ FileName = "/bin/bash", Arguments = $"youtube-dl.exe --hls-prefer-native -q -o - {url} | ffmpeg.exe -i - -f s16le -ar 48000 -ac 2 -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 10 pipe:1 -b:a 96K", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = false, }); } Console.WriteLine("Starting process..."); int blockSize = 512; var buffer = new byte[blockSize]; int byteCount = 1; do { // Don't send any data or read from the stream if the stream is supposed to be paused if (Paused) { continue; } if (cancelToken.IsCancellationRequested || WillSkip) { break; } byteCount = await _process.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize); //buffer = AdjustVolume(buffer, Volume); await stream.WriteAsync(buffer, 0, blockSize); } while (byteCount > 0); if (!WillSkip) { _process.WaitForExit(); } _process.Close(); await stream.FlushAsync(); WillSkip = false; Paused = false; #if DEBUG Console.WriteLine("Process finished."); #endif } catch (OperationCanceledException) { Console.WriteLine("Cancelled by user."); _process.Close(); await stream.FlushAsync(); WillSkip = false; } catch (FileNotFoundException) { await _context.Channel.SendMessageAsync("Error, Youtube-dl and/or ffmpeg can not be found"); } catch (Exception e) { Console.WriteLine(e.InnerException); } } }
public async Task SendAudioAsync(IGuild Guild, string UserInput) { var YTC = new YoutubeClient(); if (UserInput.ToLower().Contains("youtube.com")) { UserInput = YoutubeClient.ParseVideoId(UserInput); } else { //var SearchList = await YTC.SearchAsync( UserInput ); HttpClient _httpClient = new HttpClient(); string EncodedSearchQuery = WebUtility.UrlEncode(UserInput); string Request = $"https://www.youtube.com/search_ajax?style=xml&search_query={EncodedSearchQuery}"; var Response = await _httpClient.GetStringAsync(Request).ConfigureAwait(false); var SearchResultsXml = XElement.Parse(Response).StripNamespaces(); var VideoIds = SearchResultsXml.Descendants("encrypted_id").Select(e => ( string )e); UserInput = VideoIds.First(); } var MediaInfo = await YTC.GetVideoMediaStreamInfosAsync(UserInput); var ASI = MediaInfo.Audio.OrderBy(x => x.Bitrate).Last(); var VideoInfo = await YTC.GetVideoAsync(UserInput); var Title = VideoInfo.Title; //VideoInfo.ToString(); ; var RGX = new Regex("[^a-zA-Z0-9 -]"); Title = RGX.Replace(Title, ""); var Name = $"{Title}.{ASI.AudioEncoding.ToString()}"; #if DEBUG var Path = "bin/Debug/netcoreapp1.1/Songs/"; #else String Path = "Songs/"; #endif if (!File.Exists(Path + Name)) { using (var Input = await YTC.GetMediaStreamAsync(ASI)) { Directory.CreateDirectory(Path); using (var Out = File.Create(Path + Name)) { await Input.CopyToAsync(Out); } } } IAudioClient AudioClient; await JukeBot.DiscordClient.SetGameAsync(Title); if (this.ConnectedChannels.TryGetValue(Guild.Id, out AudioClient)) { var Output = this.CreateStream(Path + Name).StandardOutput.BaseStream; this.AudioData = new MemoryStream(); await Output.CopyToAsync(this.AudioData); await Output.FlushAsync(); Output.Dispose(); int read_length = 0; bool flipflop = false; int buffer_size = 2048; var buffer = new[] { new byte[buffer_size], new byte[buffer_size] }; this.AudioData.Seek(0x0, SeekOrigin.Begin); var DiscordStream = AudioClient.CreatePCMStream(AudioApplication.Music, 2880); Task writer; Task <int> reader; while (this.AudioData.Position < this.AudioData.Length) { if (!this.Pause) { writer = DiscordStream.WriteAsync(buffer[flipflop ? 0 : 1], 0, read_length); flipflop = !flipflop; reader = this.AudioData.ReadAsync(buffer[flipflop ? 0 : 1], 0, buffer_size); read_length = await reader; await writer; } else { await DiscordStream.WriteAsync(new byte[512], 0, 512); read_length = 0; } } await this.AudioData.FlushAsync(); //await Output.CopyToAsync(DiscordStream); await DiscordStream.FlushAsync(); await JukeBot.DiscordClient.SetGameAsync(""); } }
public async void Play() { while (Playing) { bytesSent = 0; AudioOutStream pcm = null; Stream songStream = null; Process ffmpeg = null; CancellationToken cancelToken; lock (locker) { cancelToken = SongCancelSource.Token; } try { // The size of bytes to read per frame; 1920 for mono int blockSize = 3840; byte[] buffer = new byte[blockSize]; pcm = AudioClient.CreatePCMStream(AudioApplication.Music, bufferMillis: 500); int bytesRead = 0; ffmpeg = FFmpegProcess(CurrentSong().AudioURI); ffmpeg.Start(); songStream = ffmpeg.StandardOutput.BaseStream; //When for some reason the given song has no data, an error will be shown. if (songStream.Read(buffer, 0, 3840) == 0) { ShowSongNullEmbed(); error = true; throw new OperationCanceledException(); } ShowSongStartEmbed(); while ((bytesRead = songStream.Read(buffer, 0, 3840)) > 0) { await pcm.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); unchecked { bytesSent += bytesRead; } } } catch (OperationCanceledException) { Console.WriteLine("SONG CANCELED"); //Playing = false; } finally { if (!error && !Skipped) { ShowSongEndedEmbed(); } else { error = false; Skipped = false; } if (pcm != null) { var flushCancel = new CancellationTokenSource(); var flushToken = flushCancel.Token; var flushDelay = Task.Delay(1000, flushToken); await Task.WhenAny(flushDelay, pcm.FlushAsync(flushToken)); flushCancel.Cancel(); if (ffmpeg != null) { ffmpeg.Dispose(); } pcm.Dispose(); songStream.Dispose(); //If the song list is not over yet and there is another song to be played. if (NextSong()) { CurrentSongID++; } else { //If the song list is over, but the loop setting is on (makes the list start over when //it reaches its end) if (LoopList) { CurrentSongID = 0; } else { PlaybackEndEmbed(); Clear(); Destroy(); } } } } } }