Skip to content

Commit

Permalink
Add metadata pipe support which is compatible with forked-daapd.
Browse files Browse the repository at this point in the history
  • Loading branch information
billsq committed May 7, 2018
1 parent deb240c commit 9180c72
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 7 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions playback/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ path = "../core"
path = "../metadata"

[dependencies]
base64 = "0.5.0"
futures = "0.1.8"
log = "0.3.5"
byteorder = "1.2.1"
Expand Down
2 changes: 1 addition & 1 deletion playback/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[macro_use]
extern crate log;

extern crate base64;
extern crate byteorder;
extern crate futures;

Expand Down
5 changes: 5 additions & 0 deletions playback/src/mixer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub trait Mixer: Send {
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
None
}
fn set_metadata_pipe(&mut self, _metadata_pipe: Option<String>) {}
}

pub trait AudioFilter {
Expand All @@ -18,13 +19,17 @@ pub trait AudioFilter {
pub mod softmixer;
use self::softmixer::SoftMixer;

pub mod pipemixer;
use self::pipemixer::PipeMixer;

fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> {
Box::new(M::open())
}

pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn() -> Box<Mixer>> {
match name.as_ref().map(AsRef::as_ref) {
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
Some("pipe") => Some(mk_sink::<PipeMixer>),
_ => None,
}
}
56 changes: 56 additions & 0 deletions playback/src/mixer/pipemixer.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
43 changes: 42 additions & 1 deletion playback/src/player.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use byteorder::{LittleEndian, ReadBytesExt};
use futures;
use base64;
use futures::sync::oneshot;
use futures::{future, Future};
use std;
Expand All @@ -9,6 +10,8 @@ use std::mem;
use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
use std::thread;
use std::time::Duration;
use std::fs::File;
use std::io::Write;

use config::{Bitrate, PlayerConfig};
use core::session::Session;
Expand All @@ -17,7 +20,7 @@ use core::spotify_id::SpotifyId;
use audio::{AudioDecrypt, AudioFile};
use audio::{VorbisDecoder, VorbisPacket};
use audio_backend::Sink;
use metadata::{FileFormat, Metadata, Track};
use metadata::{FileFormat, Metadata, Track, Album, Artist};
use mixer::AudioFilter;

pub struct Player {
Expand All @@ -35,6 +38,8 @@ struct PlayerInternal {
sink_running: bool,
audio_filter: Option<Box<AudioFilter + Send>>,
event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>,

metadata_pipe: Option<String>,
}

enum PlayerCommand {
Expand Down Expand Up @@ -113,6 +118,7 @@ impl Player {
config: PlayerConfig,
session: Session,
audio_filter: Option<Box<AudioFilter + Send>>,
metadata_pipe: Option<String>,
sink_builder: F,
) -> (Player, PlayerEventChannel)
where
Expand All @@ -133,6 +139,7 @@ impl Player {
sink: sink_builder(),
sink_running: false,
audio_filter: audio_filter,
metadata_pipe: metadata_pipe,
event_sender: event_sender,
};

Expand Down Expand Up @@ -535,6 +542,39 @@ impl PlayerInternal {
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) {
Some(track) => track,
None => {
Expand Down Expand Up @@ -648,3 +688,4 @@ impl<T: Read + Seek> Seek for Subfile<T> {
}
}
}
23 changes: 18 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ fn list_backends() {
struct Setup {
backend: fn(Option<String>) -> Box<Sink>,
device: Option<String>,
metadata_pipe: Option<String>,

mixer: fn() -> Box<Mixer>,

Expand Down Expand Up @@ -143,6 +144,7 @@ fn setup(args: &[String]) -> Setup {
"DEVICE",
)
.optopt("", "mixer", "Mixer to use", "MIXER")
.optopt("", "metadata-pipe", "Pipe to write metadata", "METADATA_PIPE")
.optopt(
"",
"initial-volume",
Expand Down Expand Up @@ -201,6 +203,8 @@ fn setup(args: &[String]) -> Setup {

let device = matches.opt_str("device");

let metadata_pipe = matches.opt_str("metadata-pipe");

let mixer_name = matches.opt_str("mixer");
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");

Expand Down Expand Up @@ -312,6 +316,7 @@ fn setup(args: &[String]) -> Setup {
connect_config: connect_config,
credentials: credentials,
device: device,
metadata_pipe: metadata_pipe,
enable_discovery: enable_discovery,
zeroconf_port: zeroconf_port,
mixer: mixer,
Expand All @@ -326,6 +331,7 @@ struct Main {
connect_config: ConnectConfig,
backend: fn(Option<String>) -> Box<Sink>,
device: Option<String>,
metadata_pipe: Option<String>,
mixer: fn() -> Box<Mixer>,
handle: Handle,

Expand All @@ -352,6 +358,7 @@ impl Main {
connect_config: setup.connect_config,
backend: setup.backend,
device: setup.device,
metadata_pipe: setup.metadata_pipe,
mixer: setup.mixer,

connect: Box::new(futures::future::empty()),
Expand Down Expand Up @@ -414,16 +421,22 @@ impl Future for Main {
if let Async::Ready(session) = self.connect.poll().unwrap() {
self.connect = Box::new(futures::future::empty());
let device = self.device.clone();
let mixer = (self.mixer)();
let mut mixer = (self.mixer)();
let player_config = self.player_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 backend = self.backend;
let (player, event_channel) =
Player::new(player_config, session.clone(), audio_filter, move || {
(backend)(device)
});
let (player, event_channel) = Player::new(
player_config,
session.clone(),
audio_filter,
metadata_pipe,
move || (backend)(device),
);

let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
self.spirc = Some(spirc);
Expand Down

0 comments on commit 9180c72

Please sign in to comment.