diff --git a/Cargo.lock b/Cargo.lock index 5a95ac0..ab543dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "alsa" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b" +checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47" dependencies = [ "alsa-sys", "bitflags", @@ -97,12 +97,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - [[package]] name = "base64" version = "0.13.1" @@ -394,6 +388,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -402,11 +402,12 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "coreaudio-rs" -version = "0.10.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" +checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" dependencies = [ "bitflags", + "core-foundation-sys 0.6.2", "coreaudio-sys", ] @@ -421,24 +422,25 @@ dependencies = [ [[package]] name = "cpal" -version = "0.14.2" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f342c1b63e185e9953584ff2199726bf53850d96610a310e3aca09e9405a2d0b" +checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c" dependencies = [ "alsa", - "core-foundation-sys", + "core-foundation-sys 0.8.4", "coreaudio-rs", - "jni", + "dasp_sample", + "jni 0.19.0", "js-sys", "libc", - "mach", - "ndk 0.7.0", + "mach2", + "ndk", "ndk-context", "oboe", "once_cell", "parking_lot", - "stdweb", - "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "windows", ] @@ -541,7 +543,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", + "memoffset", "scopeguard", ] @@ -595,6 +597,12 @@ dependencies = [ "memchr 2.5.0", ] +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "dbus" version = "0.6.5" @@ -650,12 +658,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "either" version = "1.8.1" @@ -935,6 +937,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -1049,10 +1065,10 @@ dependencies = [ ] [[package]] -name = "mach" -version = "0.3.2" +name = "mach2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" dependencies = [ "libc", ] @@ -1082,15 +1098,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.0" @@ -1241,19 +1248,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "ndk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" -dependencies = [ - "bitflags", - "jni-sys", - "ndk-sys 0.3.0", - "num_enum", - "thiserror", -] - [[package]] name = "ndk" version = "0.7.0" @@ -1262,7 +1256,7 @@ checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ "bitflags", "jni-sys", - "ndk-sys 0.4.1+23.1.7779620", + "ndk-sys", "num_enum", "raw-window-handle", "thiserror", @@ -1274,15 +1268,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" -dependencies = [ - "jni-sys", -] - [[package]] name = "ndk-sys" version = "0.4.1+23.1.7779620" @@ -1294,15 +1279,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags", - "cc", "cfg-if", "libc", - "memoffset 0.6.5", ] [[package]] @@ -1444,12 +1427,12 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "oboe" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" +checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0" dependencies = [ - "jni", - "ndk 0.6.0", + "jni 0.20.0", + "ndk", "ndk-context", "num-derive", "num-traits", @@ -1458,9 +1441,9 @@ dependencies = [ [[package]] name = "oboe-sys" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" +checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2" dependencies = [ "cc", ] @@ -1913,9 +1896,9 @@ dependencies = [ [[package]] name = "rodio" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb10b653d5ec0e9411a2e7d46e2c7f4046fd87d35b9955bd73ba4108d69072b5" +checksum = "bdf1d4dea18dff2e9eb6dca123724f8b60ef44ad74a9ad283cdfe025df7e73fa" dependencies = [ "cpal", "symphonia", @@ -1954,15 +1937,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "rustfft" version = "5.1.1" @@ -2013,21 +1987,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.171" @@ -2080,21 +2039,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" version = "0.10.7" @@ -2136,55 +2080,6 @@ dependencies = [ "log", ] -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "strength_reduce" version = "0.2.4" @@ -2669,6 +2564,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.87" @@ -2753,15 +2660,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.37.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" dependencies = [ - "windows_aarch64_msvc 0.37.0", - "windows_i686_gnu 0.37.0", - "windows_i686_msvc 0.37.0", - "windows_x86_64_gnu 0.37.0", - "windows_x86_64_msvc 0.37.0", + "windows-targets 0.42.2", ] [[package]] @@ -2815,12 +2718,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2833,12 +2730,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2851,12 +2742,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2869,12 +2754,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2899,12 +2778,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" diff --git a/player/Cargo.toml b/player/Cargo.toml index 35e5980..90f8be3 100644 --- a/player/Cargo.toml +++ b/player/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://git.ngni.us/NGnius/muss" readme = "README.md" [dependencies] -rodio = { version = "^0.16", features = ["symphonia-all"], default-features = false} +rodio = { version = "^0.17", features = ["symphonia-all"], default-features = false} m3u8-rs = { version = "^3.0" } mpd = { version = "0.1", optional = true } diff --git a/player/src/controller.rs b/player/src/controller.rs index 288111b..de777a9 100644 --- a/player/src/controller.rs +++ b/player/src/controller.rs @@ -3,73 +3,85 @@ use std::thread::JoinHandle; use muss_interpreter::{InterpreterError, Item}; -use super::os_controls::SystemControlWrapper; -use super::player_wrapper::{ControlAction, PlayerAction, PlayerServer}; +use crate::EventHandlerBuilder; + +use super::player_wrapper::{ControlAction, PlayerAction, PlayerServer, PlaybackAction}; use super::PlaybackError; use super::Player; use super::PlayerError; /// A controller for a Player running on another thread. /// This receives and sends events like media buttons and script errors for the Player. -pub struct Controller { +pub struct Controller { control: Sender, event: Receiver, + player: Sender, handle: JoinHandle<()>, - sys_ctrl: SystemControlWrapper, + playback: Sender, + #[allow(dead_code)] + event_handler: H, } -impl Controller { +impl Controller { pub fn create< F: FnOnce() -> Player + Send + 'static, I: std::iter::Iterator>, + E: EventHandlerBuilder >( player_gen: F, + event_handler_builder: E, ) -> Self { let (control_tx, control_rx) = channel(); let (event_tx, event_rx) = channel(); let (playback_tx, playback_rx) = channel(); - let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone()); - sys_ctrl.init(playback_rx); + let (player_tx, player_rx) = channel(); + let event_handler = event_handler_builder.with_channels(player_rx, playback_rx, control_tx.clone()); let handle = PlayerServer::spawn( player_gen, control_tx.clone(), control_rx, event_tx, - playback_tx, + playback_tx.clone(), false, ); Self { control: control_tx, event: event_rx, + player: player_tx, handle: handle, - sys_ctrl: sys_ctrl, + event_handler: event_handler, + playback: playback_tx, } } pub fn create_repl< F: FnOnce() -> Player + Send + 'static, I: std::iter::Iterator>, + E: EventHandlerBuilder >( player_gen: F, + event_handler_builder: E, ) -> Self { let (control_tx, control_rx) = channel(); let (event_tx, event_rx) = channel(); let (playback_tx, playback_rx) = channel(); - let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone()); - sys_ctrl.init(playback_rx); + let (player_tx, player_rx) = channel(); + let event_handler = event_handler_builder.with_channels(player_rx, playback_rx, control_tx.clone()); let handle = PlayerServer::spawn( player_gen, control_tx.clone(), control_rx, event_tx, - playback_tx, + playback_tx.clone(), true, ); Self { control: control_tx, event: event_rx, + player: player_tx, handle: handle, - sys_ctrl: sys_ctrl, + event_handler: event_handler, + playback: playback_tx, } } @@ -100,6 +112,7 @@ impl Controller { } fn handle_event(&self, event: PlayerAction) -> Result<(), PlayerError> { + self.player.send(event.clone()).unwrap_or(()); match event { PlayerAction::Acknowledge(_) => Ok(()), PlayerAction::Exception(e) => Err(e), @@ -142,7 +155,9 @@ impl Controller { pub fn exit(self) -> Result<(), PlayerError> { self.send_confirm(ControlAction::Exit { ack: true })?; - self.sys_ctrl.exit(); + self.playback.send(PlaybackAction::Exit).map_err(|e| PlaybackError { + msg: format!("Playback exit message did not send successfully: {}", e), + })?; match self.handle.join() { Ok(x) => Ok(x), Err(_) => Err(PlaybackError { diff --git a/player/src/event_handler.rs b/player/src/event_handler.rs new file mode 100644 index 0000000..aadb890 --- /dev/null +++ b/player/src/event_handler.rs @@ -0,0 +1,115 @@ +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread::JoinHandle; + +use super::player_wrapper::{ControlAction, PlaybackAction, PlayerAction}; + +pub trait EventHandlerBuilder { + fn with_channels(self, player: Receiver, playback: Receiver, control: Sender) -> T; +} + +#[derive(Default)] +pub struct MultiEventHandler { + taps: Vec>, +} + +impl MultiEventHandler { + pub fn with_taps(taps: Vec>) -> Self { + Self { + taps: taps, + } + } + + pub fn with_sys_ctrl() -> Self { + Self { + taps: vec![ + Box::new(super::os_controls::SystemControlWrapper::new()), + ] + } + } + + pub fn add(mut self, tap: Box) -> Self { + self.taps.push(tap); + self + } +} + +impl EventHandlerBuilder for MultiEventHandler { + fn with_channels(self, player: Receiver, playback: Receiver, control: Sender) -> MultiEventHandlerImpl { + let (event_tx, event_rx) = channel(); + MultiEventHandlerImpl { + player_join: MultiEventHandlerImpl::player_thread(event_tx.clone(), player), + playback_join: MultiEventHandlerImpl::playback_thread(event_tx, playback), + handler_join: MultiEventHandlerImpl::handler_thread(self.taps, event_rx, control), + } + } +} + +/// Like a wire tap, but for player and playback events. This can also send control events to the player. +pub trait EventTap: Send { + fn on_playback(&self, playback: &PlaybackAction) -> Option; + + fn on_player(&self, player: &PlayerAction) -> Option; + + fn init_control(&mut self, _control: &Sender) {} +} + +pub struct MultiEventHandlerImpl { + #[allow(dead_code)] + player_join: JoinHandle<()>, + #[allow(dead_code)] + playback_join: JoinHandle<()>, + #[allow(dead_code)] + handler_join: JoinHandle<()>, +} + +impl MultiEventHandlerImpl { + fn playback_thread(event_tx: Sender, playback: Receiver) -> JoinHandle<()> { + std::thread::spawn(move || + while let Ok(playback_event) = playback.recv() { + event_tx.send(SomeEvent::Playback(playback_event)).unwrap_or(()); + } + ) + } + + fn player_thread(event_tx: Sender, player: Receiver) -> JoinHandle<()> { + std::thread::spawn(move || + while let Ok(player_event) = player.recv() { + event_tx.send(SomeEvent::Player(player_event)).unwrap_or(()); + } + ) + } + + fn handler_thread(mut taps: Vec>, event_rx: Receiver, control: Sender) -> JoinHandle<()> { + std::thread::spawn(move || { + for t in taps.iter_mut() { + t.init_control(&control); + } + while let Ok(event) = event_rx.recv() { + match event { + SomeEvent::Playback(p) => { + for t in taps.iter() { + t.on_playback(&p).map(|c| control.send(c).unwrap_or(())); + } + }, + SomeEvent::Player(p) => { + for t in taps.iter() { + t.on_player(&p).map(|c| control.send(c).unwrap_or(())); + } + }, + } + } + }) + } + + #[allow(dead_code)] + pub(crate) fn join(self) -> std::thread::Result<()> { + self.player_join.join()?; + self.playback_join.join()?; + self.handler_join.join() + } +} + +enum SomeEvent { + Playback(PlaybackAction), + Player(PlayerAction), +} diff --git a/player/src/lib.rs b/player/src/lib.rs index 68f1dbf..90ea933 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -8,6 +8,7 @@ mod controller; mod errors; +mod event_handler; pub(crate) mod os_controls; mod player; pub(crate) mod player_wrapper; @@ -16,9 +17,11 @@ pub(crate) mod uri; pub use controller::Controller; pub use errors::{PlaybackError, PlayerError, UriError}; +pub use event_handler::{EventHandlerBuilder, EventTap, MultiEventHandler}; #[cfg(feature = "mpd")] pub use player::mpd_connection; pub use player::Player; +pub use player_wrapper::{ControlAction, PlaybackAction, PlayerAction}; //pub use utility::{play_script}; #[cfg(test)] diff --git a/player/src/os_controls.rs b/player/src/os_controls.rs index 0a0af09..b70c4bd 100644 --- a/player/src/os_controls.rs +++ b/player/src/os_controls.rs @@ -13,17 +13,15 @@ use muss_interpreter::Item; use std::io::Write; //use super::Controller; -use super::player_wrapper::{ControlAction, PlaybackAction}; +use super::player_wrapper::{ControlAction, PlaybackAction, PlayerAction}; /// OS-specific APIs for media controls. /// Currently only Linux (dbus) is supported. #[cfg(all(target_os = "linux", feature = "os-controls", feature = "mpris-player"))] pub struct SystemControlWrapper { - control: Sender, - dbus_handle: Option>, //std::sync::Arc, + dbus_handle: Option>, dbus_ctrl: Option>, - playback_event_handler: Option>, - playback_event_handler_killer: Option>, + state: std::sync::Mutex, } /// OS-specific APIs for media controls. @@ -33,9 +31,6 @@ pub struct SystemControlWrapper { not(all(target_os = "linux", feature = "os-controls", feature = "mpris-player")) ))] pub struct SystemControlWrapper { - #[allow(dead_code)] - control: Sender, - playback_receiver: Option>, } #[cfg(all(target_os = "linux", feature = "os-controls", feature = "mpris-player"))] @@ -47,21 +42,23 @@ enum DbusControl { #[cfg(all(target_os = "linux", feature = "os-controls", feature = "mpris-player"))] impl SystemControlWrapper { - pub fn new(control: Sender) -> Self { + pub fn new() -> Self { Self { - control: control, - dbus_handle: None, //MprisPlayer::new("mps".into(), "mps".into(), "null".into()) + dbus_handle: None, dbus_ctrl: None, - playback_event_handler: None, - playback_event_handler_killer: None, + state: std::sync::Mutex::new( + State { + playback_time: 0, + duration_cache: None, + } + ), } } - pub fn init(&mut self, playback: Receiver) { + fn init(&mut self, control: &Sender) { let (tx, dbus_ctrl) = channel(); - let dbus_ctrl_tx_clone = tx.clone(); self.dbus_ctrl = Some(tx); - let control_clone1 = self.control.clone(); + let control_clone1 = control.clone(); self.dbus_handle = Some(std::thread::spawn(move || { let dbus_conn = MprisPlayer::new("muss".into(), "muss".into(), "ngnius.muss".into()); //let (msg_tx, msg_rx) = channel(); @@ -155,57 +152,16 @@ impl SystemControlWrapper { } } })); - let (tx, rx) = channel(); - self.playback_event_handler_killer = Some(tx); - self.playback_event_handler = Some(std::thread::spawn(move || { - let mut playback_time = 0; - let mut duration_cache = None; - loop { - if let Ok(_) = rx.try_recv() { - break; - } - match playback.recv() { - Err(_) => break, - Ok(PlaybackAction::Exit) => break, - Ok(PlaybackAction::Enqueued(item)) => { - playback_time = 0; - duration_cache = None; - Self::enqueued(item, &dbus_ctrl_tx_clone); - } - Ok(PlaybackAction::Empty) => Self::empty(&dbus_ctrl_tx_clone), - Ok(PlaybackAction::Time(item, duration)) => { - duration_cache = Some(duration); - Self::time(item, duration, &dbus_ctrl_tx_clone); - } - Ok(PlaybackAction::UpdateTick(item)) => { - Self::time_update( - item, - playback_time, - &duration_cache, - &dbus_ctrl_tx_clone, - ); - playback_time += 1; - } - } - } - })); } - pub fn exit(self) { + pub fn exit(&self) { // exit dbus thread - if let Some(tx) = self.dbus_ctrl { + if let Some(tx) = &self.dbus_ctrl { tx.send(DbusControl::Die).unwrap_or(()); } - if let Some(handle) = self.dbus_handle { + /*if let Some(handle) = &self.dbus_handle { handle.join().unwrap_or(()); - } - // exit playback event thread - if let Some(tx) = self.playback_event_handler_killer { - tx.send(()).unwrap_or(()); - } - if let Some(handle) = self.playback_event_handler { - handle.join().unwrap_or(()); - } + }*/ } fn build_metadata(item: Item) -> Metadata { @@ -327,21 +283,69 @@ impl SystemControlWrapper { } } +#[cfg(all(target_os = "linux", feature = "os-controls", feature = "mpris-player"))] +struct State { + playback_time: i64, + duration_cache: Option, +} + +#[cfg(all(target_os = "linux", feature = "os-controls", feature = "mpris-player"))] +impl super::EventTap for SystemControlWrapper { + fn on_playback(&self, playback: &PlaybackAction) -> Option { + match playback { + PlaybackAction::Exit => { + self.exit(); + }, + PlaybackAction::Enqueued(item) => { + let mut state = self.state.lock().unwrap(); + state.playback_time = 0; + state.duration_cache = None; + Self::enqueued(item.to_owned(), self.dbus_ctrl.as_ref().unwrap()); + }, + PlaybackAction::Empty => Self::empty(self.dbus_ctrl.as_ref().unwrap()), + PlaybackAction::Time(item, duration) => { + let mut state = self.state.lock().unwrap(); + state.duration_cache = Some(duration.to_owned()); + Self::time(item.to_owned(), duration.to_owned(), self.dbus_ctrl.as_ref().unwrap()); + } + PlaybackAction::UpdateTick(item) => { + let mut state = self.state.lock().unwrap(); + Self::time_update( + item.to_owned(), + state.playback_time, + &state.duration_cache, + self.dbus_ctrl.as_ref().unwrap(), + ); + state.playback_time += 1; + } + } + None + } + + fn on_player(&self, _player: &PlayerAction) -> Option { None } + + fn init_control(&mut self, control: &Sender) { + self.init(control); + } +} + #[cfg(any( not(feature = "os-controls"), not(all(target_os = "linux", feature = "os-controls", feature = "mpris-player")) ))] impl SystemControlWrapper { - pub fn new(control: Sender) -> Self { + pub fn new() -> Self { Self { - control: control, - playback_receiver: None, } } - - pub fn init(&mut self, playback: Receiver) { - self.playback_receiver = Some(playback); - } - - pub fn exit(self) {} +} + +#[cfg(any( + not(feature = "os-controls"), + not(all(target_os = "linux", feature = "os-controls", feature = "mpris-player")) +))] +impl super::EventTap for SystemControlWrapper { + fn on_playback(&self, _playback: &PlaybackAction) -> Option { None } + + fn on_player(&self, _player: &PlayerAction) -> Option { None } } diff --git a/src/debug_state.rs b/src/debug_state.rs new file mode 100644 index 0000000..4563861 --- /dev/null +++ b/src/debug_state.rs @@ -0,0 +1,56 @@ +use muss_interpreter::Item; + +use muss_player::EventTap; +use muss_player::{PlaybackAction, PlayerAction, ControlAction}; + +pub struct DebugState { + pub now_playing: Option, + pub control_tx: Option>>, +} + +impl DebugState { + pub fn new() -> std::sync::Arc> { + std::sync::Arc::new(std::sync::RwLock::new(Self { + now_playing: None, + control_tx: None, + })) + } +} + +pub struct DebugEventHandler { + state: std::sync::Arc>, +} + +impl DebugEventHandler { + pub fn new(debug_state: std::sync::Arc>) -> Self { + Self { + state: debug_state, + } + } +} + +impl EventTap for DebugEventHandler { + fn on_playback(&self, playback: &PlaybackAction) -> Option { + match playback { + PlaybackAction::Enqueued(item) => { + let mut state = self.state.write().unwrap(); + state.now_playing = Some(item.to_owned()); + }, + PlaybackAction::Empty | PlaybackAction::Exit => { + let mut state = self.state.write().unwrap(); + state.now_playing = None; + }, + PlaybackAction::Time(_item, _dur) => {}, + PlaybackAction::UpdateTick(_item) => {}, + } + None + } + + fn on_player(&self, _player: &PlayerAction) -> Option { + None + } + + fn init_control(&mut self, control: &std::sync::mpsc::Sender) { + self.state.write().expect("Failed to get write lock on controller debug state").control_tx = Some(std::sync::Mutex::new(control.clone())); + } +} diff --git a/src/help.rs b/src/help.rs index 4db942b..27c5442 100644 --- a/src/help.rs +++ b/src/help.rs @@ -187,5 +187,11 @@ REPL-specific operations to help with writing Muss scripts: ?command normal Cancel any active ?skip or ?list operation. + now + Display the currently-playing item. + + next + Skip the rest of the currently-playing item and proceed to the next item. + verbose Toggle verbose messages, like extra information on items in ?list and other goodies."; diff --git a/src/main.rs b/src/main.rs index 4054922..4107492 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,9 +45,17 @@ //! ## Is Muss a scripting language? //! **Yes**. It evolved from a simple query language into something that can do arbitrary calculations. Whether it's Turing-complete is still unproven, but it's powerful enough for what I want it to do. //! +//! # Wishlist +//! +//! - Multithreaded execution (especially filtering) +//! - Lazy field value calculation +//! - Better dbus integration (duration display improvements, seeking, previous track) +//! - Vector type variant +//! mod channel_io; mod cli; +mod debug_state; mod help; mod repl; @@ -122,7 +130,8 @@ fn main() { } } else { // live playback - let ctrl = Controller::create(player_builder); + let event_handler = muss_player::MultiEventHandler::with_sys_ctrl(); + let ctrl = Controller::create(player_builder, event_handler); match ctrl.wait_for_done() { Ok(_) => println!("Success: Finished playback from script `{}`", script_file), Err(e) => eprintln!("{}", e), diff --git a/src/repl.rs b/src/repl.rs index 7647259..50c328d 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -41,6 +41,7 @@ struct ReplState { cursor_rightward_position: usize, //debug: Arc>, list_rx: Receiver, + controller_debug: std::sync::Arc>, } #[derive(Clone)] @@ -74,6 +75,7 @@ impl ReplState { debug_flag: DebugFlag::Normal, })),*/ list_rx: debug_list, + controller_debug: crate::debug_state::DebugState::new(), } } } @@ -296,7 +298,9 @@ pub fn repl(args: CliArgs) { writeln!(state.terminal, "Playback mode (output: audio device)") .expect(TERMINAL_WRITE_ERROR); } - let ctrl = Controller::create_repl(player_builder); + let event_handler = muss_player::MultiEventHandler::with_sys_ctrl() + .add(Box::new(crate::debug_state::DebugEventHandler::new(state.controller_debug.clone()))); + let ctrl = Controller::create_repl(player_builder, event_handler); read_loop(&args, &mut state, |state, args| { if args.wait { match ctrl.wait_for_empty() { @@ -689,7 +693,7 @@ fn error_prompt(error: muss_player::PlayerError, args: &CliArgs) { eprintln!("*E{}{}", args.prompt, error); } -fn repl_commands(command_str: &str, state: &mut ReplState, _args: &CliArgs) { +fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) { let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect(); match words[0] { "?help" => { @@ -747,6 +751,29 @@ fn repl_commands(command_str: &str, state: &mut ReplState, _args: &CliArgs) { } "?commands" => { writeln!(state.terminal, "{}", super::help::REPL_COMMANDS).expect(TERMINAL_WRITE_ERROR) + }, + "?now" => { + let data = state.controller_debug.read().expect("Failed to get read lock for debug player data"); + if let Some(item) = &data.now_playing { + let verbose = DEBUG_STATE + .read() + .expect("Failed to get read lock for debug state") + .verbose; + pretty_print_item(item, &mut state.terminal, args, verbose); + } else { + writeln!(state.terminal, "Nothing playing").expect(TERMINAL_WRITE_ERROR) + } + }, + "?next" => { + state.controller_debug + .read() + .expect("Failed to get read lock for debug player data") + .control_tx.as_ref() + .expect("Control action sender shouldn't be None") + .lock() + .expect("Failed to get lock for control action sender") + .send(muss_player::ControlAction::Next { ack: false }) + .unwrap_or(()); } _ => writeln!(state.terminal, "Unknown command, try ?help").expect(TERMINAL_WRITE_ERROR), }