Add MPD support to front-end playback

This commit is contained in:
NGnius (Graham) 2022-05-30 20:28:27 -04:00
parent f7e72cd96c
commit fe7962b229
10 changed files with 94 additions and 15 deletions

1
Cargo.lock generated
View file

@ -1262,6 +1262,7 @@ version = "0.8.0"
dependencies = [
"fluent-uri",
"m3u8-rs",
"mpd",
"mpris-player",
"mps-interpreter",
"rodio",

View file

@ -29,7 +29,7 @@ mps-player = { version = "0.8.0", path = "./mps-player", default-features = fals
[target.'cfg(target_os = "linux")'.dependencies]
# TODO fix need to specify OS-specific dependency of mps-player
mps-player = { version = "0.8.0", path = "./mps-player", features = ["mpris-player"] }
mps-player = { version = "0.8.0", path = "./mps-player", features = ["mpris-player", "mpd"] }
[profile.release]
debug = false

View file

@ -89,7 +89,7 @@ fn song_to_item(song: Song) -> MpsItem {
*/
for (tag, value) in song.tags {
item.set_field(&tag, MpsTypePrimitive::parse(value));
item.set_field(&tag.to_lowercase(), MpsTypePrimitive::parse(value));
}
item
}

View file

@ -9,6 +9,7 @@ readme = "README.md"
rodio = { version = "^0.15", features = ["symphonia-all"]}
m3u8-rs = { version = "^3.0" }
fluent-uri = { version = "^0.1" }
mpd = { version = "0.0.12", optional = true }
# local
mps-interpreter = { path = "../mps-interpreter", version = "0.8.0" }
@ -18,7 +19,7 @@ mps-interpreter = { path = "../mps-interpreter", version = "0.8.0" }
mpris-player = { version = "^0.6", path = "../mpris-player", optional = true }
[features]
default = ["os-controls"]
default = ["os-controls", "mpd"]
os-controls = []
# I wish this worked...

View file

@ -5,6 +5,8 @@ use std::convert::Into;
pub enum PlayerError {
Playback(PlaybackError),
Uri(UriError),
#[cfg(feature = "mpd")]
Mpd(String),
}
impl PlayerError {
@ -15,6 +17,11 @@ impl PlayerError {
/*pub(crate) fn from_err_uri<E: Display>(err: E) -> Self {
Self::Uri(UriError::from_err(err))
}*/
#[cfg(feature = "mpd")]
pub(crate) fn from_err_mpd<E: Display>(err: E) -> Self {
Self::Mpd(format!("{}", err))
}
}
impl Display for PlayerError {
@ -22,6 +29,8 @@ impl Display for PlayerError {
match self {
Self::Playback(p) => (p as &dyn Display).fmt(f),
Self::Uri(u) => (u as &dyn Display).fmt(f),
#[cfg(feature = "mpd")]
Self::Mpd(m) => (m as &dyn Display).fmt(f),
}
}
}

View file

@ -5,6 +5,9 @@ use rodio::{decoder::Decoder, OutputStream, OutputStreamHandle, Sink};
use m3u8_rs::{MediaPlaylist, MediaSegment};
#[cfg(feature = "mpd")]
use mpd::{Client, Song};
use super::uri::Uri;
use mps_interpreter::{tokens::MpsTokenReader, MpsFaye, MpsItem};
@ -21,6 +24,8 @@ pub struct MpsPlayer<'a, T: MpsTokenReader + 'a> {
#[allow(dead_code)]
output_stream: OutputStream, // this is required for playback, so it must live as long as this struct instance
output_handle: OutputStreamHandle,
#[cfg(feature = "mpd")]
mpd_connection: Option<Client<std::net::TcpStream>>,
}
impl<'a, T: MpsTokenReader + 'a> MpsPlayer<'a, T> {
@ -32,9 +37,17 @@ impl<'a, T: MpsTokenReader + 'a> MpsPlayer<'a, T> {
sink: Sink::try_new(&output_handle).map_err(PlayerError::from_err_playback)?,
output_stream: stream,
output_handle: output_handle,
#[cfg(feature = "mpd")]
mpd_connection: None,
})
}
#[cfg(feature = "mpd")]
pub fn connect_mpd(&mut self, addr: std::net::SocketAddr) -> Result<(), PlayerError> {
self.mpd_connection = Some(Client::connect(addr).map_err(PlayerError::from_err_mpd)?);
Ok(())
}
pub fn play_all(&mut self) -> Result<(), PlayerError> {
while let Some(item) = self.runner.next() {
self.sink.sleep_until_end();
@ -149,7 +162,7 @@ impl<'a, T: MpsTokenReader + 'a> MpsPlayer<'a, T> {
if let Some(filename) =
music_filename(&music)
{
println!("Adding file `{}` to playlist", filename);
//println!("Adding file `{}` to playlist", filename);
playlist.segments.push(MediaSegment {
uri: filename,
title: music_title(&music),
@ -201,7 +214,20 @@ impl<'a, T: MpsTokenReader + 'a> MpsPlayer<'a, T> {
self.sink.append(source);
Ok(())
},
//TODO "mpd:" => {},
#[cfg(feature = "mpd")]
"mpd:" => {
if let Some(mpd_client) = &mut self.mpd_connection {
//println!("Pushing {} into MPD queue", uri.path());
let song = Song {
file: uri.path().to_owned(),
..Default::default()
};
mpd_client.push(song).map_err(PlayerError::from_err_playback)?;
Ok(())
} else {
Err(PlayerError::from_err_playback("Cannot play MPD song: no MPD client connected"))
}
},
scheme => Err(UriError::Unsupported(scheme.to_owned()).into())
},
None => {

View file

@ -26,4 +26,9 @@ impl<'a> Uri<&'a str> {
None => self.0
}
}
#[allow(dead_code)]
pub fn uri(&self) -> &'a str {
self.0
}
}

View file

@ -22,8 +22,19 @@ pub struct CliArgs {
/// The volume at which to playback audio, out of 1.0
#[clap(long)]
pub volume: Option<f32>,
/// MPD server for music playback
#[clap(short, long)]
pub mpd: Option<String>,
}
pub fn parse() -> CliArgs {
CliArgs::parse()
}
pub fn validate(args: &CliArgs) -> Result<(), String> {
if let Some(mpd_addr) = &args.mpd {
let _: std::net::SocketAddr = mpd_addr.parse().map_err(|e| format!("Unrecognized MPS address `{}`: {}", mpd_addr, e))?;
}
Ok(())
}

View file

@ -67,6 +67,10 @@ fn play_cursor() -> Result<(), PlayerError> {
fn main() {
let args = cli::parse();
if let Err(e) = cli::validate(&args) {
eprintln!("{}", e);
return;
}
if let Some(script_file) = &args.file {
// interpret script
@ -77,6 +81,7 @@ fn main() {
// build playback controller
let script_file2 = script_file.clone();
let volume = args.volume.clone();
let mpd = args.mpd.clone();
let player_builder = move || {
let script_reader = io::BufReader::new(
std::fs::File::open(&script_file2)
@ -84,10 +89,13 @@ fn main() {
);
let runner = MpsFaye::with_stream(script_reader);
let player = MpsPlayer::new(runner).unwrap();
let mut player = MpsPlayer::new(runner).unwrap();
if let Some(vol) = volume {
player.set_volume(vol);
}
if let Some(mpd) = mpd {
player.connect_mpd(mpd.parse().unwrap()).unwrap();
}
player
};
if let Some(playlist_file) = &args.playlist {

View file

@ -47,23 +47,36 @@ pub fn repl(args: CliArgs) {
term.set_title("mps");
let (writer, reader) = channel_io();
let volume = args.volume.clone();
let mpd = args.mpd.clone();
let player_builder = move || {
let runner = MpsFaye::with_stream(reader);
let player = MpsPlayer::new(runner).unwrap();
let mut player = MpsPlayer::new(runner).unwrap();
if let Some(vol) = volume {
player.set_volume(vol);
}
if let Some(mpd) = mpd {
player.connect_mpd(mpd.parse().unwrap()).unwrap();
}
player
};
let mut state = ReplState::new(writer, term);
if let Some(playlist_file) = &args.playlist {
writeln!(
state.terminal,
"Playlist mode (output: `{}`)",
playlist_file
)
.expect("Failed to write to terminal output");
if args.mpd.is_some() {
writeln!(
state.terminal,
"Playlist mode (output: `{}` & MPD)",
playlist_file
)
.expect("Failed to write to terminal output");
} else {
writeln!(
state.terminal,
"Playlist mode (output: `{}`)",
playlist_file
)
.expect("Failed to write to terminal output");
}
let mut player = player_builder();
let mut playlist_writer =
io::BufWriter::new(std::fs::File::create(playlist_file).unwrap_or_else(|_| {
@ -85,8 +98,13 @@ pub fn repl(args: CliArgs) {
.expect("Failed to flush playlist to file");
});
} else {
writeln!(state.terminal, "Playback mode (output: audio device)")
.expect("Failed to write to terminal output");
if args.mpd.is_some() {
writeln!(state.terminal, "Playback mode (output: audio device & MPD)")
.expect("Failed to write to terminal output");
} else {
writeln!(state.terminal, "Playback mode (output: audio device)")
.expect("Failed to write to terminal output");
}
let ctrl = MpsController::create_repl(player_builder);
read_loop(&args, &mut state, || {
if args.wait {