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"
|
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)",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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 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 => {
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue