feat: queue videos in running player

This commit is contained in:
2026-10-22 16:02:39 +02:00
parent b12bba9125
commit 585907f876
10 changed files with 193 additions and 73 deletions
+5
View File
@@ -120,6 +120,7 @@ actions! {
ViewTabPrevious, "view.tab.previous", "Select the previous view tab", VIEW, ["shift+tab"];
ViewRefresh, "view.refresh", "Refresh the current view and configuration", VIEW, ["r"];
VideoPlay, "video.play", "Play checked videos in displayed order, or the selected video", VIEW, ["enter"];
VideoEnqueue, "video.enqueue", "Append checked videos in displayed order, or the selected video, to Terebi's queue", VIEW, ["super+enter", "ctrl+enter"];
VideoOpen, "video.open", "Open the selected YouTube video in the default browser", VIEW, ["o"];
VideoCopyUrl, "video.copy_url", "Copy the selected video's URL to the terminal clipboard", VIEW, ["y"];
VideoNext, "video.select.next", "Select the next video", VIEW, ["j", "down", "ctrl+j"];
@@ -487,6 +488,10 @@ impl Bindings {
&[VideoPlay],
"Play checked videos in displayed order, or the selected video",
),
(
&[VideoEnqueue],
"Append checked videos, or the selected video, to Terebi's queue",
),
(
&[VideoToggle],
"Check / uncheck the selected video for playback",
+17 -5
View File
File diff suppressed because one or more lines are too long
+19 -3
View File
@@ -578,6 +578,11 @@ impl Browser {
self.push(Screen::new(Content::Series { media, data: None }));
self.load_series();
} else {
self.play(false);
}
}
fn play(&mut self, enqueue: bool) {
if self.screen().playable() {
let videos = self.screen().playback_videos();
if videos.is_empty() {
return;
@@ -586,15 +591,16 @@ impl Browser {
self.screen_mut().error = None;
// Dropping the launcher cancels only its CLI child, not the native player.
self.player = None;
let result = PlayerLaunch::start(&videos, &self.options.player, &mut self.state);
let result =
PlayerLaunch::start(&videos, &self.options.player, &mut self.state, enqueue);
match result {
Ok(player) => {
self.player = Some(player);
self.screen_mut().checked.clear();
self.status = if videos.len() == 1 {
format!("Opening {} in Terebi…", safe(&videos[0].title))
format!("Handing {} to Terebi…", safe(&videos[0].title))
} else {
format!("Opening {} episodes in Terebi…", videos.len())
format!("Handing {} episodes to Terebi…", videos.len())
};
}
Err(error) => self.fail(error),
@@ -1210,6 +1216,7 @@ impl Browser {
Some(Action::ViewNext) => self.history(true),
Some(Action::ViewRefresh) => self.refresh(),
Some(Action::VideoPlay) => self.open(),
Some(Action::VideoEnqueue) => self.play(true),
Some(Action::VideoToggle) => self.screen_mut().toggle_checked(),
Some(Action::VideoClear) => self.screen_mut().checked.clear(),
Some(Action::VideoCopyUrl) if self.screen().playable() => {
@@ -1375,6 +1382,10 @@ impl Browser {
Content::Series { .. } | Content::Collection { .. }
) {
bindings.extend([
(
Action::VideoEnqueue,
"Append checked or selected episodes to Terebi's queue",
),
(Action::VideoToggle, "Toggle checked episode"),
(Action::VideoClear, "Clear checked episodes"),
(Action::VideoCopyUrl, "Copy selected episode URL"),
@@ -1445,6 +1456,7 @@ impl Browser {
];
if self.screen().playable() {
actions.extend([
(Action::VideoEnqueue, "enqueue"),
(Action::VideoToggle, "check"),
(Action::VideoClear, "clear checks"),
]);
@@ -1498,6 +1510,7 @@ impl Browser {
&[
(Action::InfoDescription, "description"),
(Action::VideoPlay, "play"),
(Action::VideoEnqueue, "enqueue"),
(Action::VideoToggle, "check"),
(Action::VideoClear, "clear checks"),
(Action::InfoHide, "close"),
@@ -1510,6 +1523,7 @@ impl Browser {
&[
(Action::InfoOverview, "info"),
(Action::VideoPlay, "play"),
(Action::VideoEnqueue, "enqueue"),
(Action::VideoToggle, "check"),
(Action::VideoClear, "clear checks"),
(Action::InfoHide, "close"),
@@ -1528,6 +1542,7 @@ impl Browser {
(Action::Help, "help"),
(Action::ProviderChoose, "provider"),
(Action::VideoPlay, "play"),
(Action::VideoEnqueue, "enqueue"),
(Action::InfoOverview, "info"),
(Action::WatchStatusWatching, "mark watching"),
(Action::WatchStatusWatched, "mark watched"),
@@ -1553,6 +1568,7 @@ impl Browser {
"play"
},
),
(Action::VideoEnqueue, "enqueue"),
(Action::InfoOverview, "info"),
(Action::WatchStatusWatching, "mark watching"),
(Action::WatchStatusWatched, "mark watched"),
+9 -2
View File
@@ -929,6 +929,7 @@ impl PlayerLaunch {
videos: &[Video],
player: &OsStr,
state: &mut crate::video_state::State,
enqueue: bool,
) -> io::Result<Self> {
if videos.is_empty() {
return Err(io::Error::new(
@@ -937,8 +938,14 @@ impl PlayerLaunch {
));
}
let mut command = isolated_command(player);
command.arg("--new-instance");
if let [video] = videos {
command.arg(if enqueue {
"--enqueue"
} else {
"--new-instance"
});
if let [video] = videos
&& !enqueue
{
let url = video_url(&video.id)?;
let report = state.progress_file(video)?;
crate::video_state::register_identity(&report, &video.id)?;