aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/yt/src/cli.rs10
-rw-r--r--crates/yt/src/main.rs7
-rw-r--r--crates/yt/src/watch/mod.rs89
-rw-r--r--crates/yt/src/watch/playlist.rs14
-rw-r--r--crates/yt/src/watch/playlist_handler/mod.rs135
5 files changed, 89 insertions, 166 deletions
diff --git a/crates/yt/src/cli.rs b/crates/yt/src/cli.rs
index 8892efd..dc200eb 100644
--- a/crates/yt/src/cli.rs
+++ b/crates/yt/src/cli.rs
@@ -98,7 +98,15 @@ pub(crate) enum Command {
},
/// Watch the already cached (and selected) videos
- Watch {},
+ Watch {
+ /// Print the path to an ipc socket for mpv control to stdout at startup.
+ #[arg(long)]
+ provide_ipc_socket: bool,
+
+ /// Don't start an mpv window at all.
+ #[arg(long)]
+ headless: bool,
+ },
/// Visualize the current playlist
Playlist {
diff --git a/crates/yt/src/main.rs b/crates/yt/src/main.rs
index 824f842..fcbe0da 100644
--- a/crates/yt/src/main.rs
+++ b/crates/yt/src/main.rs
@@ -273,7 +273,10 @@ async fn main() -> Result<()> {
}
},
- Command::Watch {} => watch::watch(Arc::new(app)).await?,
+ Command::Watch {
+ provide_ipc_socket,
+ headless,
+ } => watch::watch(Arc::new(app), provide_ipc_socket, headless).await?,
Command::Playlist { watch } => watch::playlist::playlist(&app, watch).await?,
Command::Status {} => status::show(&app).await?,
@@ -310,7 +313,7 @@ async fn dowa(arc_app: Arc<App>) -> Result<()> {
}
});
- watch::watch(arc_app).await?;
+ watch::watch(arc_app, false, false).await?;
download.await?;
Ok(())
}
diff --git a/crates/yt/src/watch/mod.rs b/crates/yt/src/watch/mod.rs
index 1936d48..8c7d6f8 100644
--- a/crates/yt/src/watch/mod.rs
+++ b/crates/yt/src/watch/mod.rs
@@ -10,47 +10,65 @@
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
use std::{
+ fs,
+ path::PathBuf,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
- time::Duration,
};
use anyhow::{Context, Result};
use libmpv2::{Mpv, events::EventContext};
use log::{debug, info, trace, warn};
-use playlist_handler::{reload_mpv_playlist, save_watch_progress};
-use tokio::{task, time::sleep};
+use tokio::{task, time};
use self::playlist_handler::Status;
use crate::{
app::App,
cache::maintain,
- storage::video_database::{get, notify::wait_for_db_write},
+ storage::{
+ db::{insert::Operations, playlist::Playlist},
+ notify::wait_for_db_write,
+ },
};
-pub mod playlist;
-pub mod playlist_handler;
+pub(crate) mod playlist;
+pub(crate) mod playlist_handler;
-fn init_mpv(app: &App) -> Result<(Mpv, EventContext)> {
+fn init_mpv(app: &App, ipc_socket: Option<PathBuf>, headless: bool) -> Result<(Mpv, EventContext)> {
// set some default values, to make things easier (these can be overridden by the config file,
// which we load later)
let mpv = Mpv::with_initializer(|mpv| {
- // Enable default key bindings, so the user can actually interact with
- // the player (and e.g. close the window).
- mpv.set_property("input-default-bindings", "yes")?;
- mpv.set_property("input-vo-keyboard", "yes")?;
+ if let Some(socket) = ipc_socket {
+ mpv.set_property(
+ "input-ipc-server",
+ socket
+ .to_str()
+ .expect("This path comes from us, it should never contain not-utf8"),
+ )?;
+ }
+
+ if headless {
+ // Do not provide video output.
+ mpv.set_property("vid", "no")?;
+ } else {
+ // Enable default key bindings, so the user can actually interact with
+ // the player (and e.g. close the window).
+ mpv.set_property("input-default-bindings", "yes")?;
+ mpv.set_property("input-vo-keyboard", "yes")?;
- // Show the on screen controller.
- mpv.set_property("osc", "yes")?;
+ // Show the on screen controller.
+ mpv.set_property("osc", "yes")?;
- // Don't automatically advance to the next video (or exit the player)
- mpv.set_option("keep-open", "always")?;
+ // Don't automatically advance to the next video (or exit the player)
+ mpv.set_option("keep-open", "always")?;
+
+ // Always display an window, even for non-video playback.
+ // As mpv does not have cli access, no window means no control and no user feedback.
+ mpv.set_option("force-window", "yes")?;
+ }
- // Always display an window, even for non-video playback.
- // As mpv does not have cli access, no window means no control and no user feedback.
- mpv.set_option("force-window", "yes")?;
Ok(())
})
.context("Failed to initialize mpv")?;
@@ -93,12 +111,23 @@ fn init_mpv(app: &App) -> Result<(Mpv, EventContext)> {
Ok((mpv, ev_ctx))
}
-pub(crate) async fn watch(app: Arc<App>) -> Result<()> {
- maintain(&app, false).await?;
+pub(crate) async fn watch(app: Arc<App>, provide_ipc_socket: bool, headless: bool) -> Result<()> {
+ maintain(&app).await?;
- let (mpv, mut ev_ctx) = init_mpv(&app).context("Failed to initialize mpv instance")?;
+ let ipc_socket = if provide_ipc_socket {
+ Some(app.config.paths.mpv_ipc_socket_path.clone())
+ } else {
+ None
+ };
+
+ let (mpv, mut ev_ctx) =
+ init_mpv(&app, ipc_socket, headless).context("Failed to initialize mpv instance")?;
let mpv = Arc::new(mpv);
- reload_mpv_playlist(&app, &mpv, None, None).await?;
+
+ // We now _know_ that the socket is set-up and ready.
+ if provide_ipc_socket {
+ println!("{}", app.config.paths.mpv_ipc_socket_path.display());
+ }
let should_break = Arc::new(AtomicBool::new(false));
@@ -173,10 +202,13 @@ pub(crate) async fn watch(app: Arc<App>) -> Result<()> {
}
}
- if let Some(ev) = ev_ctx.wait_event(30.) {
+ // TODO(@bpeetz): Is the following assumption correct? <2025-07-10>
+ // We wait until forever for the next event, because we really don't need to do anything
+ // else.
+ if let Some(ev) = ev_ctx.wait_event(f64::MAX) {
match ev {
Ok(event) => {
- trace!("Mpv event triggered: {:#?}", event);
+ trace!("Mpv event triggered: {event:#?}");
if playlist_handler::handle_mpv_event(&app, &mpv, &event)
.await
.with_context(|| format!("Failed to handle mpv event: '{event:#?}'"))?
@@ -192,5 +224,14 @@ pub(crate) async fn watch(app: Arc<App>) -> Result<()> {
should_break.store(true, Ordering::Relaxed);
progress_handle.await??;
+ if provide_ipc_socket {
+ fs::remove_file(&app.config.paths.mpv_ipc_socket_path).with_context(|| {
+ format!(
+ "Failed to clean-up the mpve ipc socket at {}",
+ app.config.paths.mpv_ipc_socket_path.display()
+ )
+ })?;
+ }
+
Ok(())
}
diff --git a/crates/yt/src/watch/playlist.rs b/crates/yt/src/watch/playlist.rs
index 0bd8f2e..7f1db2b 100644
--- a/crates/yt/src/watch/playlist.rs
+++ b/crates/yt/src/watch/playlist.rs
@@ -69,11 +69,17 @@ pub(crate) async fn playlist(app: &App, watch: bool) -> Result<()> {
if is_focused {
output.push_str(" (");
- output.push_str(&if let Some(duration) = video.duration.as_secs() {
- format!("{:0.0}%", (video.watch_progress.as_secs() / duration) * 100)
+ if let Some(duration) = video.duration.as_secs() {
+ let watch_progress: f64 = f64::from(
+ u32::try_from(video.watch_progress.as_secs()).expect("No overflow"),
+ );
+ let duration = f64::from(u32::try_from(duration).expect("No overflow"));
+
+ write!(output, "{:0.0}%", (watch_progress / duration) * 100.0)?;
} else {
- video.watch_progress_fmt(app)
- });
+ write!(output, "{}", video.watch_progress_fmt(app))?;
+ }
+
output.push(')');
}
output.push(']');
diff --git a/crates/yt/src/watch/playlist_handler/mod.rs b/crates/yt/src/watch/playlist_handler/mod.rs
index 71f277c..443fd26 100644
--- a/crates/yt/src/watch/playlist_handler/mod.rs
+++ b/crates/yt/src/watch/playlist_handler/mod.rs
@@ -47,141 +47,6 @@ fn mpv_message(mpv: &Mpv, message: &str, time: Duration) -> Result<()> {
Ok(())
}
-async fn apply_video_options(app: &App, mpv: &Mpv, video: &ExtractorHash) -> Result<()> {
- let options = get::video_mpv_opts(app, video).await?;
- let video = get::video_by_hash(app, video).await?;
-
- mpv.set_property("speed", options.playback_speed)?;
-
- // We already start at 0, so setting it twice adds a uncomfortable skip sound.
- if video.watch_progress.as_secs() != 0 {
- mpv.set_property(
- "time-pos",
- i64::try_from(video.watch_progress.as_secs()).expect("This should not overflow"),
- )?;
- }
- Ok(())
-}
-
-async fn mark_video_watched(app: &App, mpv: &Mpv) -> Result<()> {
- let current_video = get::currently_focused_video(app)
- .await?
- .expect("This should be some at this point");
-
- debug!(
- "playlist handler will mark video '{}' watched.",
- current_video.title
- );
-
- save_watch_progress(app, mpv).await?;
-
- set::video_watched(app, &current_video.extractor_hash).await?;
-
- Ok(())
-}
-
-/// Saves the `watch_progress` of the currently focused video.
-pub(super) async fn save_watch_progress(app: &App, mpv: &Mpv) -> Result<()> {
- let current_video = get::currently_focused_video(app)
- .await?
- .expect("This should be some at this point");
- let watch_progress = u32::try_from(
- mpv.get_property::<i64>("time-pos")
- .context("Failed to get the watchprogress of the currently playling video")?,
- )
- .expect("This conversion should never fail as the `time-pos` property is positive");
-
- debug!(
- "Setting the watch progress for the current_video '{}' to {watch_progress}s",
- current_video.title_fmt_no_color()
- );
-
- set::video_watch_progress(app, &current_video.extractor_hash, watch_progress).await
-}
-
-/// Sync the mpv playlist with the internal playlist.
-///
-/// This takes an `maybe_playlist` argument, if you have already fetched the playlist and want to
-/// add that.
-pub(super) async fn reload_mpv_playlist(
- app: &App,
- mpv: &Mpv,
- maybe_playlist: Option<Playlist>,
- maybe_index: Option<PlaylistIndex>,
-) -> Result<()> {
- fn get_playlist_count(mpv: &Mpv) -> Result<usize> {
- mpv.get_property::<i64>("playlist/count")
- .context("Failed to get mpv playlist len")
- .map(|count| {
- usize::try_from(count).expect("The playlist_count should always be positive")
- })
- }
-
- if get_playlist_count(mpv)? != 0 {
- // We could also use `loadlist`, but that would require use to start a unix socket or even
- // write all the video paths to a file beforehand
- mpv.command("playlist-clear", &[])?;
- mpv.command("playlist-remove", &["current"])?;
- }
-
- assert_eq!(
- get_playlist_count(mpv)?,
- 0,
- "The playlist should be empty at this point."
- );
-
- let playlist = if let Some(p) = maybe_playlist {
- p
- } else {
- get::playlist(app).await?
- };
-
- debug!("Will add {} videos to playlist.", playlist.len());
- playlist.into_iter().try_for_each(|cache_path| {
- mpv.command(
- "loadfile",
- &[
- cache_path.to_str().with_context(|| {
- format!(
- "Failed to parse the video cache path ('{}') as valid utf8",
- cache_path.display()
- )
- })?,
- "append-play",
- ],
- )?;
-
- Ok::<(), anyhow::Error>(())
- })?;
-
- let index = if let Some(index) = maybe_index {
- let index = usize::from(index);
- let playlist_length = get_playlist_count(mpv)?;
-
- match index.cmp(&playlist_length) {
- Ordering::Greater => {
- unreachable!(
- "The index '{index}' execeeds the playlist length '{playlist_length}'."
- );
- }
- Ordering::Less => index,
- Ordering::Equal => {
- // The index is pointing to the end of the playlist. We could either go the second
- // to last entry (i.e., one entry back) or wrap around to the start.
- // We wrap around:
- 0
- }
- }
- } else {
- get::current_playlist_index(app)
- .await?
- .map_or(0, usize::from)
- };
- mpv.set_property("playlist-pos", index.to_string().as_str())?;
-
- Ok(())
-}
-
/// Return the status of the playback queue
pub(crate) async fn status(app: &App) -> Result<Status> {
let playlist = Playlist::create(app).await?;