about summary refs log tree commit diff stats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-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?;