diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-21 10:49:23 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-21 11:28:43 +0200 |
commit | 1debeb77f7986de1b659dcfdc442de6415e1d9f5 (patch) | |
tree | 4df3e7c3f6a2d1ec116e4088c5ace7f143a8b05f /src/watch/events.rs | |
download | yt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.tar.gz yt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.zip |
chore: Initial Commit
This repository was migrated out of my nixos-config.
Diffstat (limited to 'src/watch/events.rs')
-rw-r--r-- | src/watch/events.rs | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/src/watch/events.rs b/src/watch/events.rs new file mode 100644 index 0000000..815ad28 --- /dev/null +++ b/src/watch/events.rs @@ -0,0 +1,235 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use std::{env::current_exe, mem, usize}; + +use anyhow::{bail, Result}; +use libmpv2::{events::Event, EndFileReason, Mpv}; +use log::{debug, info}; +use tokio::process::Command; + +use crate::{ + app::App, + comments::get_comments, + constants::LOCAL_COMMENTS_LENGTH, + storage::video_database::{ + extractor_hash::ExtractorHash, + getters::{get_video_by_hash, get_video_mpv_opts, get_videos}, + setters::{set_state_change, set_video_watched}, + VideoStatus, + }, +}; + +pub struct MpvEventHandler { + currently_playing_index: Option<usize>, + current_playlist_position: usize, + current_playlist: Vec<ExtractorHash>, +} + +impl MpvEventHandler { + pub fn from_playlist(playlist: Vec<ExtractorHash>) -> Self { + Self { + currently_playing_index: None, + current_playlist: playlist, + current_playlist_position: 0, + } + } + + /// Checks, whether new videos are ready to be played + pub async fn possibly_add_new_videos(&mut self, app: &App, mpv: &Mpv) -> Result<()> { + let play_things = get_videos(app, &[VideoStatus::Cached], Some(false)).await?; + + // There is nothing to watch + if play_things.len() == 0 { + return Ok(()); + } + + let play_things = play_things + .into_iter() + .filter(|val| !self.current_playlist.contains(&val.extractor_hash)) + .collect::<Vec<_>>(); + + info!( + "{} videos are cached and will be added to the list to be played", + play_things.len() + ); + + self.current_playlist.reserve(play_things.len()); + + for play_thing in play_things { + debug!("Adding '{}' to playlist.", play_thing.title); + + let orig_cache_path = play_thing.cache_path.expect("Is cached and thus some"); + let cache_path = orig_cache_path.to_str().expect("Should be vaild utf8"); + let cache_path = format!("\"{}\"", cache_path); + + let args = &[&cache_path, "append-play"]; + + mpv.execute("loadfile", args)?; + self.current_playlist.push(play_thing.extractor_hash); + } + + Ok(()) + } + + async fn mark_video_watched(&mut self, app: &App, hash: &ExtractorHash) -> Result<()> { + let video = get_video_by_hash(app, hash).await?; + set_video_watched(&app, &video).await?; + Ok(()) + } + async fn mark_cvideo_watched(&mut self, app: &App) -> Result<()> { + if let Some(index) = self.currently_playing_index { + let video_hash = self.current_playlist[(index) as usize].clone(); + self.mark_video_watched(app, &video_hash).await?; + } + Ok(()) + } + + async fn mark_cvideo_inactive(&mut self, app: &App) -> Result<()> { + if let Some(index) = self.currently_playing_index { + let video_hash = &self.current_playlist[(index) as usize]; + self.currently_playing_index = None; + set_state_change(&app, video_hash, false).await?; + } + Ok(()) + } + async fn mark_video_active(&mut self, app: &App, playlist_index: usize) -> Result<()> { + let video_hash = &self.current_playlist[(playlist_index) as usize]; + self.currently_playing_index = Some(playlist_index); + set_state_change(&app, video_hash, true).await?; + Ok(()) + } + + /// Apply the options set with e.g. `watch --speed=<speed>` + async fn apply_options(&self, app: &App, mpv: &Mpv, hash: &ExtractorHash) -> Result<()> { + let options = get_video_mpv_opts(app, hash).await?; + + mpv.set_property("speed", options.playback_speed)?; + + Ok(()) + } + + /// This will return [`true`], if the event handling should be stopped + pub async fn handle_mpv_event<'a>( + &mut self, + app: &App, + mpv: &Mpv, + event: Event<'a>, + ) -> Result<bool> { + match event { + Event::EndFile(r) => match r { + EndFileReason::Eof => { + info!("Mpv reached eof of current video. Marking it watched."); + + self.mark_cvideo_watched(app).await?; + self.mark_cvideo_inactive(app).await?; + } + EndFileReason::Stop => {} + EndFileReason::Quit => { + info!("Mpv quit. Exiting playback"); + + // draining the playlist is okay, as mpv is done playing + let videos = mem::take(&mut self.current_playlist); + for video in videos { + self.mark_video_watched(app, &video).await?; + set_state_change(&app, &video, false).await?; + } + return Ok(true); + } + EndFileReason::Error => { + unreachable!("This have raised a separate error") + } + EndFileReason::Redirect => { + todo!("We probably need to handle this somehow"); + } + }, + Event::StartFile(playlist_index) => { + self.possibly_add_new_videos(app, &mpv).await?; + + self.mark_video_active(app, (playlist_index - 1) as usize) + .await?; + self.current_playlist_position = (playlist_index - 1) as usize; + self.apply_options( + app, + mpv, + &self.current_playlist[self.current_playlist_position], + ) + .await?; + } + Event::FileLoaded => {} + Event::ClientMessage(a) => { + debug!("Got Client Message event: '{}'", a.join(" ")); + + match a.as_slice() { + &["yt-comments-external"] => { + let binary = current_exe().expect("A current exe should exist"); + + let status = Command::new("riverctl") + .args(["focus-output", "next"]) + .status() + .await?; + if !status.success() { + bail!("focusing the next output failed!"); + } + + let status = Command::new("alacritty") + .args(&[ + "--title", + "floating please", + "--command", + binary.to_str().expect("Should be valid unicode"), + "comments", + ]) + .status() + .await?; + if !status.success() { + bail!("Falied to start `yt comments`"); + } + + let status = Command::new("riverctl") + .args(["focus-output", "next"]) + .status() + .await?; + if !status.success() { + bail!("focusing the next output failed!"); + } + } + &["yt-comments-local"] => { + let comments: String = get_comments(app) + .await? + .render(false) + .replace("\"", "") + .replace("'", "") + .chars() + .take(LOCAL_COMMENTS_LENGTH) + .collect(); + + mpv.execute("show-text", &[&format!("'{}'", comments), "6000"])?; + } + &["yt-description"] => { + // let description = description(app).await?; + mpv.execute("script-message", &["osc-message", "'<YT Description>'"])?; + } + &["yt-mark-watch-later"] => { + self.mark_cvideo_inactive(app).await?; + mpv.execute("write-watch-later-config", &[])?; + mpv.execute("playlist-remove", &["current"])?; + } + other => { + debug!("Unknown message: {}", other.join(" ")) + } + } + } + _ => {} + } + + Ok(false) + } +} |