Add metadata pipe support which is compatible with forked-daapd.

This commit is contained in:
billsq 2018-05-07 05:51:05 +00:00
parent deb240c02f
commit 6de2ac5ce3
7 changed files with 123 additions and 7 deletions

1
Cargo.lock generated
View file

@ -487,6 +487,7 @@ name = "librespot-playback"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)", "alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)",
"base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -11,6 +11,7 @@ path = "../core"
path = "../metadata" path = "../metadata"
[dependencies] [dependencies]
base64 = "0.5.0"
futures = "0.1.8" futures = "0.1.8"
log = "0.3.5" log = "0.3.5"
byteorder = "1.2.1" byteorder = "1.2.1"

View file

@ -1,6 +1,6 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate base64;
extern crate byteorder; extern crate byteorder;
extern crate futures; extern crate futures;

View file

@ -9,6 +9,7 @@ pub trait Mixer: Send {
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> { fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
None None
} }
fn set_metadata_pipe(&mut self, _metadata_pipe: Option<String>) {}
} }
pub trait AudioFilter { pub trait AudioFilter {
@ -18,6 +19,9 @@ pub trait AudioFilter {
pub mod softmixer; pub mod softmixer;
use self::softmixer::SoftMixer; use self::softmixer::SoftMixer;
pub mod pipemixer;
use self::pipemixer::PipeMixer;
fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> { fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> {
Box::new(M::open()) Box::new(M::open())
} }
@ -25,6 +29,7 @@ fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> {
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn() -> Box<Mixer>> { pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn() -> Box<Mixer>> {
match name.as_ref().map(AsRef::as_ref) { match name.as_ref().map(AsRef::as_ref) {
None | Some("softvol") => Some(mk_sink::<SoftMixer>), None | Some("softvol") => Some(mk_sink::<SoftMixer>),
Some("pipe") => Some(mk_sink::<PipeMixer>),
_ => None, _ => None,
} }
} }

View file

@ -0,0 +1,56 @@
use base64;
use std::f32;
use std::fs::File;
use std::io::Write;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use super::Mixer;
#[derive(Clone)]
pub struct PipeMixer {
volume: Arc<AtomicUsize>,
pipe: Option<String>,
}
impl Mixer for PipeMixer {
fn open() -> PipeMixer {
PipeMixer {
volume: Arc::new(AtomicUsize::new(0xFFFF)),
pipe: None,
}
}
fn start(&self) {}
fn stop(&self) {}
fn volume(&self) -> u16 {
self.volume.load(Ordering::Relaxed) as u16
}
fn set_volume(&self, volume: u16) {
self.volume.store(volume as usize, Ordering::Relaxed);
if let Some(path) = self.pipe.as_ref() {
let vol = volume;
let metadata_vol = if vol == 0 {
-144.0f32
} else if vol == 1 {
-30.0f32
} else if vol == 0xFFFF {
0.0f32
} else {
((vol as f32) - (0xFFFF as f32)) * 30.0f32 / (0xFFFE as f32)
};
let vol_string = format!("{:.*},0.00,0.00,0.00", 2, metadata_vol);
let vol_string_len = vol_string.chars().count();
let metadata_vol_string = base64::encode(&vol_string);
let metadata_xml = format!("<item><type>73736e63</type><code>70766f6c</code><length>{}</length>\n<data encoding=\"base64\">\n{}</data></item>", vol_string_len, metadata_vol_string);
let mut f = File::create(path).expect("Unable to open pipe");
f.write_all(metadata_xml.as_bytes())
.expect("Unable to write data");
}
}
fn set_metadata_pipe(&mut self, metadata_pipe: Option<String>) {
self.pipe = metadata_pipe;
}
}

View file

@ -1,5 +1,6 @@
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use futures; use futures;
use base64;
use futures::sync::oneshot; use futures::sync::oneshot;
use futures::{future, Future}; use futures::{future, Future};
use std; use std;
@ -9,6 +10,8 @@ use std::mem;
use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError}; use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use std::fs::File;
use std::io::Write;
use config::{Bitrate, PlayerConfig}; use config::{Bitrate, PlayerConfig};
use core::session::Session; use core::session::Session;
@ -17,7 +20,7 @@ use core::spotify_id::SpotifyId;
use audio::{AudioDecrypt, AudioFile}; use audio::{AudioDecrypt, AudioFile};
use audio::{VorbisDecoder, VorbisPacket}; use audio::{VorbisDecoder, VorbisPacket};
use audio_backend::Sink; use audio_backend::Sink;
use metadata::{FileFormat, Metadata, Track}; use metadata::{FileFormat, Metadata, Track, Album, Artist};
use mixer::AudioFilter; use mixer::AudioFilter;
pub struct Player { pub struct Player {
@ -35,6 +38,8 @@ struct PlayerInternal {
sink_running: bool, sink_running: bool,
audio_filter: Option<Box<AudioFilter + Send>>, audio_filter: Option<Box<AudioFilter + Send>>,
event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>, event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>,
metadata_pipe: Option<String>,
} }
enum PlayerCommand { enum PlayerCommand {
@ -113,6 +118,7 @@ impl Player {
config: PlayerConfig, config: PlayerConfig,
session: Session, session: Session,
audio_filter: Option<Box<AudioFilter + Send>>, audio_filter: Option<Box<AudioFilter + Send>>,
metadata_pipe: Option<String>,
sink_builder: F, sink_builder: F,
) -> (Player, PlayerEventChannel) ) -> (Player, PlayerEventChannel)
where where
@ -133,6 +139,7 @@ impl Player {
sink: sink_builder(), sink: sink_builder(),
sink_running: false, sink_running: false,
audio_filter: audio_filter, audio_filter: audio_filter,
metadata_pipe: metadata_pipe,
event_sender: event_sender, event_sender: event_sender,
}; };
@ -535,6 +542,39 @@ impl PlayerInternal {
track_id.to_base62() track_id.to_base62()
); );
if let Some(path) = self.metadata_pipe.as_ref() {
let mut f = File::create(path).expect("Unable to open pipe");
// title
let title = track.name.clone();
let title_len = title.chars().count();
let title_string = base64::encode(&title);
let title_xml = format!("<item><type>636f7265</type><code>6d696e6d</code><length>{}</length>\n<data encoding=\"base64\">\n{}</data></item>", title_len, title_string);
f.write_all(title_xml.as_bytes()).expect("Unable to write title");
// album
let album = Album::get(&self.session, track.album).wait().unwrap();
let album_name = album.name.clone();
let album_name_len = album_name.chars().count();
let album_name_string = base64::encode(&album_name);
let album_name_xml = format!("<item><type>636f7265</type><code>6173616c</code><length>{}</length>\n<data encoding=\"base64\">\n{}</data></item>", album_name_len, album_name_string);
f.write_all(album_name_xml.as_bytes()).expect("Unable to write album");
// artist
let mut artists = String::new();
for id in &track.artists {
if artists != "" {
artists.push_str(" & ");
}
let artist = Artist::get(&self.session, *id).wait().unwrap();
artists.push_str(&artist.name);
}
let artists_len = artists.chars().count();
let artists_string = base64::encode(&artists);
let artists_xml = format!("<item><type>636f7265</type><code>61736172</code><length>{}</length>\n<data encoding=\"base64\">\n{}</data></item>", artists_len, artists_string);
f.write_all(artists_xml.as_bytes()).expect("Unable to write artists");
}
let track = match self.find_available_alternative(&track) { let track = match self.find_available_alternative(&track) {
Some(track) => track, Some(track) => track,
None => { None => {

View file

@ -90,6 +90,7 @@ fn list_backends() {
struct Setup { struct Setup {
backend: fn(Option<String>) -> Box<Sink>, backend: fn(Option<String>) -> Box<Sink>,
device: Option<String>, device: Option<String>,
metadata_pipe: Option<String>,
mixer: fn() -> Box<Mixer>, mixer: fn() -> Box<Mixer>,
@ -143,6 +144,7 @@ fn setup(args: &[String]) -> Setup {
"DEVICE", "DEVICE",
) )
.optopt("", "mixer", "Mixer to use", "MIXER") .optopt("", "mixer", "Mixer to use", "MIXER")
.optopt("", "metadata-pipe", "Pipe to write metadata", "METADATA_PIPE")
.optopt( .optopt(
"", "",
"initial-volume", "initial-volume",
@ -201,6 +203,8 @@ fn setup(args: &[String]) -> Setup {
let device = matches.opt_str("device"); let device = matches.opt_str("device");
let metadata_pipe = matches.opt_str("metadata-pipe");
let mixer_name = matches.opt_str("mixer"); let mixer_name = matches.opt_str("mixer");
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer"); let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
@ -312,6 +316,7 @@ fn setup(args: &[String]) -> Setup {
connect_config: connect_config, connect_config: connect_config,
credentials: credentials, credentials: credentials,
device: device, device: device,
metadata_pipe: metadata_pipe,
enable_discovery: enable_discovery, enable_discovery: enable_discovery,
zeroconf_port: zeroconf_port, zeroconf_port: zeroconf_port,
mixer: mixer, mixer: mixer,
@ -326,6 +331,7 @@ struct Main {
connect_config: ConnectConfig, connect_config: ConnectConfig,
backend: fn(Option<String>) -> Box<Sink>, backend: fn(Option<String>) -> Box<Sink>,
device: Option<String>, device: Option<String>,
metadata_pipe: Option<String>,
mixer: fn() -> Box<Mixer>, mixer: fn() -> Box<Mixer>,
handle: Handle, handle: Handle,
@ -352,6 +358,7 @@ impl Main {
connect_config: setup.connect_config, connect_config: setup.connect_config,
backend: setup.backend, backend: setup.backend,
device: setup.device, device: setup.device,
metadata_pipe: setup.metadata_pipe,
mixer: setup.mixer, mixer: setup.mixer,
connect: Box::new(futures::future::empty()), connect: Box::new(futures::future::empty()),
@ -414,16 +421,22 @@ impl Future for Main {
if let Async::Ready(session) = self.connect.poll().unwrap() { if let Async::Ready(session) = self.connect.poll().unwrap() {
self.connect = Box::new(futures::future::empty()); self.connect = Box::new(futures::future::empty());
let device = self.device.clone(); let device = self.device.clone();
let mixer = (self.mixer)(); let mut mixer = (self.mixer)();
let player_config = self.player_config.clone(); let player_config = self.player_config.clone();
let connect_config = self.connect_config.clone(); let connect_config = self.connect_config.clone();
let metadata_pipe = self.metadata_pipe.clone();
mixer.set_metadata_pipe(metadata_pipe.clone());
let audio_filter = mixer.get_audio_filter(); let audio_filter = mixer.get_audio_filter();
let backend = self.backend; let backend = self.backend;
let (player, event_channel) = let (player, event_channel) = Player::new(
Player::new(player_config, session.clone(), audio_filter, move || { player_config,
(backend)(device) session.clone(),
}); audio_filter,
metadata_pipe,
move || (backend)(device),
);
let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer); let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
self.spirc = Some(spirc); self.spirc = Some(spirc);