mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Add metadata pipe support which is compatible with forked-daapd.
This commit is contained in:
parent
deb240c02f
commit
6de2ac5ce3
7 changed files with 123 additions and 7 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -487,6 +487,7 @@ name = "librespot-playback"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"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)",
|
||||
"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)",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
extern crate base64;
|
||||
extern crate byteorder;
|
||||
extern crate futures;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -18,6 +19,9 @@ 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())
|
||||
}
|
||||
|
@ -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>> {
|
||||
match name.as_ref().map(AsRef::as_ref) {
|
||||
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
|
||||
Some("pipe") => Some(mk_sink::<PipeMixer>),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
56
playback/src/mixer/pipemixer.rs
Normal file
56
playback/src/mixer/pipemixer.rs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use futures;
|
||||
use base64;
|
||||
use futures::sync::oneshot;
|
||||
use futures::{future, Future};
|
||||
use std;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -133,6 +139,7 @@ impl Player {
|
|||
sink: sink_builder(),
|
||||
sink_running: false,
|
||||
audio_filter: audio_filter,
|
||||
metadata_pipe: metadata_pipe,
|
||||
event_sender: event_sender,
|
||||
};
|
||||
|
||||
|
@ -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 => {
|
||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -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>,
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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()),
|
||||
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue