private void YoutubeDLMetadata(string path, MidoFile StreamData) { Process youtubedl; // Get Video Title ProcessStartInfo youtubedlMetaData = new ProcessStartInfo() { FileName = "youtube-dl", Arguments = $"-s -e --get-description --get-thumbnail {path}",// Add more flags for more options. CreateNoWindow = true, RedirectStandardOutput = true, UseShellExecute = false }; youtubedl = Process.Start(youtubedlMetaData); youtubedl.WaitForExit(); // Read the output of the simulation string[] output = youtubedl.StandardOutput.ReadToEnd().Split('\n'); // Set the file name. StreamData.FileName = path; // Extract each line printed for it's corresponding data. if (output.Length > 0) { StreamData.Title = output[0]; StreamData.Description = output[2]; StreamData.Thumbnail = output[1]; } }
// Starts the audioplayer playback for the specific song. // If something else is already playing, we stop it before putting this into the loop. public async Task Play(IAudioClient client, MidoFile song) { // Stop the current song. We wait until it's done to play the next song. if (m_IsRunning) { Stop(); } while (m_IsRunning) { await Task.Delay(1000).ConfigureAwait(false); } currentMidoFile = song; // Start playback. await AudioPlaybackAsync(client, currentMidoFile).ConfigureAwait(false); }
// Async function that handles the playback of the audio. This function is technically blocking in it's for loop. // It can be broken by cancelling m_Process or when it reads to the end of the file. // At the start, m_Process, m_Stream, amd m_IsPlaying is flushed. // While it is playing, these will hold values of the current playback audio. It will depend on m_Volume for the volume. // In the end, the three are flushed again. private async Task AudioPlaybackAsync(IAudioClient client, MidoFile song) { // Set running to true. m_IsRunning = true; // Start a new process and create an output stream. m_Process = CreateNetworkStream(song.FileName); m_Stream = client.CreatePCMStream(AudioApplication.Music); // Consider setting custom bitrate, buffers, and packet loss props. m_IsPlaying = true; // Set this to true to start the loop properly. await Task.Delay(2000).ConfigureAwait(false); // We should wait for ffmpeg to buffer some of the audio first. // We stream the audio in chunks. while (true) { // If the process is already over, we're finished. If something else kills this process, we stop. if (m_Process == null || m_Process.HasExited) { break; } // If the stream is broken, we exit. if (m_Stream == null) { break; } // We pause within this function while it's 'not playing'. if (!m_IsPlaying) { continue; } // Read the stream in chunks. int blockSize = m_BLOCK_SIZE; // Size of bytes to read per frame. byte[] buffer = new byte[blockSize]; int byteCount; byteCount = await m_Process.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize).ConfigureAwait(false); // If the stream cannot be read or we reach the end of the file, we exit. if (byteCount <= 0) { break; } try { // Write out to the stream. Relies on m_Volume to adjust bytes accordingly. await m_Stream.WriteAsync(ScaleVolumeSafeAllocateBuffers(buffer, m_Volume), 0, byteCount).ConfigureAwait(false); } catch (Exception exception) { Console.WriteLine(exception); throw; } } // Kill the process, if it's lingering. if (m_Process != null && !m_Process.HasExited) { m_Process.Kill(); } // Flush the stream and wait until it's fully done before continuing. if (m_Stream != null) { m_Stream.FlushAsync().Wait(); } // Reset values. Basically clearing out values (Flush). m_Process = null; m_Stream = null; m_IsPlaying = false; // Set running to false. m_IsRunning = false; }
// Extracts data from a network path or a search term and allocates it to an MidoFile. // // Filename - source by local filename or from network link. // Title - name of the song. // IsNetwork - If it's local or network. public async Task <MidoFile> GetAudioFileInfo(string path) { if (path == null) { return(null); } Console.WriteLine("Extracting Meta Data for : " + path); // Verify if it's a network path or not. bool?verifyURL = VerifyNetworkPath(path); if (verifyURL == null) { Console.WriteLine("Path invalid."); return(null); } // Construct audio file. MidoFile StreamData = new MidoFile(); // Search for the path on YouTube. if (verifyURL == false) { Process youtubedl; try { var youtubePath = "https://www.youtube.com/watch?v="; ProcessStartInfo youtubedlMetaData = new ProcessStartInfo() { FileName = "youtube-dl", Arguments = $" -s -e --get-id --get-description --get-thumbnail \"ytsearch:{path}\"", CreateNoWindow = true, RedirectStandardOutput = true, UseShellExecute = false }; youtubedl = Process.Start(youtubedlMetaData); youtubedl.WaitForExit(); string[] output = youtubedl.StandardOutput.ReadToEnd().Split('\n'); if (output.Length > 0) { StreamData.Title = output[0]; StreamData.FileName = youtubePath + output[1]; StreamData.Description = output[3]; StreamData.Thumbnail = output[2]; } } catch { throw new Exception("Failed to get media. ~(>_<。)\"); } } // Network file. else if (verifyURL == true) { // Figure out if its a direct path to a file bool directLink = Regex.IsMatch(path, "^.*\\.(wav|flac|alas|aac|m4a|webm|mp4|mp3|ogg)$"); try { if (directLink) { StreamData.FileName = path; StreamData.Title = path.Replace("?:[^/][\\d\\w\\.] +)$(?<=\\.\\w{ 3,4})", ""); StreamData.Description = $"A media file"; StreamData.Thumbnail = "https://www.shareicon.net/data/512x512/2016/07/26/802170_mp3_512x512.png"; // Can't be arsed } else { YoutubeDLMetadata(path, StreamData); } } catch { throw new Exception("Something inside of me went wrong... ~(>_<。)\"); } } await Task.Delay(0).ConfigureAwait(false); return(StreamData); }
// Adds a song to the queue for download. public void Push(MidoFile song) { m_DownloadQueue.Enqueue(song); } // Only add if there's no errors.