feat: support multiple video selection
This commit is contained in:
+82
-14
@@ -1,8 +1,9 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
collections::{BTreeSet, VecDeque},
|
||||
ffi::OsStr,
|
||||
fs::{self, OpenOptions},
|
||||
io::{self, Read},
|
||||
path::Path,
|
||||
path::PathBuf,
|
||||
process::{Child, Command, Stdio},
|
||||
sync::mpsc::{self, Receiver, TryRecvError},
|
||||
thread,
|
||||
@@ -914,20 +915,87 @@ pub struct PlayerLaunch {
|
||||
}
|
||||
|
||||
impl PlayerLaunch {
|
||||
pub fn start(id: &str, player: &OsStr, progress_file: &Path) -> io::Result<Self> {
|
||||
let url = if valid_video_id(id) {
|
||||
format!("https://www.youtube.com/watch?v={id}")
|
||||
} else {
|
||||
tubular::miruro::Identity::from_key(id)
|
||||
.and_then(|identity| identity.watch_url())
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?
|
||||
pub fn start(
|
||||
videos: &[Video],
|
||||
player: &OsStr,
|
||||
state: &mut crate::video_state::State,
|
||||
) -> io::Result<Self> {
|
||||
if videos.is_empty() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"No videos selected",
|
||||
));
|
||||
}
|
||||
let playback_url = |id: &str| {
|
||||
if valid_video_id(id) {
|
||||
Ok(format!("https://www.youtube.com/watch?v={id}"))
|
||||
} else {
|
||||
tubular::miruro::Identity::from_key(id)
|
||||
.and_then(|identity| identity.watch_url())
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))
|
||||
}
|
||||
};
|
||||
crate::video_state::register_identity(progress_file, id)?;
|
||||
let mut command = isolated_command(player);
|
||||
command
|
||||
.args(["--new-instance", "--url", &url])
|
||||
.arg("--progress-file")
|
||||
.arg(progress_file);
|
||||
command.arg("--new-instance");
|
||||
if let [video] = videos {
|
||||
let url = playback_url(&video.id)?;
|
||||
let report = state.progress_file(video)?;
|
||||
crate::video_state::register_identity(&report, &video.id)?;
|
||||
command
|
||||
.args(["--url", &url])
|
||||
.arg("--progress-file")
|
||||
.arg(report);
|
||||
return Self::launch(command);
|
||||
}
|
||||
let mut ids = BTreeSet::new();
|
||||
let urls = videos
|
||||
.iter()
|
||||
.map(|video| {
|
||||
if !ids.insert(video.id.as_str()) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Duplicate queue identity",
|
||||
));
|
||||
}
|
||||
playback_url(&video.id)
|
||||
})
|
||||
.collect::<io::Result<Vec<_>>>()?;
|
||||
let reports = state.progress_files(videos)?;
|
||||
#[derive(Serialize)]
|
||||
struct QueueItem {
|
||||
url: String,
|
||||
progress_file: PathBuf,
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
struct Queue {
|
||||
version: u8,
|
||||
items: Vec<QueueItem>,
|
||||
}
|
||||
let items = videos
|
||||
.iter()
|
||||
.zip(urls)
|
||||
.zip(reports)
|
||||
.map(|((video, url), report)| {
|
||||
crate::video_state::register_identity(&report, &video.id)?;
|
||||
let parent = fs::canonicalize(report.parent().expect("session directory"))?;
|
||||
Ok(QueueItem {
|
||||
url,
|
||||
progress_file: parent.join("progress.jsonl"),
|
||||
})
|
||||
})
|
||||
.collect::<io::Result<Vec<_>>>()?;
|
||||
let manifest = items[0].progress_file.with_file_name("queue.json");
|
||||
let mut options = OpenOptions::new();
|
||||
options.write(true).create_new(true);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
options.mode(0o600);
|
||||
}
|
||||
let file = options.open(&manifest)?;
|
||||
serde_json::to_writer(&file, &Queue { version: 1, items })?;
|
||||
file.sync_all()?;
|
||||
command.arg("--queue-file").arg(manifest);
|
||||
Self::launch(command)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user