Add MPD support to front-end playback
This commit is contained in:
parent
f7e72cd96c
commit
fe7962b229
10 changed files with 94 additions and 15 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1262,6 +1262,7 @@ version = "0.8.0"
|
|||
dependencies = [
|
||||
"fluent-uri",
|
||||
"m3u8-rs",
|
||||
"mpd",
|
||||
"mpris-player",
|
||||
"mps-interpreter",
|
||||
"rodio",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -26,4 +26,9 @@ impl<'a> Uri<&'a str> {
|
|||
None => self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn uri(&self) -> &'a str {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
|
11
src/cli.rs
11
src/cli.rs
|
@ -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(())
|
||||
}
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -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 {
|
||||
|
|
36
src/repl.rs
36
src/repl.rs
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue