Create barebones music player for mps interpretor
This commit is contained in:
parent
dbea13e676
commit
7a327767f3
13 changed files with 520 additions and 11 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "mpris-player"]
|
||||||
|
path = mpris-player
|
||||||
|
url = https://github.com/NGnius/mpris-player
|
27
Cargo.lock
generated
27
Cargo.lock
generated
|
@ -238,6 +238,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48b5f0f36f1eebe901b0e6bee369a77ed3396334bf3f09abd46454a576f71819"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libdbus-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -419,6 +429,15 @@ version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libdbus-sys"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -507,6 +526,13 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mpris-player"
|
||||||
|
version = "0.6.1"
|
||||||
|
dependencies = [
|
||||||
|
"dbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mps"
|
name = "mps"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -529,6 +555,7 @@ name = "mps-player"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"m3u8-rs",
|
"m3u8-rs",
|
||||||
|
"mpris-player",
|
||||||
"mps-interpreter",
|
"mps-interpreter",
|
||||||
"rodio",
|
"rodio",
|
||||||
]
|
]
|
||||||
|
|
1
mpris-player
Submodule
1
mpris-player
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 2e843ec8d917a4859301de366a7141d5f19df6ec
|
|
@ -5,7 +5,9 @@ pub enum MpsToken {
|
||||||
Sql,
|
Sql,
|
||||||
OpenBracket,
|
OpenBracket,
|
||||||
CloseBracket,
|
CloseBracket,
|
||||||
|
Comma,
|
||||||
Literal(String),
|
Literal(String),
|
||||||
|
Name(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MpsToken {
|
impl MpsToken {
|
||||||
|
@ -14,7 +16,22 @@ impl MpsToken {
|
||||||
"sql" => Ok(Self::Sql),
|
"sql" => Ok(Self::Sql),
|
||||||
"(" => Ok(Self::OpenBracket),
|
"(" => Ok(Self::OpenBracket),
|
||||||
")" => Ok(Self::CloseBracket),
|
")" => Ok(Self::CloseBracket),
|
||||||
_ => Err(s),
|
"," => Ok(Self::Comma),
|
||||||
|
_ => {
|
||||||
|
// name validation
|
||||||
|
let mut ok = true;
|
||||||
|
for invalid_c in ["-", "+", ","] {
|
||||||
|
if s.contains(invalid_c) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
Ok(Self::Name(s))
|
||||||
|
} else {
|
||||||
|
Err(s)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +62,13 @@ impl MpsToken {
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_name(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Name(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for MpsToken {
|
impl Display for MpsToken {
|
||||||
|
@ -53,7 +77,9 @@ impl Display for MpsToken {
|
||||||
Self::Sql => write!(f, "sql"),
|
Self::Sql => write!(f, "sql"),
|
||||||
Self::OpenBracket => write!(f, "("),
|
Self::OpenBracket => write!(f, "("),
|
||||||
Self::CloseBracket => write!(f, ")"),
|
Self::CloseBracket => write!(f, ")"),
|
||||||
|
Self::Comma => write!(f, ","),
|
||||||
Self::Literal(s) => write!(f, "\"{}\"", s),
|
Self::Literal(s) => write!(f, "\"{}\"", s),
|
||||||
|
Self::Name(s) => write!(f, "{}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ impl<R> MpsTokenizer<R> where R: std::io::Read {
|
||||||
);
|
);
|
||||||
bigger_buf.clear();
|
bigger_buf.clear();
|
||||||
},
|
},
|
||||||
ReaderStateMachine::Bracket{..} => {
|
ReaderStateMachine::SingleCharToken{..} => {
|
||||||
let out = bigger_buf.pop().unwrap(); // bracket token
|
let out = bigger_buf.pop().unwrap(); // bracket or comma token
|
||||||
if bigger_buf.len() != 0 { // bracket tokens can be beside other tokens, without separator
|
if bigger_buf.len() != 0 { // bracket tokens can be beside other tokens, without separator
|
||||||
let token = String::from_utf8(bigger_buf.clone())
|
let token = String::from_utf8(bigger_buf.clone())
|
||||||
.map_err(|e| self.error(format!("UTF-8 encoding error: {}", e)))?;
|
.map_err(|e| self.error(format!("UTF-8 encoding error: {}", e)))?;
|
||||||
|
@ -171,7 +171,7 @@ enum ReaderStateMachine {
|
||||||
InsideQuoteLiteral{
|
InsideQuoteLiteral{
|
||||||
out: u8,
|
out: u8,
|
||||||
},
|
},
|
||||||
Bracket {
|
SingleCharToken {
|
||||||
out: u8,
|
out: u8,
|
||||||
},
|
},
|
||||||
EndLiteral{},
|
EndLiteral{},
|
||||||
|
@ -186,7 +186,7 @@ impl ReaderStateMachine {
|
||||||
match self {
|
match self {
|
||||||
Self::Start{}
|
Self::Start{}
|
||||||
| Self::Regular{..}
|
| Self::Regular{..}
|
||||||
| Self::Bracket{..}
|
| Self::SingleCharToken{..}
|
||||||
| Self::EndLiteral{}
|
| Self::EndLiteral{}
|
||||||
| Self::EndToken{}
|
| Self::EndToken{}
|
||||||
| Self::EndStatement{} =>
|
| Self::EndStatement{} =>
|
||||||
|
@ -197,7 +197,7 @@ impl ReaderStateMachine {
|
||||||
' ' => Self::EndToken{},
|
' ' => Self::EndToken{},
|
||||||
'\n' | '\r' | ';' => Self::EndStatement{},
|
'\n' | '\r' | ';' => Self::EndStatement{},
|
||||||
'\0' => Self::EndOfFile{},
|
'\0' => Self::EndOfFile{},
|
||||||
'(' | ')' => Self::Bracket{out: input},
|
'(' | ')' | ',' => Self::SingleCharToken{out: input},
|
||||||
_ => Self::Regular{out: input},
|
_ => Self::Regular{out: input},
|
||||||
},
|
},
|
||||||
Self::Escaped{inside} => match inside {
|
Self::Escaped{inside} => match inside {
|
||||||
|
@ -240,7 +240,7 @@ impl ReaderStateMachine {
|
||||||
pub fn output(&self) -> Option<u8> {
|
pub fn output(&self) -> Option<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Regular{ out, ..}
|
Self::Regular{ out, ..}
|
||||||
| Self::Bracket{ out, ..}
|
| Self::SingleCharToken{ out, ..}
|
||||||
| Self::InsideTickLiteral{ out, ..}
|
| Self::InsideTickLiteral{ out, ..}
|
||||||
| Self::InsideQuoteLiteral{ out, ..} => Some(*out),
|
| Self::InsideQuoteLiteral{ out, ..} => Some(*out),
|
||||||
_ => None
|
_ => None
|
||||||
|
|
|
@ -9,3 +9,7 @@ m3u8-rs = { version = "^3.0.0" }
|
||||||
|
|
||||||
# local
|
# local
|
||||||
mps-interpreter = { path = "../mps-interpreter" }
|
mps-interpreter = { path = "../mps-interpreter" }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
#dbus = { version = "^0.9" }
|
||||||
|
mpris-player = { version = "^0.6.1", path = "../mpris-player" }
|
||||||
|
|
115
mps-player/src/controller.rs
Normal file
115
mps-player/src/controller.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use std::sync::mpsc::{Sender, Receiver, channel};
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
use mps_interpreter::tokens::MpsTokenReader;
|
||||||
|
|
||||||
|
use super::MpsPlayer;
|
||||||
|
use super::PlaybackError;
|
||||||
|
use super::player_wrapper::{ControlAction, PlayerAction, MpsPlayerServer};
|
||||||
|
use super::os_controls::SystemControlWrapper;
|
||||||
|
|
||||||
|
pub struct MpsController {
|
||||||
|
control: Sender<ControlAction>,
|
||||||
|
event: Receiver<PlayerAction>,
|
||||||
|
handle: JoinHandle<()>,
|
||||||
|
sys_ctrl: SystemControlWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MpsController {
|
||||||
|
pub fn create<F: FnOnce() -> MpsPlayer<T> + Send + 'static, T: MpsTokenReader>(
|
||||||
|
player_gen: F
|
||||||
|
) -> Self {
|
||||||
|
let (control_tx, control_rx) = channel();
|
||||||
|
let (event_tx, event_rx) = channel();
|
||||||
|
let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone());
|
||||||
|
sys_ctrl.init();
|
||||||
|
let handle = MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx);
|
||||||
|
Self {
|
||||||
|
control: control_tx,
|
||||||
|
event: event_rx,
|
||||||
|
handle: handle,
|
||||||
|
sys_ctrl: sys_ctrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_confirm(&self, to_send: ControlAction) -> Result<(), PlaybackError> {
|
||||||
|
self.control.send(to_send.clone()).map_err(PlaybackError::from_err)?;
|
||||||
|
let mut response = self.event.recv().map_err(PlaybackError::from_err)?;
|
||||||
|
while !response.is_acknowledgement() {
|
||||||
|
Self::handle_event(response)?;
|
||||||
|
response = self.event.recv().map_err(PlaybackError::from_err)?;
|
||||||
|
}
|
||||||
|
if let PlayerAction::Acknowledge(action) = response {
|
||||||
|
if action == to_send {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(PlaybackError {
|
||||||
|
msg: "Incorrect acknowledgement received for MpsController control action".into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(PlaybackError {
|
||||||
|
msg: "Invalid acknowledgement received for MpsController control action".into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(event: PlayerAction) -> Result<(), PlaybackError> {
|
||||||
|
match event {
|
||||||
|
PlayerAction::Acknowledge(_) => Ok(()),
|
||||||
|
PlayerAction::Exception(e) => Err(e),
|
||||||
|
PlayerAction::End => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::Next{ack: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&self) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::Previous{ack: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(&self) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::Play{ack: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&self) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::Pause{ack: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&self) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::Stop{ack: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue(&self, count: usize) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::Enqueue{amount: count, ack: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ping(&self) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::NoOp{ack: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit(self) -> Result<(), PlaybackError> {
|
||||||
|
self.send_confirm(ControlAction::Exit{ack: true})?;
|
||||||
|
self.sys_ctrl.exit();
|
||||||
|
match self.handle.join() {
|
||||||
|
Ok(x) => Ok(x),
|
||||||
|
Err(_) => Err(PlaybackError {
|
||||||
|
msg: "MpsPlayerServer did not exit correctly".into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_done(&self) -> Result<(), PlaybackError> {
|
||||||
|
loop {
|
||||||
|
let msg = self.event.recv().map_err(PlaybackError::from_err)?;
|
||||||
|
if let PlayerAction::End = msg {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Self::handle_event(msg)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt::{Debug, Display, Formatter, Error};
|
use std::fmt::{Debug, Display, Formatter, Error};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PlaybackError {
|
pub struct PlaybackError {
|
||||||
pub(crate) msg: String
|
pub(crate) msg: String
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
mod controller;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
pub(crate) mod os_controls;
|
||||||
mod player;
|
mod player;
|
||||||
|
pub(crate) mod player_wrapper;
|
||||||
|
//mod utility;
|
||||||
|
|
||||||
|
pub use controller::MpsController;
|
||||||
pub use errors::PlaybackError;
|
pub use errors::PlaybackError;
|
||||||
pub use player::MpsPlayer;
|
pub use player::MpsPlayer;
|
||||||
|
//pub use utility::{play_script};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {}
|
mod tests {}
|
||||||
|
|
131
mps-player/src/os_controls.rs
Normal file
131
mps-player/src/os_controls.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::sync::mpsc::{Sender, channel};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use mpris_player::{MprisPlayer, PlaybackStatus};
|
||||||
|
|
||||||
|
//use super::MpsController;
|
||||||
|
use super::player_wrapper::ControlAction;
|
||||||
|
|
||||||
|
pub struct SystemControlWrapper {
|
||||||
|
control: Sender<ControlAction>,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
dbus_handle: Option<JoinHandle<()>>,//std::sync::Arc<MprisPlayer>,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
dbus_die: Option<Sender<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
impl SystemControlWrapper {
|
||||||
|
pub fn new(control: Sender<ControlAction>) -> Self {
|
||||||
|
Self {
|
||||||
|
control: control,
|
||||||
|
dbus_handle: None,//MprisPlayer::new("mps".into(), "mps".into(), "null".into())
|
||||||
|
dbus_die: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self) {
|
||||||
|
let (tx, die) = channel();
|
||||||
|
self.dbus_die = Some(tx);
|
||||||
|
let control_clone1 = self.control.clone();
|
||||||
|
self.dbus_handle = Some(std::thread::spawn(move || {
|
||||||
|
let dbus_conn = MprisPlayer::new("mps".into(), "mps".into(), "null".into());
|
||||||
|
//let (msg_tx, msg_rx) = channel();
|
||||||
|
// dbus setup
|
||||||
|
//self.dbus_conn.set_supported_mime_types(vec![]);
|
||||||
|
//self.dbus_conn.set_supported_uri_schemes(vec![]);
|
||||||
|
let mut is_playing = true;
|
||||||
|
dbus_conn.set_playback_status(PlaybackStatus::Playing);
|
||||||
|
dbus_conn.set_can_play(true);
|
||||||
|
dbus_conn.set_can_pause(true);
|
||||||
|
dbus_conn.set_can_go_next(true);
|
||||||
|
|
||||||
|
let control_clone = control_clone1.clone();
|
||||||
|
dbus_conn.connect_next(
|
||||||
|
move || {
|
||||||
|
//println!("Got next signal");
|
||||||
|
control_clone.send(ControlAction::Next{ack: false}).unwrap_or(())
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let control_clone = control_clone1.clone();
|
||||||
|
dbus_conn.connect_previous(
|
||||||
|
move || control_clone.send(ControlAction::Previous{ack: false}).unwrap_or(())
|
||||||
|
);
|
||||||
|
|
||||||
|
let control_clone = control_clone1.clone();
|
||||||
|
let dbus_conn_clone = dbus_conn.clone();
|
||||||
|
dbus_conn.connect_pause(
|
||||||
|
move || {
|
||||||
|
//println!("Got pause signal");
|
||||||
|
dbus_conn_clone.set_playback_status(PlaybackStatus::Paused);
|
||||||
|
control_clone.send(ControlAction::Pause{ack: false}).unwrap_or(());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let control_clone = control_clone1.clone();
|
||||||
|
let dbus_conn_clone = dbus_conn.clone();
|
||||||
|
dbus_conn.connect_play(
|
||||||
|
move || {
|
||||||
|
//println!("Got play signal");
|
||||||
|
dbus_conn_clone.set_playback_status(PlaybackStatus::Playing);
|
||||||
|
control_clone.send(ControlAction::Play{ack: false}).unwrap_or(())
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let control_clone = control_clone1.clone();
|
||||||
|
let dbus_conn_clone = dbus_conn.clone();
|
||||||
|
dbus_conn.connect_play_pause(
|
||||||
|
move || {
|
||||||
|
//println!("Got play_pause signal (was playing? {})", is_playing);
|
||||||
|
if is_playing {
|
||||||
|
dbus_conn_clone.set_playback_status(PlaybackStatus::Paused);
|
||||||
|
control_clone.send(ControlAction::Pause{ack: false}).unwrap_or(());
|
||||||
|
} else {
|
||||||
|
dbus_conn_clone.set_playback_status(PlaybackStatus::Playing);
|
||||||
|
control_clone.send(ControlAction::Play{ack: false}).unwrap_or(());
|
||||||
|
}
|
||||||
|
is_playing = !is_playing;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let control_clone = control_clone1.clone();
|
||||||
|
dbus_conn.connect_volume(
|
||||||
|
move |v| control_clone.send(ControlAction::SetVolume{ack: false, volume: (v * (u32::MAX as f64)) as _}).unwrap_or(())
|
||||||
|
);
|
||||||
|
|
||||||
|
// poll loop, using my custom mpris lib because original did it wrong
|
||||||
|
loop {
|
||||||
|
dbus_conn.poll(5);
|
||||||
|
if let Ok(_) = die.try_recv() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit(self) {
|
||||||
|
if let Some(tx) = self.dbus_die {
|
||||||
|
tx.send(()).unwrap_or(());
|
||||||
|
}
|
||||||
|
if let Some(handle) = self.dbus_handle {
|
||||||
|
handle.join().unwrap_or(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux")))]
|
||||||
|
impl SystemControlWrapper {
|
||||||
|
pub fn new(control: Sender<ControlAction>) -> Self {
|
||||||
|
Self {
|
||||||
|
control: control,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self) {}
|
||||||
|
|
||||||
|
pub fn exit(self) {}
|
||||||
|
}
|
|
@ -62,6 +62,26 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn enqueue(&mut self, count: usize) -> Result<(), PlaybackError> {
|
||||||
|
let mut items_left = count;
|
||||||
|
for item in &mut self.runner {
|
||||||
|
if items_left == 0 { return Ok(()); }
|
||||||
|
match item {
|
||||||
|
Ok(music) => {
|
||||||
|
let file = fs::File::open(music.filename).map_err(PlaybackError::from_err)?;
|
||||||
|
let stream = io::BufReader::new(file);
|
||||||
|
let source = Decoder::new(stream).map_err(PlaybackError::from_err)?;
|
||||||
|
self.sink.append(source);
|
||||||
|
self.sink.play(); // idk if this is necessary
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(e) => Err(PlaybackError::from_err(e))
|
||||||
|
}?;
|
||||||
|
items_left -= 1;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resume(&self) {
|
pub fn resume(&self) {
|
||||||
self.sink.play()
|
self.sink.play()
|
||||||
}
|
}
|
||||||
|
@ -105,6 +125,14 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
}
|
}
|
||||||
playlist.write_to(w).map_err(PlaybackError::from_err)
|
playlist.write_to(w).map_err(PlaybackError::from_err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_paused(&self) -> bool {
|
||||||
|
self.sink.is_paused()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_volume(&self, volume: f32) {
|
||||||
|
self.sink.set_volume(volume);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
160
mps-player/src/player_wrapper.rs
Normal file
160
mps-player/src/player_wrapper.rs
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
use std::sync::mpsc::{Sender, Receiver};
|
||||||
|
use std::{thread, thread::JoinHandle};
|
||||||
|
|
||||||
|
use mps_interpreter::tokens::MpsTokenReader;
|
||||||
|
|
||||||
|
use super::MpsPlayer;
|
||||||
|
use super::PlaybackError;
|
||||||
|
|
||||||
|
const DEFAULT_QUEUE_SIZE: usize = 1;
|
||||||
|
|
||||||
|
pub struct MpsPlayerServer<T: MpsTokenReader> {
|
||||||
|
player: MpsPlayer<T>,
|
||||||
|
control: Receiver<ControlAction>,
|
||||||
|
event: Sender<PlayerAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
|
pub fn new(player: MpsPlayer<T>, ctrl: Receiver<ControlAction>, event: Sender<PlayerAction>) -> Self {
|
||||||
|
Self {
|
||||||
|
player: player,
|
||||||
|
control: ctrl,
|
||||||
|
event: event,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_loop(&mut self) {
|
||||||
|
// this can panic since it's not on the main thread
|
||||||
|
if let Err(e) = self.player.enqueue_all() {
|
||||||
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let command = self.control.recv().unwrap();
|
||||||
|
|
||||||
|
// keep queue full
|
||||||
|
if self.player.queue_len() < DEFAULT_QUEUE_SIZE {
|
||||||
|
if let Err(e) = self.player.enqueue(DEFAULT_QUEUE_SIZE - self.player.queue_len()) {
|
||||||
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
|
}
|
||||||
|
if self.player.queue_len() == 0 { // no more music to add
|
||||||
|
self.event.send(PlayerAction::End).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process command
|
||||||
|
let mut is_exiting = false;
|
||||||
|
match command {
|
||||||
|
ControlAction::Next{..} => {
|
||||||
|
if self.player.queue_len() <= 1 {
|
||||||
|
self.player.stop();
|
||||||
|
//self.player.resume();
|
||||||
|
self.player.enqueue(1).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ControlAction::Previous{..} => {}, // TODO
|
||||||
|
ControlAction::Play{..} => self.player.resume(),
|
||||||
|
ControlAction::Pause{..} => self.player.pause(),
|
||||||
|
ControlAction::PlayPause{..} => {
|
||||||
|
if self.player.is_paused() {
|
||||||
|
self.player.resume();
|
||||||
|
} else {
|
||||||
|
self.player.pause();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ControlAction::Stop{..} => self.player.stop(),
|
||||||
|
ControlAction::Exit{..} => {
|
||||||
|
self.player.stop();
|
||||||
|
is_exiting = true;
|
||||||
|
},
|
||||||
|
ControlAction::Enqueue{amount,..} => {
|
||||||
|
if let Err(e) = self.player.enqueue(amount) {
|
||||||
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ControlAction::NoOp{..} => {}, // empty by design
|
||||||
|
ControlAction::SetVolume{volume,..} => {
|
||||||
|
self.player.set_volume((volume as f32) / (u32::MAX as f32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if command.needs_ack() {
|
||||||
|
self.event.send(PlayerAction::Acknowledge(command)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_exiting { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn<F: FnOnce() -> MpsPlayer<T> + Send + 'static>(
|
||||||
|
factory: F,
|
||||||
|
ctrl_tx: Sender<ControlAction>,
|
||||||
|
ctrl_rx: Receiver<ControlAction>,
|
||||||
|
event: Sender<PlayerAction>
|
||||||
|
) -> JoinHandle<()> {
|
||||||
|
thread::spawn(move || Self::unblocking_timer_loop(ctrl_tx, 50));
|
||||||
|
thread::spawn(move || {
|
||||||
|
let player = factory();
|
||||||
|
let mut server_obj = Self::new(player, ctrl_rx, event);
|
||||||
|
server_obj.run_loop();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unblocking_timer_loop(ctrl_tx: Sender<ControlAction>, sleep_ms: u64) {
|
||||||
|
let dur = std::time::Duration::from_millis(sleep_ms);
|
||||||
|
loop {
|
||||||
|
if let Err(_) = ctrl_tx.send(ControlAction::NoOp{ack: false}) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
thread::sleep(dur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Action the controller wants the player to perform
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub enum ControlAction {
|
||||||
|
Next{ack: bool},
|
||||||
|
Previous{ack: bool},
|
||||||
|
Play{ack: bool},
|
||||||
|
Pause{ack: bool},
|
||||||
|
PlayPause{ack: bool},
|
||||||
|
Stop{ack: bool},
|
||||||
|
Exit{ack: bool},
|
||||||
|
Enqueue {amount: usize, ack: bool},
|
||||||
|
NoOp{ack: bool},
|
||||||
|
SetVolume{ack: bool, volume: u32}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlAction {
|
||||||
|
fn needs_ack(&self) -> bool {
|
||||||
|
*match self {
|
||||||
|
Self::Next{ack} => ack,
|
||||||
|
Self::Previous{ack} => ack,
|
||||||
|
Self::Play{ack} => ack,
|
||||||
|
Self::Pause{ack} => ack,
|
||||||
|
Self::PlayPause{ack} => ack,
|
||||||
|
Self::Stop{ack} => ack,
|
||||||
|
Self::Exit{ack} => ack,
|
||||||
|
Self::Enqueue{ack,..} => ack,
|
||||||
|
Self::NoOp{ack,..} => ack,
|
||||||
|
Self::SetVolume{ack,..} => ack,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Action the player has performed/encountered
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum PlayerAction {
|
||||||
|
Acknowledge(ControlAction),
|
||||||
|
Exception(PlaybackError),
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerAction {
|
||||||
|
pub fn is_acknowledgement(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Acknowledge(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/main.rs
14
src/main.rs
|
@ -1,15 +1,23 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use mps_interpreter::MpsRunner;
|
use mps_interpreter::MpsRunner;
|
||||||
use mps_player::{MpsPlayer, PlaybackError};
|
use mps_player::{MpsPlayer, PlaybackError, MpsController};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn play_cursor() -> Result<(), PlaybackError> {
|
fn play_cursor() -> Result<(), PlaybackError> {
|
||||||
let cursor = io::Cursor::new("sql(`SELECT * FROM songs JOIN artists ON songs.artist = artists.artist_id WHERE artists.name like 'thundercat'`);");
|
let cursor = io::Cursor::<&'static str>::new("sql(`SELECT * FROM songs JOIN artists ON songs.artist = artists.artist_id WHERE artists.name like 'thundercat'`);");
|
||||||
let runner = MpsRunner::with_stream(cursor);
|
let runner = MpsRunner::with_stream(cursor);
|
||||||
let mut player = MpsPlayer::new(runner)?;
|
let mut player = MpsPlayer::new(runner)?;
|
||||||
player.play_all()
|
player.play_all()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
play_cursor().unwrap();
|
//play_cursor().unwrap();
|
||||||
|
let ctrl = MpsController::create(|| {
|
||||||
|
let cursor = io::Cursor::<&'static str>::new("sql(`SELECT * FROM songs JOIN artists ON songs.artist = artists.artist_id WHERE artists.name like 'thundercat'`);");
|
||||||
|
let runner = MpsRunner::with_stream(cursor);
|
||||||
|
MpsPlayer::new(runner).unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
ctrl.wait_for_done().unwrap();
|
||||||
|
ctrl.exit().unwrap();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue