Add some built-in and code documentation
This commit is contained in:
parent
5ef1b4a2b8
commit
e6e52ddb58
20 changed files with 329 additions and 198 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -634,7 +634,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mps"
|
name = "mps"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"mps-interpreter",
|
"mps-interpreter",
|
||||||
|
@ -643,7 +643,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mps-interpreter"
|
name = "mps-interpreter"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -654,7 +654,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mps-player"
|
name = "mps-player"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"m3u8-rs",
|
"m3u8-rs",
|
||||||
"mpris-player",
|
"mpris-player",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mps"
|
name = "mps"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||||
description = "Music Playlist Scripting language (MPS)"
|
description = "Music Playlist Scripting language (MPS)"
|
||||||
|
@ -15,7 +15,7 @@ members = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# local
|
# local
|
||||||
mps-interpreter = { version = "0.1.0", path = "./mps-interpreter" }
|
mps-interpreter = { version = "0.2.0", path = "./mps-interpreter" }
|
||||||
mps-player = { version = "0.1.0", path = "./mps-player" }
|
mps-player = { version = "0.2.0", path = "./mps-player" }
|
||||||
# external
|
# external
|
||||||
clap = { version = "3.0", features = ["derive"] }
|
clap = { version = "3.0", features = ["derive"] }
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# mps
|
# mps
|
||||||
|
|
||||||
An MPS program which plays music.
|
An MPS program which plays music.
|
||||||
This doesn't do much yet, since mps-interpreter is still under construction.
|
This project implements the interpreter (mps-interpreter), music player (mps-player), and CLI interface for MPS (root).
|
||||||
|
The CLI interface includes a REPL for running scripts.
|
||||||
Future home of a MPS REPL for playing music ergonomically through a CLI.
|
The REPL interactive mode also provides more details about using MPS through the `?help` command.
|
||||||
|
|
||||||
|
|
||||||
License: LGPL-2.1-only OR GPL-2.0-or-later
|
License: LGPL-2.1-only OR GPL-2.0-or-later
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mps-interpreter"
|
name = "mps-interpreter"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "LGPL-2.1-only OR GPL-2.0-or-later"
|
license = "LGPL-2.1-only OR GPL-2.0-or-later"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -8,6 +8,8 @@ use super::tokens::MpsToken;
|
||||||
use super::MpsContext;
|
use super::MpsContext;
|
||||||
use super::MpsMusicItem;
|
use super::MpsMusicItem;
|
||||||
|
|
||||||
|
/// The script interpreter.
|
||||||
|
/// Use MpsRunner for a better interface.
|
||||||
pub struct MpsInterpretor<T>
|
pub struct MpsInterpretor<T>
|
||||||
where
|
where
|
||||||
T: crate::tokens::MpsTokenReader,
|
T: crate::tokens::MpsTokenReader,
|
||||||
|
@ -151,6 +153,7 @@ fn box_error_with_ctx<E: MpsLanguageError + 'static>(
|
||||||
Box::new(error) as Box<dyn MpsLanguageError>
|
Box::new(error) as Box<dyn MpsLanguageError>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder function to add the standard statements of MPS.
|
||||||
pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
|
pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
|
||||||
vocabulary
|
vocabulary
|
||||||
.add(crate::lang::vocabulary::filters::empty_filter())
|
.add(crate::lang::vocabulary::filters::empty_filter())
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::VecDeque;
|
||||||
//use std::fmt::{Debug, Display, Error, Formatter};
|
//use std::fmt::{Debug, Display, Error, Formatter};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::lang::utility::{assert_token, assert_token_raw, assert_token_raw_back, assert_empty};
|
use crate::lang::utility::{assert_empty, assert_token, assert_token_raw, assert_token_raw_back};
|
||||||
use crate::lang::MpsLanguageDictionary;
|
use crate::lang::MpsLanguageDictionary;
|
||||||
use crate::lang::SyntaxError;
|
use crate::lang::SyntaxError;
|
||||||
use crate::lang::{BoxedMpsOpFactory, MpsOp};
|
use crate::lang::{BoxedMpsOpFactory, MpsOp};
|
||||||
|
|
|
@ -23,6 +23,7 @@ impl<T: MpsTokenReader> MpsRunnerSettings<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper around MpsInterpretor which provides a simpler (and more powerful) interface.
|
||||||
pub struct MpsRunner<T: MpsTokenReader> {
|
pub struct MpsRunner<T: MpsTokenReader> {
|
||||||
interpretor: MpsInterpretor<T>,
|
interpretor: MpsInterpretor<T>,
|
||||||
new_statement: bool,
|
new_statement: bool,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mps-player"
|
name = "mps-player"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "LGPL-2.1-only OR GPL-2.0-or-later"
|
license = "LGPL-2.1-only OR GPL-2.0-or-later"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -10,7 +10,7 @@ rodio = { version = "^0.14"}
|
||||||
m3u8-rs = { version = "^3.0.0" }
|
m3u8-rs = { version = "^3.0.0" }
|
||||||
|
|
||||||
# local
|
# local
|
||||||
mps-interpreter = { path = "../mps-interpreter" }
|
mps-interpreter = { path = "../mps-interpreter", version = "0.2.0" }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
#dbus = { version = "^0.9" }
|
#dbus = { version = "^0.9" }
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# mps-player
|
# mps-player
|
||||||
|
|
||||||
An MPS playback library with support for Linux media controls (D-Bus).
|
An MPS playback library with support for media controls (Linux & D-Bus only atm).
|
||||||
|
This handles the output from interpreting a script.
|
||||||
|
Music playback and m3u8 playlist generation are implemented in this part of the project.
|
||||||
|
|
||||||
|
|
||||||
License: LGPL-2.1-only OR GPL-2.0-or-later
|
License: LGPL-2.1-only OR GPL-2.0-or-later
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
use std::sync::mpsc::{Sender, Receiver, channel};
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
use mps_interpreter::tokens::MpsTokenReader;
|
use mps_interpreter::tokens::MpsTokenReader;
|
||||||
|
|
||||||
|
use super::os_controls::SystemControlWrapper;
|
||||||
|
use super::player_wrapper::{ControlAction, MpsPlayerServer, PlayerAction};
|
||||||
use super::MpsPlayer;
|
use super::MpsPlayer;
|
||||||
use super::PlaybackError;
|
use super::PlaybackError;
|
||||||
use super::player_wrapper::{ControlAction, PlayerAction, MpsPlayerServer};
|
|
||||||
use super::os_controls::SystemControlWrapper;
|
|
||||||
|
|
||||||
|
/// A controller for a MpsPlayer running on another thread.
|
||||||
|
/// This receives and sends events like media buttons and script errors for the MpsPlayer.
|
||||||
pub struct MpsController {
|
pub struct MpsController {
|
||||||
control: Sender<ControlAction>,
|
control: Sender<ControlAction>,
|
||||||
event: Receiver<PlayerAction>,
|
event: Receiver<PlayerAction>,
|
||||||
handle: JoinHandle<()>,
|
handle: JoinHandle<()>,
|
||||||
sys_ctrl: SystemControlWrapper
|
sys_ctrl: SystemControlWrapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MpsController {
|
impl MpsController {
|
||||||
|
@ -23,7 +25,8 @@ impl MpsController {
|
||||||
let (event_tx, event_rx) = channel();
|
let (event_tx, event_rx) = channel();
|
||||||
let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone());
|
let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone());
|
||||||
sys_ctrl.init();
|
sys_ctrl.init();
|
||||||
let handle = MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, false);
|
let handle =
|
||||||
|
MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, false);
|
||||||
Self {
|
Self {
|
||||||
control: control_tx,
|
control: control_tx,
|
||||||
event: event_rx,
|
event: event_rx,
|
||||||
|
@ -39,7 +42,8 @@ impl MpsController {
|
||||||
let (event_tx, event_rx) = channel();
|
let (event_tx, event_rx) = channel();
|
||||||
let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone());
|
let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone());
|
||||||
sys_ctrl.init();
|
sys_ctrl.init();
|
||||||
let handle = MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, true);
|
let handle =
|
||||||
|
MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, true);
|
||||||
Self {
|
Self {
|
||||||
control: control_tx,
|
control: control_tx,
|
||||||
event: event_rx,
|
event: event_rx,
|
||||||
|
@ -49,7 +53,9 @@ impl MpsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_confirm(&self, to_send: ControlAction) -> Result<(), PlaybackError> {
|
fn send_confirm(&self, to_send: ControlAction) -> Result<(), PlaybackError> {
|
||||||
self.control.send(to_send.clone()).map_err(PlaybackError::from_err)?;
|
self.control
|
||||||
|
.send(to_send.clone())
|
||||||
|
.map_err(PlaybackError::from_err)?;
|
||||||
let mut response = self.event.recv().map_err(PlaybackError::from_err)?;
|
let mut response = self.event.recv().map_err(PlaybackError::from_err)?;
|
||||||
while !response.is_acknowledgement() {
|
while !response.is_acknowledgement() {
|
||||||
Self::handle_event(response)?;
|
Self::handle_event(response)?;
|
||||||
|
@ -60,12 +66,13 @@ impl MpsController {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(PlaybackError {
|
Err(PlaybackError {
|
||||||
msg: "Incorrect acknowledgement received for MpsController control action".into()
|
msg: "Incorrect acknowledgement received for MpsController control action"
|
||||||
|
.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(PlaybackError {
|
Err(PlaybackError {
|
||||||
msg: "Invalid acknowledgement received for MpsController control action".into()
|
msg: "Invalid acknowledgement received for MpsController control action".into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,41 +87,44 @@ impl MpsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&self) -> Result<(), PlaybackError> {
|
pub fn next(&self) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::Next{ack: true})
|
self.send_confirm(ControlAction::Next { ack: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous(&self) -> Result<(), PlaybackError> {
|
pub fn previous(&self) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::Previous{ack: true})
|
self.send_confirm(ControlAction::Previous { ack: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&self) -> Result<(), PlaybackError> {
|
pub fn play(&self) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::Play{ack: true})
|
self.send_confirm(ControlAction::Play { ack: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pause(&self) -> Result<(), PlaybackError> {
|
pub fn pause(&self) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::Pause{ack: true})
|
self.send_confirm(ControlAction::Pause { ack: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&self) -> Result<(), PlaybackError> {
|
pub fn stop(&self) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::Stop{ack: true})
|
self.send_confirm(ControlAction::Stop { ack: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enqueue(&self, count: usize) -> Result<(), PlaybackError> {
|
pub fn enqueue(&self, count: usize) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::Enqueue{amount: count, ack: true})
|
self.send_confirm(ControlAction::Enqueue {
|
||||||
|
amount: count,
|
||||||
|
ack: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ping(&self) -> Result<(), PlaybackError> {
|
pub fn ping(&self) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::NoOp{ack: true})
|
self.send_confirm(ControlAction::NoOp { ack: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(self) -> Result<(), PlaybackError> {
|
pub fn exit(self) -> Result<(), PlaybackError> {
|
||||||
self.send_confirm(ControlAction::Exit{ack: true})?;
|
self.send_confirm(ControlAction::Exit { ack: true })?;
|
||||||
self.sys_ctrl.exit();
|
self.sys_ctrl.exit();
|
||||||
match self.handle.join() {
|
match self.handle.join() {
|
||||||
Ok(x) => Ok(x),
|
Ok(x) => Ok(x),
|
||||||
Err(_) => Err(PlaybackError {
|
Err(_) => Err(PlaybackError {
|
||||||
msg: "MpsPlayerServer did not exit correctly".into()
|
msg: "MpsPlayerServer did not exit correctly".into(),
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +144,9 @@ impl MpsController {
|
||||||
for msg in self.event.try_iter() {
|
for msg in self.event.try_iter() {
|
||||||
Self::handle_event(msg)?;
|
Self::handle_event(msg)?;
|
||||||
}
|
}
|
||||||
self.control.send(ControlAction::CheckEmpty{ack: true}).map_err(PlaybackError::from_err)?;
|
self.control
|
||||||
|
.send(ControlAction::CheckEmpty { ack: true })
|
||||||
|
.map_err(PlaybackError::from_err)?;
|
||||||
loop {
|
loop {
|
||||||
let msg = self.event.recv().map_err(PlaybackError::from_err)?;
|
let msg = self.event.recv().map_err(PlaybackError::from_err)?;
|
||||||
if let PlayerAction::Empty = msg {
|
if let PlayerAction::Empty = msg {
|
||||||
|
@ -161,8 +173,12 @@ impl MpsController {
|
||||||
/// Like check(), but it also waits for an acknowledgement to ensure it gets the latest events.
|
/// Like check(), but it also waits for an acknowledgement to ensure it gets the latest events.
|
||||||
pub fn check_ack(&self) -> Vec<PlaybackError> {
|
pub fn check_ack(&self) -> Vec<PlaybackError> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
let to_send = ControlAction::NoOp{ack: true};
|
let to_send = ControlAction::NoOp { ack: true };
|
||||||
if let Err(e) = self.control.send(to_send.clone()).map_err(PlaybackError::from_err) {
|
if let Err(e) = self
|
||||||
|
.control
|
||||||
|
.send(to_send.clone())
|
||||||
|
.map_err(PlaybackError::from_err)
|
||||||
|
{
|
||||||
result.push(e);
|
result.push(e);
|
||||||
}
|
}
|
||||||
for msg in self.event.iter() {
|
for msg in self.event.iter() {
|
||||||
|
@ -171,7 +187,8 @@ impl MpsController {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
result.push(PlaybackError {
|
result.push(PlaybackError {
|
||||||
msg: "Incorrect acknowledgement received for MpsController control action".into()
|
msg: "Incorrect acknowledgement received for MpsController control action"
|
||||||
|
.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if let Err(e) = Self::handle_event(msg) {
|
} else if let Err(e) = Self::handle_event(msg) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::fmt::{Debug, Display, Formatter, Error};
|
use std::fmt::{Debug, Display, Error, Formatter};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PlaybackError {
|
pub struct PlaybackError {
|
||||||
pub(crate) msg: String
|
pub(crate) msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaybackError {
|
impl PlaybackError {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
//! An MPS playback library with support for Linux media controls (D-Bus).
|
//! An MPS playback library with support for media controls (Linux & D-Bus only atm).
|
||||||
|
//! This handles the output from interpreting a script.
|
||||||
|
//! Music playback and m3u8 playlist generation are implemented in this part of the project.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
mod controller;
|
mod controller;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::sync::mpsc::{Sender, channel};
|
use std::sync::mpsc::{channel, Sender};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
@ -9,10 +9,12 @@ use mpris_player::{MprisPlayer, PlaybackStatus};
|
||||||
//use super::MpsController;
|
//use super::MpsController;
|
||||||
use super::player_wrapper::ControlAction;
|
use super::player_wrapper::ControlAction;
|
||||||
|
|
||||||
|
/// OS-specific APIs for media controls.
|
||||||
|
/// Currently only Linux (dbus) is supported.
|
||||||
pub struct SystemControlWrapper {
|
pub struct SystemControlWrapper {
|
||||||
control: Sender<ControlAction>,
|
control: Sender<ControlAction>,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
dbus_handle: Option<JoinHandle<()>>,//std::sync::Arc<MprisPlayer>,
|
dbus_handle: Option<JoinHandle<()>>, //std::sync::Arc<MprisPlayer>,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
dbus_die: Option<Sender<()>>,
|
dbus_die: Option<Sender<()>>,
|
||||||
}
|
}
|
||||||
|
@ -22,7 +24,7 @@ impl SystemControlWrapper {
|
||||||
pub fn new(control: Sender<ControlAction>) -> Self {
|
pub fn new(control: Sender<ControlAction>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
control: control,
|
control: control,
|
||||||
dbus_handle: None,//MprisPlayer::new("mps".into(), "mps".into(), "null".into())
|
dbus_handle: None, //MprisPlayer::new("mps".into(), "mps".into(), "null".into())
|
||||||
dbus_die: None,
|
dbus_die: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,58 +46,67 @@ impl SystemControlWrapper {
|
||||||
dbus_conn.set_can_go_next(true);
|
dbus_conn.set_can_go_next(true);
|
||||||
|
|
||||||
let control_clone = control_clone1.clone();
|
let control_clone = control_clone1.clone();
|
||||||
dbus_conn.connect_next(
|
dbus_conn.connect_next(move || {
|
||||||
move || {
|
//println!("Got next signal");
|
||||||
//println!("Got next signal");
|
control_clone
|
||||||
control_clone.send(ControlAction::Next{ack: false}).unwrap_or(())
|
.send(ControlAction::Next { ack: false })
|
||||||
}
|
.unwrap_or(())
|
||||||
);
|
});
|
||||||
|
|
||||||
let control_clone = control_clone1.clone();
|
let control_clone = control_clone1.clone();
|
||||||
dbus_conn.connect_previous(
|
dbus_conn.connect_previous(move || {
|
||||||
move || control_clone.send(ControlAction::Previous{ack: false}).unwrap_or(())
|
control_clone
|
||||||
);
|
.send(ControlAction::Previous { ack: false })
|
||||||
|
.unwrap_or(())
|
||||||
|
});
|
||||||
|
|
||||||
let control_clone = control_clone1.clone();
|
let control_clone = control_clone1.clone();
|
||||||
let dbus_conn_clone = dbus_conn.clone();
|
let dbus_conn_clone = dbus_conn.clone();
|
||||||
dbus_conn.connect_pause(
|
dbus_conn.connect_pause(move || {
|
||||||
move || {
|
//println!("Got pause signal");
|
||||||
//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);
|
dbus_conn_clone.set_playback_status(PlaybackStatus::Paused);
|
||||||
control_clone.send(ControlAction::Pause{ack: false}).unwrap_or(());
|
control_clone
|
||||||
}
|
.send(ControlAction::Pause { ack: false })
|
||||||
);
|
.unwrap_or(());
|
||||||
|
} else {
|
||||||
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);
|
dbus_conn_clone.set_playback_status(PlaybackStatus::Playing);
|
||||||
control_clone.send(ControlAction::Play{ack: false}).unwrap_or(())
|
control_clone
|
||||||
|
.send(ControlAction::Play { ack: false })
|
||||||
|
.unwrap_or(());
|
||||||
}
|
}
|
||||||
);
|
is_playing = !is_playing;
|
||||||
|
});
|
||||||
|
|
||||||
let control_clone = control_clone1.clone();
|
let control_clone = control_clone1.clone();
|
||||||
let dbus_conn_clone = dbus_conn.clone();
|
dbus_conn.connect_volume(move |v| {
|
||||||
dbus_conn.connect_play_pause(
|
control_clone
|
||||||
move || {
|
.send(ControlAction::SetVolume {
|
||||||
//println!("Got play_pause signal (was playing? {})", is_playing);
|
ack: false,
|
||||||
if is_playing {
|
volume: (v * (u32::MAX as f64)) as _,
|
||||||
dbus_conn_clone.set_playback_status(PlaybackStatus::Paused);
|
})
|
||||||
control_clone.send(ControlAction::Pause{ack: false}).unwrap_or(());
|
.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
|
// poll loop, using my custom mpris lib because original did it wrong
|
||||||
loop {
|
loop {
|
||||||
|
@ -120,9 +131,7 @@ impl SystemControlWrapper {
|
||||||
#[cfg(not(any(target_os = "linux")))]
|
#[cfg(not(any(target_os = "linux")))]
|
||||||
impl SystemControlWrapper {
|
impl SystemControlWrapper {
|
||||||
pub fn new(control: Sender<ControlAction>) -> Self {
|
pub fn new(control: Sender<ControlAction>) -> Self {
|
||||||
Self {
|
Self { control: control }
|
||||||
control: control,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self) {}
|
pub fn init(&mut self) {}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use std::io;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use rodio::{decoder::Decoder, OutputStream, Sink, OutputStreamHandle};
|
use rodio::{decoder::Decoder, OutputStream, OutputStreamHandle, Sink};
|
||||||
|
|
||||||
use m3u8_rs::{MediaPlaylist, MediaSegment};
|
use m3u8_rs::{MediaPlaylist, MediaSegment};
|
||||||
|
|
||||||
use mps_interpreter::{MpsRunner, tokens::MpsTokenReader};
|
use mps_interpreter::{tokens::MpsTokenReader, MpsRunner};
|
||||||
|
|
||||||
use super::PlaybackError;
|
use super::PlaybackError;
|
||||||
|
|
||||||
|
/// Playback functionality for a script.
|
||||||
|
/// This takes the output of the runner and plays or saves it.
|
||||||
pub struct MpsPlayer<T: MpsTokenReader> {
|
pub struct MpsPlayer<T: MpsTokenReader> {
|
||||||
runner: MpsRunner<T>,
|
runner: MpsRunner<T>,
|
||||||
sink: Sink,
|
sink: Sink,
|
||||||
|
@ -19,12 +21,13 @@ pub struct MpsPlayer<T: MpsTokenReader> {
|
||||||
|
|
||||||
impl<T: MpsTokenReader> MpsPlayer<T> {
|
impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
pub fn new(runner: MpsRunner<T>) -> Result<Self, PlaybackError> {
|
pub fn new(runner: MpsRunner<T>) -> Result<Self, PlaybackError> {
|
||||||
let (stream, output_handle) = OutputStream::try_default().map_err(PlaybackError::from_err)?;
|
let (stream, output_handle) =
|
||||||
Ok(Self{
|
OutputStream::try_default().map_err(PlaybackError::from_err)?;
|
||||||
|
Ok(Self {
|
||||||
runner: runner,
|
runner: runner,
|
||||||
sink: Sink::try_new(&output_handle).map_err(PlaybackError::from_err)?,
|
sink: Sink::try_new(&output_handle).map_err(PlaybackError::from_err)?,
|
||||||
output_stream: stream,
|
output_stream: stream,
|
||||||
output_handle: output_handle
|
output_handle: output_handle,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +42,8 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
self.sink.append(source);
|
self.sink.append(source);
|
||||||
//self.sink.play(); // idk if this is necessary
|
//self.sink.play(); // idk if this is necessary
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
Err(e) => Err(PlaybackError::from_err(e))
|
Err(e) => Err(PlaybackError::from_err(e)),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
self.sink.sleep_until_end();
|
self.sink.sleep_until_end();
|
||||||
|
@ -57,8 +60,8 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
self.sink.append(source);
|
self.sink.append(source);
|
||||||
//self.sink.play(); // idk if this is necessary
|
//self.sink.play(); // idk if this is necessary
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
Err(e) => Err(PlaybackError::from_err(e))
|
Err(e) => Err(PlaybackError::from_err(e)),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -66,7 +69,9 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
|
|
||||||
pub fn enqueue(&mut self, count: usize) -> Result<(), PlaybackError> {
|
pub fn enqueue(&mut self, count: usize) -> Result<(), PlaybackError> {
|
||||||
let mut items_left = count;
|
let mut items_left = count;
|
||||||
if items_left == 0 { return Ok(()); }
|
if items_left == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
for item in &mut self.runner {
|
for item in &mut self.runner {
|
||||||
match item {
|
match item {
|
||||||
Ok(music) => {
|
Ok(music) => {
|
||||||
|
@ -77,11 +82,13 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
self.sink.append(source);
|
self.sink.append(source);
|
||||||
//self.sink.play(); // idk if this is necessary
|
//self.sink.play(); // idk if this is necessary
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
Err(e) => Err(PlaybackError::from_err(e))
|
Err(e) => Err(PlaybackError::from_err(e)),
|
||||||
}?;
|
}?;
|
||||||
items_left -= 1;
|
items_left -= 1;
|
||||||
if items_left == 0 { break; }
|
if items_left == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//println!("Enqueued {} items", count - items_left);
|
//println!("Enqueued {} items", count - items_left);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -120,16 +127,14 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
for item in &mut self.runner {
|
for item in &mut self.runner {
|
||||||
match item {
|
match item {
|
||||||
Ok(music) => {
|
Ok(music) => {
|
||||||
playlist.segments.push(
|
playlist.segments.push(MediaSegment {
|
||||||
MediaSegment {
|
uri: music.filename,
|
||||||
uri: music.filename,
|
title: Some(music.title),
|
||||||
title: Some(music.title),
|
..Default::default()
|
||||||
..Default::default()
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
Err(e) => Err(PlaybackError::from_err(e))
|
Err(e) => Err(PlaybackError::from_err(e)),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
playlist.write_to(w).map_err(PlaybackError::from_err)
|
playlist.write_to(w).map_err(PlaybackError::from_err)
|
||||||
|
@ -160,9 +165,9 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::io;
|
|
||||||
use mps_interpreter::MpsRunner;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use mps_interpreter::MpsRunner;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::sync::mpsc::{Sender, Receiver};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use std::{thread, thread::JoinHandle};
|
use std::{thread, thread::JoinHandle};
|
||||||
|
|
||||||
use mps_interpreter::tokens::MpsTokenReader;
|
use mps_interpreter::tokens::MpsTokenReader;
|
||||||
|
@ -6,6 +6,10 @@ use mps_interpreter::tokens::MpsTokenReader;
|
||||||
use super::MpsPlayer;
|
use super::MpsPlayer;
|
||||||
use super::PlaybackError;
|
use super::PlaybackError;
|
||||||
|
|
||||||
|
/// A wrapper around MpsPlayer so that playback can occur on a different thread.
|
||||||
|
/// This allows for message passing between the threads.
|
||||||
|
///
|
||||||
|
/// You will probably never directly interact with this, instead using MpsController to communicate.
|
||||||
pub struct MpsPlayerServer<T: MpsTokenReader> {
|
pub struct MpsPlayerServer<T: MpsTokenReader> {
|
||||||
player: MpsPlayer<T>,
|
player: MpsPlayer<T>,
|
||||||
control: Receiver<ControlAction>,
|
control: Receiver<ControlAction>,
|
||||||
|
@ -14,7 +18,12 @@ pub struct MpsPlayerServer<T: MpsTokenReader> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
pub fn new(player: MpsPlayer<T>, ctrl: Receiver<ControlAction>, event: Sender<PlayerAction>, keep_alive: bool) -> Self {
|
pub fn new(
|
||||||
|
player: MpsPlayer<T>,
|
||||||
|
ctrl: Receiver<ControlAction>,
|
||||||
|
event: Sender<PlayerAction>,
|
||||||
|
keep_alive: bool,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
player: player,
|
player: player,
|
||||||
control: ctrl,
|
control: ctrl,
|
||||||
|
@ -39,7 +48,7 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
|
|
||||||
// process command
|
// process command
|
||||||
match command {
|
match command {
|
||||||
ControlAction::Next{..} => {
|
ControlAction::Next { .. } => {
|
||||||
//println!("Executing next command (queue_len: {})", self.player.queue_len());
|
//println!("Executing next command (queue_len: {})", self.player.queue_len());
|
||||||
if let Err(e) = self.player.new_sink() {
|
if let Err(e) = self.player.new_sink() {
|
||||||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
|
@ -47,32 +56,32 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
if !self.player.is_paused() {
|
if !self.player.is_paused() {
|
||||||
self.player.enqueue(1).unwrap();
|
self.player.enqueue(1).unwrap();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ControlAction::Previous{..} => {}, // TODO
|
ControlAction::Previous { .. } => {} // TODO
|
||||||
ControlAction::Play{..} => self.player.resume(),
|
ControlAction::Play { .. } => self.player.resume(),
|
||||||
ControlAction::Pause{..} => self.player.pause(),
|
ControlAction::Pause { .. } => self.player.pause(),
|
||||||
ControlAction::PlayPause{..} => {
|
ControlAction::PlayPause { .. } => {
|
||||||
if self.player.is_paused() {
|
if self.player.is_paused() {
|
||||||
self.player.resume();
|
self.player.resume();
|
||||||
} else {
|
} else {
|
||||||
self.player.pause();
|
self.player.pause();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ControlAction::Stop{..} => self.player.stop(),
|
ControlAction::Stop { .. } => self.player.stop(),
|
||||||
ControlAction::Exit{..} => {
|
ControlAction::Exit { .. } => {
|
||||||
self.player.stop();
|
self.player.stop();
|
||||||
is_exiting = true;
|
is_exiting = true;
|
||||||
},
|
}
|
||||||
ControlAction::Enqueue{amount,..} => {
|
ControlAction::Enqueue { amount, .. } => {
|
||||||
if let Err(e) = self.player.enqueue(amount) {
|
if let Err(e) = self.player.enqueue(amount) {
|
||||||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ControlAction::NoOp{..} => {}, // empty by design
|
ControlAction::NoOp { .. } => {} // empty by design
|
||||||
ControlAction::SetVolume{volume,..} => {
|
ControlAction::SetVolume { volume, .. } => {
|
||||||
self.player.set_volume((volume as f32) / (u32::MAX as f32));
|
self.player.set_volume((volume as f32) / (u32::MAX as f32));
|
||||||
},
|
}
|
||||||
ControlAction::CheckEmpty{..} => {
|
ControlAction::CheckEmpty { .. } => {
|
||||||
check_empty = true;
|
check_empty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +91,8 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
if let Err(e) = self.player.enqueue(1) {
|
if let Err(e) = self.player.enqueue(1) {
|
||||||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
}
|
}
|
||||||
if self.player.queue_len() == 0 { // no more music to add
|
if self.player.queue_len() == 0 {
|
||||||
|
// no more music to add
|
||||||
is_exiting = !self.keep_alive || is_exiting;
|
is_exiting = !self.keep_alive || is_exiting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,10 +102,12 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// always check for empty state change
|
// always check for empty state change
|
||||||
if self.player.queue_len() == 0 && !is_empty { // just became empty
|
if self.player.queue_len() == 0 && !is_empty {
|
||||||
|
// just became empty
|
||||||
is_empty = true;
|
is_empty = true;
|
||||||
self.event.send(PlayerAction::Empty).unwrap();
|
self.event.send(PlayerAction::Empty).unwrap();
|
||||||
} else if self.player.queue_len() != 0 && is_empty { // just got filled
|
} else if self.player.queue_len() != 0 && is_empty {
|
||||||
|
// just got filled
|
||||||
is_empty = false;
|
is_empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +115,9 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
self.event.send(PlayerAction::Empty).unwrap();
|
self.event.send(PlayerAction::Empty).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_exiting { break; }
|
if is_exiting {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
println!("Exiting playback server");
|
println!("Exiting playback server");
|
||||||
self.event.send(PlayerAction::End).unwrap();
|
self.event.send(PlayerAction::End).unwrap();
|
||||||
|
@ -114,7 +128,7 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
ctrl_tx: Sender<ControlAction>,
|
ctrl_tx: Sender<ControlAction>,
|
||||||
ctrl_rx: Receiver<ControlAction>,
|
ctrl_rx: Receiver<ControlAction>,
|
||||||
event: Sender<PlayerAction>,
|
event: Sender<PlayerAction>,
|
||||||
keep_alive: bool
|
keep_alive: bool,
|
||||||
) -> JoinHandle<()> {
|
) -> JoinHandle<()> {
|
||||||
thread::spawn(move || Self::unblocking_timer_loop(ctrl_tx, 50));
|
thread::spawn(move || Self::unblocking_timer_loop(ctrl_tx, 50));
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
@ -127,7 +141,7 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
pub fn unblocking_timer_loop(ctrl_tx: Sender<ControlAction>, sleep_ms: u64) {
|
pub fn unblocking_timer_loop(ctrl_tx: Sender<ControlAction>, sleep_ms: u64) {
|
||||||
let dur = std::time::Duration::from_millis(sleep_ms);
|
let dur = std::time::Duration::from_millis(sleep_ms);
|
||||||
loop {
|
loop {
|
||||||
if let Err(_) = ctrl_tx.send(ControlAction::NoOp{ack: false}) {
|
if let Err(_) = ctrl_tx.send(ControlAction::NoOp { ack: false }) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
thread::sleep(dur);
|
thread::sleep(dur);
|
||||||
|
@ -139,33 +153,33 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum ControlAction {
|
pub enum ControlAction {
|
||||||
Next{ack: bool},
|
Next { ack: bool },
|
||||||
Previous{ack: bool},
|
Previous { ack: bool },
|
||||||
Play{ack: bool},
|
Play { ack: bool },
|
||||||
Pause{ack: bool},
|
Pause { ack: bool },
|
||||||
PlayPause{ack: bool},
|
PlayPause { ack: bool },
|
||||||
Stop{ack: bool},
|
Stop { ack: bool },
|
||||||
Exit{ack: bool},
|
Exit { ack: bool },
|
||||||
Enqueue {amount: usize, ack: bool},
|
Enqueue { amount: usize, ack: bool },
|
||||||
NoOp{ack: bool},
|
NoOp { ack: bool },
|
||||||
SetVolume{ack: bool, volume: u32},
|
SetVolume { ack: bool, volume: u32 },
|
||||||
CheckEmpty{ack: bool},
|
CheckEmpty { ack: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlAction {
|
impl ControlAction {
|
||||||
fn needs_ack(&self) -> bool {
|
fn needs_ack(&self) -> bool {
|
||||||
*match self {
|
*match self {
|
||||||
Self::Next{ack} => ack,
|
Self::Next { ack } => ack,
|
||||||
Self::Previous{ack} => ack,
|
Self::Previous { ack } => ack,
|
||||||
Self::Play{ack} => ack,
|
Self::Play { ack } => ack,
|
||||||
Self::Pause{ack} => ack,
|
Self::Pause { ack } => ack,
|
||||||
Self::PlayPause{ack} => ack,
|
Self::PlayPause { ack } => ack,
|
||||||
Self::Stop{ack} => ack,
|
Self::Stop { ack } => ack,
|
||||||
Self::Exit{ack} => ack,
|
Self::Exit { ack } => ack,
|
||||||
Self::Enqueue{ack,..} => ack,
|
Self::Enqueue { ack, .. } => ack,
|
||||||
Self::NoOp{ack,..} => ack,
|
Self::NoOp { ack, .. } => ack,
|
||||||
Self::SetVolume{ack,..} => ack,
|
Self::SetVolume { ack, .. } => ack,
|
||||||
Self::CheckEmpty{ack} => ack,
|
Self::CheckEmpty { ack } => ack,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +197,7 @@ impl PlayerAction {
|
||||||
pub fn is_acknowledgement(&self) -> bool {
|
pub fn is_acknowledgement(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Acknowledge(_) => true,
|
Self::Acknowledge(_) => true,
|
||||||
_ => false
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
use std::io::{self, Read, Write};
|
||||||
use std::io::{Read, Write, self};
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
|
|
||||||
pub struct ChannelWriter {
|
pub struct ChannelWriter {
|
||||||
tx: Sender<u8>,
|
tx: Sender<u8>,
|
||||||
|
@ -29,34 +29,36 @@ pub struct ChannelReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for ChannelReader {
|
impl Read for ChannelReader {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
if self.blocking {
|
if self.blocking {
|
||||||
for b in self.rx.iter() {
|
for b in self.rx.iter() {
|
||||||
buf[count] = b;
|
buf[count] = b;
|
||||||
count += 1;
|
count += 1;
|
||||||
if count >= buf.len() {break;}
|
if count >= buf.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for b in self.rx.try_iter() {
|
for b in self.rx.try_iter() {
|
||||||
buf[count] = b;
|
buf[count] = b;
|
||||||
count += 1;
|
count += 1;
|
||||||
if count >= buf.len() {break;}
|
if count >= buf.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_io() -> (ChannelWriter, ChannelReader) {
|
pub fn channel_io() -> (ChannelWriter, ChannelReader) {
|
||||||
let (sender, receiver) = channel();
|
let (sender, receiver) = channel();
|
||||||
(
|
(
|
||||||
ChannelWriter {
|
ChannelWriter { tx: sender },
|
||||||
tx: sender,
|
|
||||||
},
|
|
||||||
ChannelReader {
|
ChannelReader {
|
||||||
rx: receiver,
|
rx: receiver,
|
||||||
blocking: false,
|
blocking: false,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use clap::{Parser};
|
use clap::Parser;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(author, version)]
|
#[clap(author, version)]
|
||||||
|
@ -17,7 +17,7 @@ pub struct CliArgs {
|
||||||
|
|
||||||
/// In REPL mode, the prompt to display
|
/// In REPL mode, the prompt to display
|
||||||
#[clap(long, default_value_t = String::from("|"))]
|
#[clap(long, default_value_t = String::from("|"))]
|
||||||
pub prompt: String
|
pub prompt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse() -> CliArgs {
|
pub fn parse() -> CliArgs {
|
||||||
|
|
50
src/help.rs
Normal file
50
src/help.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/// Standard help string containing usage information for MPS.
|
||||||
|
pub fn help() -> String {
|
||||||
|
"This language is all about iteration. Almost everything is an iterator or operates on iterators. By default, any operation which is not an assignment will cause the script runner to handle (play/save) the items which that statement contains.
|
||||||
|
|
||||||
|
To view the currently-supported operations, try ?functions and ?filters".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn functions() -> String {
|
||||||
|
"FUNCTIONS (?functions)
|
||||||
|
Similar to most other languages: function_name(param1, param2, etc.)
|
||||||
|
|
||||||
|
sql_init(generate = true|false, folder = `path/to/music`)
|
||||||
|
Initialize the SQLite database connection using the provided parameters. This must be performed before any other database operation (otherwise the database will be connected with default settings).
|
||||||
|
|
||||||
|
sql(`SQL query here`)
|
||||||
|
Perform a raw SQLite query on the database which MPS auto-generates. An iterator of the results is returned.
|
||||||
|
|
||||||
|
song(`something`)
|
||||||
|
Retrieve all songs in the database with a title like something.
|
||||||
|
|
||||||
|
album(`something`)
|
||||||
|
Retrieve all songs in the database with an album title like something.
|
||||||
|
|
||||||
|
artist(`something`)
|
||||||
|
Retrieve all songs in the database with an artist name like something.
|
||||||
|
|
||||||
|
genre(`something`)
|
||||||
|
Retrieve all songs in the database with a genre title like something.
|
||||||
|
|
||||||
|
repeat(iterable, count)
|
||||||
|
Repeat the iterable count times, or infinite times if count is omitted.
|
||||||
|
|
||||||
|
files(folder = `path/to/music`, recursive = true|false, regex = `pattern`)
|
||||||
|
Retrieve all files from a folder, matching a regex pattern.".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filters() -> String {
|
||||||
|
"FILTERS (?filters)
|
||||||
|
Operations to reduce the items in an iterable: iterable.(filter)
|
||||||
|
|
||||||
|
field == something
|
||||||
|
field >= something
|
||||||
|
field > something
|
||||||
|
field <= something
|
||||||
|
field < something
|
||||||
|
Compare all items, keeping only those that match the condition.
|
||||||
|
|
||||||
|
[empty]
|
||||||
|
Matches all items".to_string()
|
||||||
|
}
|
24
src/main.rs
24
src/main.rs
|
@ -1,18 +1,19 @@
|
||||||
//! An MPS program which plays music.
|
//! An MPS program which plays music.
|
||||||
//! This doesn't do much yet, since mps-interpreter is still under construction.
|
//! This project implements the interpreter (mps-interpreter), music player (mps-player), and CLI interface for MPS (root).
|
||||||
//!
|
//! The CLI interface includes a REPL for running scripts.
|
||||||
//! Future home of a MPS REPL for playing music ergonomically through a CLI.
|
//! The REPL interactive mode also provides more details about using MPS through the `?help` command.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
mod channel_io;
|
mod channel_io;
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod help;
|
||||||
mod repl;
|
mod repl;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use mps_interpreter::MpsRunner;
|
use mps_interpreter::MpsRunner;
|
||||||
use mps_player::{MpsPlayer, PlaybackError, MpsController};
|
use mps_player::{MpsController, MpsPlayer, PlaybackError};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn play_cursor() -> Result<(), PlaybackError> {
|
fn play_cursor() -> Result<(), PlaybackError> {
|
||||||
|
@ -36,7 +37,7 @@ fn main() {
|
||||||
let player_builder = move || {
|
let player_builder = move || {
|
||||||
let script_reader = io::BufReader::new(
|
let script_reader = io::BufReader::new(
|
||||||
std::fs::File::open(&script_file2)
|
std::fs::File::open(&script_file2)
|
||||||
.expect(&format!("Abort: Cannot open file `{}`", &script_file2))
|
.expect(&format!("Abort: Cannot open file `{}`", &script_file2)),
|
||||||
);
|
);
|
||||||
let runner = MpsRunner::with_stream(script_reader);
|
let runner = MpsRunner::with_stream(script_reader);
|
||||||
let player = MpsPlayer::new(runner).unwrap();
|
let player = MpsPlayer::new(runner).unwrap();
|
||||||
|
@ -45,12 +46,14 @@ fn main() {
|
||||||
if let Some(playlist_file) = &args.playlist {
|
if let Some(playlist_file) = &args.playlist {
|
||||||
// generate playlist
|
// generate playlist
|
||||||
let mut player = player_builder();
|
let mut player = player_builder();
|
||||||
let mut writer = io::BufWriter::new(
|
let mut writer = io::BufWriter::new(std::fs::File::create(playlist_file).expect(
|
||||||
std::fs::File::create(playlist_file)
|
&format!("Abort: Cannot create writeable file `{}`", playlist_file),
|
||||||
.expect(&format!("Abort: Cannot create writeable file `{}`", playlist_file))
|
));
|
||||||
);
|
|
||||||
match player.save_m3u8(&mut writer) {
|
match player.save_m3u8(&mut writer) {
|
||||||
Ok(_) => println!("Succes: Finished playlist `{}` from script `{}`", playlist_file, script_file),
|
Ok(_) => println!(
|
||||||
|
"Succes: Finished playlist `{}` from script `{}`",
|
||||||
|
playlist_file, script_file
|
||||||
|
),
|
||||||
Err(e) => eprintln!("{}", e),
|
Err(e) => eprintln!("{}", e),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,6 +67,7 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
// start REPL
|
// start REPL
|
||||||
println!("Welcome to MPS interactive mode!");
|
println!("Welcome to MPS interactive mode!");
|
||||||
|
println!("Run ?help for usage instructions.");
|
||||||
println!("End a statement with ; to execute it.");
|
println!("End a statement with ; to execute it.");
|
||||||
repl::repl(args)
|
repl::repl(args)
|
||||||
}
|
}
|
||||||
|
|
54
src/repl.rs
54
src/repl.rs
|
@ -1,12 +1,12 @@
|
||||||
//! Read, Execute, Print Loop functionality
|
//! Read, Execute, Print Loop functionality
|
||||||
|
|
||||||
use std::io::{self, Write, Read, Stdin};
|
use std::io::{self, Read, Stdin, Write};
|
||||||
|
|
||||||
use mps_interpreter::MpsRunner;
|
use mps_interpreter::MpsRunner;
|
||||||
use mps_player::{MpsPlayer, MpsController};
|
use mps_player::{MpsController, MpsPlayer};
|
||||||
|
|
||||||
use super::cli::CliArgs;
|
|
||||||
use super::channel_io::{channel_io, ChannelWriter};
|
use super::channel_io::{channel_io, ChannelWriter};
|
||||||
|
use super::cli::CliArgs;
|
||||||
|
|
||||||
struct ReplState {
|
struct ReplState {
|
||||||
stdin: Stdin,
|
stdin: Stdin,
|
||||||
|
@ -37,16 +37,17 @@ pub fn repl(args: CliArgs) {
|
||||||
if let Some(playlist_file) = &args.playlist {
|
if let Some(playlist_file) = &args.playlist {
|
||||||
println!("Playlist mode (output: `{}`)", playlist_file);
|
println!("Playlist mode (output: `{}`)", playlist_file);
|
||||||
let mut player = player_builder();
|
let mut player = player_builder();
|
||||||
let mut playlist_writer = io::BufWriter::new(
|
let mut playlist_writer = io::BufWriter::new(std::fs::File::create(playlist_file).expect(
|
||||||
std::fs::File::create(playlist_file)
|
&format!("Abort: Cannot create writeable file `{}`", playlist_file),
|
||||||
.expect(&format!("Abort: Cannot create writeable file `{}`", playlist_file))
|
));
|
||||||
);
|
|
||||||
read_loop(&args, &mut state, || {
|
read_loop(&args, &mut state, || {
|
||||||
match player.save_m3u8(&mut playlist_writer) {
|
match player.save_m3u8(&mut playlist_writer) {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(e) => eprintln!("{}", e.message()),
|
Err(e) => eprintln!("{}", e.message()),
|
||||||
}
|
}
|
||||||
playlist_writer.flush().expect("Failed to flush playlist to file");
|
playlist_writer
|
||||||
|
.flush()
|
||||||
|
.expect("Failed to flush playlist to file");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
println!("Playback mode (output: audio device)");
|
println!("Playback mode (output: audio device)");
|
||||||
|
@ -54,7 +55,7 @@ pub fn repl(args: CliArgs) {
|
||||||
read_loop(&args, &mut state, || {
|
read_loop(&args, &mut state, || {
|
||||||
if args.wait {
|
if args.wait {
|
||||||
match ctrl.wait_for_empty() {
|
match ctrl.wait_for_empty() {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(e) => eprintln!("{}", e.message()),
|
Err(e) => eprintln!("{}", e.message()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -67,23 +68,34 @@ pub fn repl(args: CliArgs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! {
|
fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! {
|
||||||
let mut read_buf: [u8;1] = [0];
|
let mut read_buf: [u8; 1] = [0];
|
||||||
prompt(&mut state.line_number, args);
|
prompt(&mut state.line_number, args);
|
||||||
loop {
|
loop {
|
||||||
read_buf[0] = 0;
|
read_buf[0] = 0;
|
||||||
while read_buf[0] == 0 {
|
while read_buf[0] == 0 {
|
||||||
// TODO: enable raw mode (char by char) reading of stdin
|
// TODO: enable raw mode (char by char) reading of stdin
|
||||||
state.stdin.read(&mut read_buf).expect("Failed to read stdin");
|
state
|
||||||
|
.stdin
|
||||||
|
.read(&mut read_buf)
|
||||||
|
.expect("Failed to read stdin");
|
||||||
}
|
}
|
||||||
match read_buf[0] as char {
|
match read_buf[0] as char {
|
||||||
'\n' => {
|
'\n' => {
|
||||||
state.statement_buf.push(read_buf[0]);
|
state.statement_buf.push(read_buf[0]);
|
||||||
state.writer.write(state.statement_buf.as_slice())
|
let statement_result = std::str::from_utf8(state.statement_buf.as_slice());
|
||||||
.expect("Failed to write to MPS interpreter");
|
if statement_result.is_ok() && statement_result.unwrap().starts_with("?") {
|
||||||
execute();
|
repl_commands(statement_result.unwrap());
|
||||||
|
state.writer.write(&[';' as u8]).unwrap_or(0);
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
.writer
|
||||||
|
.write(state.statement_buf.as_slice())
|
||||||
|
.expect("Failed to write to MPS interpreter");
|
||||||
|
execute();
|
||||||
|
}
|
||||||
state.statement_buf.clear();
|
state.statement_buf.clear();
|
||||||
prompt(&mut state.line_number, args);
|
prompt(&mut state.line_number, args);
|
||||||
},
|
}
|
||||||
_ => state.statement_buf.push(read_buf[0]),
|
_ => state.statement_buf.push(read_buf[0]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,3 +107,13 @@ fn prompt(line: &mut usize, args: &CliArgs) {
|
||||||
*line += 1;
|
*line += 1;
|
||||||
std::io::stdout().flush().expect("Failed to flush stdout");
|
std::io::stdout().flush().expect("Failed to flush stdout");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repl_commands(command_str: &str) {
|
||||||
|
let words: Vec<&str> = command_str.split(" ").map(|s| s.trim()).collect();
|
||||||
|
match words[0] {
|
||||||
|
"?help" => println!("{}", super::help::help()),
|
||||||
|
"?function" | "?functions" => println!("{}", super::help::functions()),
|
||||||
|
"?filter" | "?filters" => println!("{}", super::help::filters()),
|
||||||
|
_ => println!("Unknown command, try ?help"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue