mirror of
https://github.com/socatlolmeow/drift.git
synced 2026-11-03 19:27:19 +00:00
feat(player): stream audio by default, add [audio] config for cookies and downloads
Long tracks (15 min to 1 hour+) didnt playbecause every song was fully downloaded with yt-dlp into the cache before mpv was started. Resolve the direct media URL with yt-dlp -g and hand it to mpv, so playback starts within a couple of seconds. Cached files are still used when present. Add an [audio] section to config.toml: - download: download songs to the cache before playing (default false, i.e. stream). Prefetch of the next track only runs in this mode. - download_max_minutes: in download mode, songs longer than this (or of unknown length) are still streamed so 0 means no limit. default is le 15. - cookies_from_browser: passed to yt-dlp as --cookies-from-browser for playback, for when YouTube asks to sign in or blocks a song.
This commit is contained in:
parent
f25b96d5fd
commit
5237a6bae8
4 changed files with 200 additions and 14 deletions
|
|
@ -6,6 +6,8 @@ use std::time::{Duration, Instant};
|
|||
use anyhow::{anyhow, bail, Result};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::config::Audio;
|
||||
|
||||
/// Platform-specific transport for mpv's JSON IPC.
|
||||
/// Unix: a Unix domain socket. Windows: a named pipe (`\\.\pipe\...`).
|
||||
#[cfg(unix)]
|
||||
|
|
@ -311,6 +313,15 @@ impl Drop for Player {
|
|||
|
||||
const KEEP_FILES: usize = 40;
|
||||
|
||||
/// Base yt-dlp command with the user's cookie setting applied.
|
||||
fn ytdlp(audio: &Audio) -> Command {
|
||||
let mut cmd = Command::new("yt-dlp");
|
||||
if let Some(browser) = audio.cookies() {
|
||||
cmd.arg("--cookies-from-browser").arg(browser);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
fn audio_dir() -> PathBuf {
|
||||
dirs::cache_dir()
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
|
|
@ -326,13 +337,13 @@ pub fn cached_audio(id: &str) -> Option<PathBuf> {
|
|||
.find(|p| p.file_stem().and_then(|s| s.to_str()) == Some(id))
|
||||
}
|
||||
|
||||
pub fn fetch_audio(id: &str) -> Result<PathBuf, String> {
|
||||
pub fn fetch_audio(id: &str, audio: &Audio) -> Result<PathBuf, String> {
|
||||
if let Some(path) = cached_audio(id) {
|
||||
return Ok(path);
|
||||
}
|
||||
let dir = audio_dir();
|
||||
std::fs::create_dir_all(&dir).map_err(|e| format!("can't create {}: {e}", dir.display()))?;
|
||||
let mut cmd = Command::new("yt-dlp");
|
||||
let mut cmd = ytdlp(audio);
|
||||
cmd.args(["-f", "bestaudio/best", "--no-playlist", "--no-warnings", "--no-progress", "--no-mtime"])
|
||||
.arg("-P")
|
||||
.arg(&dir)
|
||||
|
|
@ -357,6 +368,35 @@ pub fn fetch_audio(id: &str) -> Result<PathBuf, String> {
|
|||
.unwrap_or_else(|| "yt-dlp downloaded nothing".into()))
|
||||
}
|
||||
|
||||
/// Ask yt-dlp for the direct media URL so mpv can start playing right away
|
||||
/// instead of waiting for the whole file to download.
|
||||
pub fn resolve_stream(id: &str, audio: &Audio) -> Result<String, String> {
|
||||
let mut cmd = ytdlp(audio);
|
||||
cmd.args(["-f", "bestaudio/best", "--no-playlist", "--no-warnings", "-g"])
|
||||
.arg(format!("https://music.youtube.com/watch?v={id}"))
|
||||
.stdin(Stdio::null());
|
||||
crate::sys::no_window(&mut cmd);
|
||||
let out = cmd
|
||||
.output()
|
||||
.map_err(|e| format!("could not run yt-dlp ({e}); is it installed and on PATH?"))?;
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
if let Some(url) = stdout
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.find(|l| l.starts_with("http"))
|
||||
.filter(|_| out.status.success())
|
||||
{
|
||||
return Ok(url.to_string());
|
||||
}
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
Err(stderr
|
||||
.lines()
|
||||
.rev()
|
||||
.find_map(|l| l.strip_prefix("ERROR: "))
|
||||
.map(ytdl_reason)
|
||||
.unwrap_or_else(|| "yt-dlp returned no stream URL".into()))
|
||||
}
|
||||
|
||||
fn prune(dir: &std::path::Path) {
|
||||
let Ok(entries) = std::fs::read_dir(dir) else {
|
||||
return;
|
||||
|
|
|
|||
Loading…
Reference in a new issue