Format according to rustfmt

This commit is contained in:
Sasha Hilton 2018-02-26 02:50:41 +01:00
parent c3745a958a
commit 237ef1e4f9
22 changed files with 502 additions and 360 deletions

View file

@ -7,8 +7,9 @@ use std::ops::Add;
use core::audio_key::AudioKey;
const AUDIO_AESIV: &'static [u8] = &[0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8,
0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93];
const AUDIO_AESIV: &'static [u8] = &[
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93
];
pub struct AudioDecrypt<T: io::Read> {
cipher: Box<SynchronousStreamCipher + 'static>,
@ -44,8 +45,8 @@ impl<T: io::Read + io::Seek> io::Seek for AudioDecrypt<T> {
let skip = newpos % 16;
let iv = BigUint::from_bytes_be(AUDIO_AESIV)
.add(BigUint::from_u64(newpos / 16).unwrap())
.to_bytes_be();
.add(BigUint::from_u64(newpos / 16).unwrap())
.to_bytes_be();
self.cipher = aes::ctr(aes::KeySize::KeySize128, &self.key.0, &iv);
let buf = vec![0u8; skip as usize];

View file

@ -1,11 +1,11 @@
use bit_set::BitSet;
use byteorder::{ByteOrder, BigEndian, WriteBytesExt};
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use futures::{Async, Future, Poll};
use futures::Stream;
use futures::sync::{oneshot, mpsc};
use futures::{Poll, Async, Future};
use futures::sync::{mpsc, oneshot};
use std::cmp::min;
use std::fs;
use std::io::{self, Read, Write, Seek, SeekFrom};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::sync::{Arc, Condvar, Mutex};
use tempfile::NamedTempFile;
@ -71,7 +71,12 @@ impl AudioFileOpenStreaming {
let (seek_tx, seek_rx) = mpsc::unbounded();
let fetcher = AudioFileFetch::new(
self.session.clone(), shared.clone(), data_rx, write_file, seek_rx, complete_tx
self.session.clone(),
shared.clone(),
data_rx,
write_file,
seek_rx,
complete_tx,
);
self.session.spawn(move |_| fetcher);
@ -148,14 +153,16 @@ impl AudioFile {
let session_ = session.clone();
session.spawn(move |_| {
complete_rx.map(move |mut file| {
if let Some(cache) = session_.cache() {
cache.save_file(file_id, &mut file);
debug!("File {} complete, saving to cache", file_id);
} else {
debug!("File {} complete", file_id);
}
}).or_else(|oneshot::Canceled| Ok(()))
complete_rx
.map(move |mut file| {
if let Some(cache) = session_.cache() {
cache.save_file(file_id, &mut file);
debug!("File {} complete, saving to cache", file_id);
} else {
debug!("File {} complete", file_id);
}
})
.or_else(|oneshot::Canceled| Ok(()))
});
AudioFileOpen::Streaming(open)
@ -200,11 +207,14 @@ struct AudioFileFetch {
}
impl AudioFileFetch {
fn new(session: Session, shared: Arc<AudioFileShared>,
data_rx: ChannelData, output: NamedTempFile,
seek_rx: mpsc::UnboundedReceiver<u64>,
complete_tx: oneshot::Sender<NamedTempFile>) -> AudioFileFetch
{
fn new(
session: Session,
shared: Arc<AudioFileShared>,
data_rx: ChannelData,
output: NamedTempFile,
seek_rx: mpsc::UnboundedReceiver<u64>,
complete_tx: oneshot::Sender<NamedTempFile>,
) -> AudioFileFetch {
AudioFileFetch {
session: session,
shared: shared,
@ -233,8 +243,11 @@ impl AudioFileFetch {
let offset = self.index * CHUNK_SIZE;
self.output.as_mut().unwrap()
.seek(SeekFrom::Start(offset as u64)).unwrap();
self.output
.as_mut()
.unwrap()
.seek(SeekFrom::Start(offset as u64))
.unwrap();
let (_headers, data) = request_chunk(&self.session, self.shared.file_id, self.index).split();
self.data_rx = data;
@ -275,13 +288,20 @@ impl Future for AudioFileFetch {
Ok(Async::Ready(Some(data))) => {
progress = true;
self.output.as_mut().unwrap()
.write_all(data.as_ref()).unwrap();
self.output
.as_mut()
.unwrap()
.write_all(data.as_ref())
.unwrap();
}
Ok(Async::Ready(None)) => {
Ok(Async::Ready(None)) => {
progress = true;
trace!("chunk {} / {} complete", self.index, self.shared.chunk_count);
trace!(
"chunk {} / {} complete",
self.index,
self.shared.chunk_count
);
let full = {
let mut bitmap = self.shared.bitmap.lock().unwrap();
@ -303,7 +323,7 @@ impl Future for AudioFileFetch {
Err(ChannelError) => {
warn!("error from channel");
return Ok(Async::Ready(()));
},
}
}
if !progress {

View file

@ -2,16 +2,17 @@ extern crate lewton;
use self::lewton::inside_ogg::OggStreamReader;
use std::io::{Read, Seek};
use std::fmt;
use std::error;
use std::fmt;
use std::io::{Read, Seek};
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
pub struct VorbisPacket(Vec<i16>);
pub struct VorbisError(lewton::VorbisError);
impl <R> VorbisDecoder<R>
where R: Read + Seek
impl<R> VorbisDecoder<R>
where
R: Read + Seek,
{
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
Ok(VorbisDecoder(OggStreamReader::new(input)?))

View file

@ -1,27 +1,29 @@
#[macro_use] extern crate log;
#[macro_use] extern crate futures;
#[macro_use]
extern crate futures;
#[macro_use]
extern crate log;
extern crate bit_set;
extern crate byteorder;
extern crate crypto;
extern crate num_traits;
extern crate num_bigint;
extern crate num_traits;
extern crate tempfile;
extern crate librespot_core as core;
mod fetch;
mod decrypt;
mod fetch;
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
mod lewton_decoder;
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
mod libvorbis_decoder;
pub use fetch::{AudioFile, AudioFileOpen};
pub use decrypt::AudioDecrypt;
pub use fetch::{AudioFile, AudioFileOpen};
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
pub use lewton_decoder::{VorbisDecoder, VorbisPacket, VorbisError};
pub use lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
pub use libvorbis_decoder::{VorbisDecoder, VorbisPacket, VorbisError};
pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket};

View file

@ -1,16 +1,19 @@
#[cfg(not(feature = "with-tremor"))] extern crate vorbis;
#[cfg(feature = "with-tremor")] extern crate tremor as vorbis;
#[cfg(feature = "with-tremor")]
extern crate tremor as vorbis;
#[cfg(not(feature = "with-tremor"))]
extern crate vorbis;
use std::io::{Read, Seek};
use std::fmt;
use std::error;
use std::fmt;
use std::io::{Read, Seek};
pub struct VorbisDecoder<R: Read + Seek>(vorbis::Decoder<R>);
pub struct VorbisPacket(vorbis::Packet);
pub struct VorbisError(vorbis::VorbisError);
impl <R> VorbisDecoder<R>
where R: Read + Seek
impl<R> VorbisDecoder<R>
where
R: Read + Seek,
{
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))

View file

@ -6,9 +6,9 @@ use tokio_core::reactor::Core;
use librespot::core::authentication::Credentials;
use librespot::core::config::SessionConfig;
use librespot::playback::config::PlayerConfig;
use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId;
use librespot::playback::config::PlayerConfig;
use librespot::playback::audio_backend;
use librespot::playback::player::Player;
@ -20,7 +20,7 @@ fn main() {
let session_config = SessionConfig::default();
let player_config = PlayerConfig::default();
let args : Vec<_> = env::args().collect();
let args: Vec<_> = env::args().collect();
if args.len() != 4 {
println!("Usage: {} USERNAME PASSWORD TRACK", args[0]);
}
@ -33,9 +33,12 @@ fn main() {
let backend = audio_backend::find(None).unwrap();
println!("Connecting ..");
let session = core.run(Session::connect(session_config, credentials, None, handle)).unwrap();
let session = core.run(Session::connect(session_config, credentials, None, handle))
.unwrap();
let player = Player::new(player_config, session.clone(), None, move || (backend)(None));
let player = Player::new(player_config, session.clone(), None, move || {
(backend)(None)
});
println!("Playing...");
core.run(player.load(track, true, 0)).unwrap();

View file

@ -1,11 +1,11 @@
use super::{Open, Sink};
use alsa::{Access, Format, Mode, Stream, PCM};
use std::io;
use alsa::{PCM, Stream, Mode, Format, Access};
pub struct AlsaSink(Option<PCM>, String);
impl Open for AlsaSink {
fn open(device: Option<String>) -> AlsaSink {
fn open(device: Option<String>) -> AlsaSink {
info!("Using alsa sink");
let name = device.unwrap_or("default".to_string());
@ -17,14 +17,22 @@ impl Open for AlsaSink {
impl Sink for AlsaSink {
fn start(&mut self) -> io::Result<()> {
if self.0.is_none() {
match PCM::open(&*self.1,
Stream::Playback, Mode::Blocking,
Format::Signed16, Access::Interleaved,
2, 44100) {
match PCM::open(
&*self.1,
Stream::Playback,
Mode::Blocking,
Format::Signed16,
Access::Interleaved,
2,
44100,
) {
Ok(f) => self.0 = Some(f),
Err(e) => {
error!("Alsa error PCM open {}", e);
return Err(io::Error::new(io::ErrorKind::Other, "Alsa error: PCM open failed"));
return Err(io::Error::new(
io::ErrorKind::Other,
"Alsa error: PCM open failed",
));
}
}
}

View file

@ -1,11 +1,12 @@
use std::io;
use super::{Open, Sink};
use jack::prelude::{AudioOutPort, AudioOutSpec, Client, JackControl, ProcessScope, AsyncClient, client_options, ProcessHandler, Port };
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use jack::prelude::{client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port,
ProcessHandler, ProcessScope};
use std::io;
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
pub struct JackSink {
send: SyncSender<i16>,
active_client: AsyncClient<(),JackData>,
active_client: AsyncClient<(), JackData>,
}
pub struct JackData {
@ -43,15 +44,26 @@ impl Open for JackSink {
let client_name = client_name.unwrap_or("librespot".to_string());
let (client, _status) = Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap();
let ch_r = client.register_port("out_0", AudioOutSpec::default()).unwrap();
let ch_l = client.register_port("out_1", AudioOutSpec::default()).unwrap();
let ch_r = client
.register_port("out_0", AudioOutSpec::default())
.unwrap();
let ch_l = client
.register_port("out_1", AudioOutSpec::default())
.unwrap();
// buffer for samples from librespot (~10ms)
let (tx, rx) = sync_channel(2*1024*4);
let jack_data = JackData { rec: rx, port_l: ch_l, port_r: ch_r };
let (tx, rx) = sync_channel(2 * 1024 * 4);
let jack_data = JackData {
rec: rx,
port_l: ch_l,
port_r: ch_r,
};
let active_client = AsyncClient::new(client, (), jack_data).unwrap();
JackSink { send: tx, active_client: active_client }
}
JackSink {
send: tx,
active_client: active_client,
}
}
}
impl Sink for JackSink {

View file

@ -37,9 +37,7 @@ use self::jackaudio::JackSink;
mod pipe;
use self::pipe::StdoutSink;
pub const BACKENDS : &'static [
(&'static str, fn(Option<String>) -> Box<Sink>)
] = &[
pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] = &[
#[cfg(feature = "alsa-backend")]
("alsa", mk_sink::<AlsaSink>),
#[cfg(feature = "portaudio-backend")]
@ -53,8 +51,16 @@ pub const BACKENDS : &'static [
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<Sink>> {
if let Some(name) = name {
BACKENDS.iter().find(|backend| name == backend.0).map(|backend| backend.1)
BACKENDS
.iter()
.find(|backend| name == backend.0)
.map(|backend| backend.1)
} else {
Some(BACKENDS.first().expect("No backends were enabled at build time").1)
Some(
BACKENDS
.first()
.expect("No backends were enabled at build time")
.1,
)
}
}

View file

@ -28,7 +28,10 @@ impl Sink for StdoutSink {
fn write(&mut self, data: &[i16]) -> io::Result<()> {
let data: &[u8] = unsafe {
slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::<i16>())
slice::from_raw_parts(
data.as_ptr() as *const u8,
data.len() * mem::size_of::<i16>(),
)
};
self.0.write_all(data)?;
@ -37,4 +40,3 @@ impl Sink for StdoutSink {
Ok(())
}
}

View file

@ -1,21 +1,21 @@
use super::{Open, Sink};
use portaudio_rs;
use portaudio_rs::device::{get_default_output_index, DeviceIndex, DeviceInfo};
use portaudio_rs::stream::*;
use std::io;
use std::process::exit;
use std::time::Duration;
use portaudio_rs;
use portaudio_rs::stream::*;
use portaudio_rs::device::{DeviceIndex, DeviceInfo, get_default_output_index};
pub struct PortAudioSink<'a>(Option<portaudio_rs::stream::Stream<'a, i16, i16>>, StreamParameters<i16>);
pub struct PortAudioSink<'a>(
Option<portaudio_rs::stream::Stream<'a, i16, i16>>,
StreamParameters<i16>,
);
fn output_devices() -> Box<Iterator<Item=(DeviceIndex, DeviceInfo)>> {
fn output_devices() -> Box<Iterator<Item = (DeviceIndex, DeviceInfo)>> {
let count = portaudio_rs::device::get_count().unwrap();
let devices = (0..count)
.filter_map(|idx| {
portaudio_rs::device::get_info(idx).map(|info| (idx, info))
}).filter(|&(_, ref info)| {
info.max_output_channels > 0
});
.filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info)))
.filter(|&(_, ref info)| info.max_output_channels > 0);
Box::new(devices)
}
@ -38,9 +38,8 @@ fn find_output(device: &str) -> Option<DeviceIndex> {
.map(|(idx, _)| idx)
}
impl <'a> Open for PortAudioSink<'a> {
impl<'a> Open for PortAudioSink<'a> {
fn open(device: Option<String>) -> PortAudioSink<'a> {
debug!("Using PortAudio sink");
portaudio_rs::initialize().unwrap();
@ -71,16 +70,19 @@ impl <'a> Open for PortAudioSink<'a> {
}
}
impl <'a> Sink for PortAudioSink<'a> {
impl<'a> Sink for PortAudioSink<'a> {
fn start(&mut self) -> io::Result<()> {
if self.0.is_none() {
self.0 = Some(Stream::open(
None, Some(self.1),
44100.0,
FRAMES_PER_BUFFER_UNSPECIFIED,
StreamFlags::empty(),
None
).unwrap());;
self.0 = Some(
Stream::open(
None,
Some(self.1),
44100.0,
FRAMES_PER_BUFFER_UNSPECIFIED,
StreamFlags::empty(),
None,
).unwrap(),
);;
}
self.0.as_mut().unwrap().start().unwrap();
@ -94,8 +96,7 @@ impl <'a> Sink for PortAudioSink<'a> {
fn write(&mut self, data: &[i16]) -> io::Result<()> {
match self.0.as_mut().unwrap().write(data) {
Ok(_) => (),
Err(portaudio_rs::PaError::OutputUnderflowed) =>
error!("PortAudio write underflow"),
Err(portaudio_rs::PaError::OutputUnderflowed) => error!("PortAudio write underflow"),
Err(e) => panic!("PA Error {}", e),
};
@ -103,7 +104,7 @@ impl <'a> Sink for PortAudioSink<'a> {
}
}
impl <'a> Drop for PortAudioSink<'a> {
impl<'a> Drop for PortAudioSink<'a> {
fn drop(&mut self) {
portaudio_rs::terminate().unwrap();
}

View file

@ -1,20 +1,21 @@
use super::{Open, Sink};
use std::io;
use libpulse_sys::*;
use std::ptr::{null, null_mut};
use std::ffi::CString;
use std::ffi::CStr;
use std::mem;
use libc;
use libpulse_sys::*;
use std::ffi::CStr;
use std::ffi::CString;
use std::io;
use std::mem;
use std::ptr::{null, null_mut};
pub struct PulseAudioSink {
s : *mut pa_simple,
ss : pa_sample_spec,
name : CString,
desc : CString
s: *mut pa_simple,
ss: pa_sample_spec,
name: CString,
desc: CString,
}
fn call_pulseaudio<T, F, FailCheck>(f: F, fail_check: FailCheck, kind: io::ErrorKind) -> io::Result<T> where
fn call_pulseaudio<T, F, FailCheck>(f: F, fail_check: FailCheck, kind: io::ErrorKind) -> io::Result<T>
where
T: Copy,
F: Fn(*mut libc::c_int) -> T,
FailCheck: Fn(T) -> bool,
@ -23,7 +24,7 @@ fn call_pulseaudio<T, F, FailCheck>(f: F, fail_check: FailCheck, kind: io::Error
let ret = f(&mut error);
if fail_check(ret) {
let err_cstr = unsafe { CStr::from_ptr(pa_strerror(error)) };
let errstr = err_cstr.to_string_lossy().into_owned();
let errstr = err_cstr.to_string_lossy().into_owned();
Err(io::Error::new(kind, errstr))
} else {
Ok(ret)
@ -48,7 +49,7 @@ impl Drop for PulseAudioSink {
}
impl Open for PulseAudioSink {
fn open(device: Option<String>) -> PulseAudioSink {
fn open(device: Option<String>) -> PulseAudioSink {
debug!("Using PulseAudio sink");
if device.is_some() {
@ -58,7 +59,7 @@ impl Open for PulseAudioSink {
let ss = pa_sample_spec {
format: PA_SAMPLE_S16LE,
channels: 2, // stereo
rate: 44100
rate: 44100,
};
let name = CString::new("librespot").unwrap();
@ -68,7 +69,7 @@ impl Open for PulseAudioSink {
s: null_mut(),
ss: ss,
name: name,
desc: description
desc: description,
}
}
}
@ -78,18 +79,21 @@ impl Sink for PulseAudioSink {
if self.s == null_mut() {
self.s = call_pulseaudio(
|err| unsafe {
pa_simple_new(null(), // Use the default server.
self.name.as_ptr(), // Our application's name.
PA_STREAM_PLAYBACK,
null(), // Use the default device.
self.desc.as_ptr(), // desc of our stream.
&self.ss, // Our sample format.
null(), // Use default channel map
null(), // Use default buffering attributes.
err)
pa_simple_new(
null(), // Use the default server.
self.name.as_ptr(), // Our application's name.
PA_STREAM_PLAYBACK,
null(), // Use the default device.
self.desc.as_ptr(), // desc of our stream.
&self.ss, // Our sample format.
null(), // Use default channel map
null(), // Use default buffering attributes.
err,
)
},
|ptr| ptr == null_mut(),
io::ErrorKind::ConnectionRefused)?;
io::ErrorKind::ConnectionRefused,
)?;
}
Ok(())
}
@ -101,17 +105,18 @@ impl Sink for PulseAudioSink {
fn write(&mut self, data: &[i16]) -> io::Result<()> {
if self.s == null_mut() {
Err(io::Error::new(io::ErrorKind::NotConnected, "Not connected to pulseaudio"))
}
else {
Err(io::Error::new(
io::ErrorKind::NotConnected,
"Not connected to pulseaudio",
))
} else {
let ptr = data.as_ptr() as *const libc::c_void;
let len = data.len() as usize * mem::size_of::<i16>();
call_pulseaudio(
|err| unsafe {
pa_simple_write(self.s, ptr, len, err)
},
|err| unsafe { pa_simple_write(self.s, ptr, len, err) },
|ret| ret < 0,
io::ErrorKind::BrokenPipe)?;
io::ErrorKind::BrokenPipe,
)?;
Ok(())
}
}

View file

@ -1,7 +1,8 @@
#[macro_use] extern crate log;
#[macro_use]
extern crate log;
extern crate futures;
extern crate byteorder;
extern crate futures;
#[cfg(feature = "alsa-backend")]
extern crate alsa;

View file

@ -1,5 +1,7 @@
pub trait Mixer : Send {
fn open() -> Self where Self: Sized;
pub trait Mixer: Send {
fn open() -> Self
where
Self: Sized;
fn start(&self);
fn stop(&self);
fn set_volume(&self, volume: u16);

View file

@ -1,24 +1,22 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use super::Mixer;
use super::AudioFilter;
use super::Mixer;
#[derive(Clone)]
pub struct SoftMixer {
volume: Arc<AtomicUsize>
volume: Arc<AtomicUsize>,
}
impl Mixer for SoftMixer {
fn open() -> SoftMixer {
SoftMixer {
volume: Arc::new(AtomicUsize::new(0xFFFF))
volume: Arc::new(AtomicUsize::new(0xFFFF)),
}
}
fn start(&self) {
}
fn stop(&self) {
}
fn start(&self) {}
fn stop(&self) {}
fn volume(&self) -> u16 {
self.volume.load(Ordering::Relaxed) as u16
}
@ -26,12 +24,14 @@ impl Mixer for SoftMixer {
self.volume.store(volume as usize, Ordering::Relaxed);
}
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
Some(Box::new(SoftVolumeApplier { volume: self.volume.clone() }))
Some(Box::new(SoftVolumeApplier {
volume: self.volume.clone(),
}))
}
}
struct SoftVolumeApplier {
volume: Arc<AtomicUsize>
volume: Arc<AtomicUsize>,
}
impl AudioFilter for SoftVolumeApplier {

View file

@ -1,12 +1,12 @@
use byteorder::{LittleEndian, ReadBytesExt};
use futures::sync::oneshot;
use futures::{future, Future};
use futures;
use futures::{future, Future};
use futures::sync::oneshot;
use std;
use std::borrow::Cow;
use std::io::{Read, Seek, SeekFrom, Result};
use std::io::{Read, Result, Seek, SeekFrom};
use std::mem;
use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError};
use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
use std::thread;
use std::time::Duration;
@ -14,10 +14,10 @@ use config::{Bitrate, PlayerConfig};
use core::session::Session;
use core::spotify_id::SpotifyId;
use audio_backend::Sink;
use audio::{AudioFile, AudioDecrypt};
use audio::{AudioDecrypt, AudioFile};
use audio::{VorbisDecoder, VorbisPacket};
use metadata::{FileFormat, Track, Metadata};
use audio_backend::Sink;
use metadata::{FileFormat, Metadata, Track};
use mixer::AudioFilter;
pub struct Player {
@ -58,7 +58,7 @@ pub enum PlayerEvent {
Stopped {
track_id: SpotifyId,
}
},
}
type PlayerEventChannel = futures::sync::mpsc::UnboundedReceiver<PlayerEvent>;
@ -74,7 +74,8 @@ struct NormalisationData {
impl NormalisationData {
fn parse_from_file<T: Read + Seek>(mut file: T) -> Result<NormalisationData> {
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET)).unwrap();
file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))
.unwrap();
let track_gain_db = file.read_f32::<LittleEndian>().unwrap();
let track_peak = file.read_f32::<LittleEndian>().unwrap();
@ -92,7 +93,10 @@ impl NormalisationData {
}
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
let mut normalisation_factor = f32::powf(10.0, (data.track_gain_db + config.normalisation_pregain) / 20.0);
let mut normalisation_factor = f32::powf(
10.0,
(data.track_gain_db + config.normalisation_pregain) / 20.0,
);
if normalisation_factor * data.track_peak > 1.0 {
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
@ -107,10 +111,14 @@ impl NormalisationData {
}
impl Player {
pub fn new<F>(config: PlayerConfig, session: Session,
audio_filter: Option<Box<AudioFilter + Send>>,
sink_builder: F) -> (Player, PlayerEventChannel)
where F: FnOnce() -> Box<Sink> + Send + 'static
pub fn new<F>(
config: PlayerConfig,
session: Session,
audio_filter: Option<Box<AudioFilter + Send>>,
sink_builder: F,
) -> (Player, PlayerEventChannel)
where
F: FnOnce() -> Box<Sink> + Send + 'static,
{
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
let (event_sender, event_receiver) = futures::sync::mpsc::unbounded();
@ -133,17 +141,25 @@ impl Player {
internal.run();
});
(Player { commands: Some(cmd_tx), thread_handle: Some(handle) },
event_receiver)
(
Player {
commands: Some(cmd_tx),
thread_handle: Some(handle),
},
event_receiver,
)
}
fn command(&self, cmd: PlayerCommand) {
self.commands.as_ref().unwrap().send(cmd).unwrap();
}
pub fn load(&self, track: SpotifyId, start_playing: bool, position_ms: u32)
-> oneshot::Receiver<()>
{
pub fn load(
&self,
track: SpotifyId,
start_playing: bool,
position_ms: u32,
) -> oneshot::Receiver<()> {
let (tx, rx) = oneshot::channel();
self.command(PlayerCommand::Load(track, start_playing, position_ms, tx));
@ -174,7 +190,7 @@ impl Drop for Player {
if let Some(handle) = self.thread_handle.take() {
match handle.join() {
Ok(_) => (),
Err(_) => error!("Player thread panicked!")
Err(_) => error!("Player thread panicked!"),
}
}
}
@ -195,7 +211,9 @@ enum PlayerState {
end_of_track: oneshot::Sender<()>,
normalisation_factor: f32,
},
EndOfTrack { track_id: SpotifyId },
EndOfTrack {
track_id: SpotifyId,
},
Invalid,
}
@ -213,8 +231,12 @@ impl PlayerState {
use self::PlayerState::*;
match *self {
Stopped | EndOfTrack { .. } => None,
Paused { ref mut decoder, .. } |
Playing { ref mut decoder, .. } => Some(decoder),
Paused {
ref mut decoder, ..
}
| Playing {
ref mut decoder, ..
} => Some(decoder),
Invalid => panic!("invalid state"),
}
}
@ -222,18 +244,27 @@ impl PlayerState {
fn playing_to_end_of_track(&mut self) {
use self::PlayerState::*;
match mem::replace(self, Invalid) {
Playing { track_id, end_of_track, ..} => {
Playing {
track_id,
end_of_track,
..
} => {
let _ = end_of_track.send(());
*self = EndOfTrack { track_id };
},
_ => panic!("Called playing_to_end_of_track in non-playing state.")
}
_ => panic!("Called playing_to_end_of_track in non-playing state."),
}
}
fn paused_to_playing(&mut self) {
use self::PlayerState::*;
match ::std::mem::replace(self, Invalid) {
Paused { track_id, decoder, end_of_track, normalisation_factor } => {
Paused {
track_id,
decoder,
end_of_track,
normalisation_factor,
} => {
*self = Playing {
track_id: track_id,
decoder: decoder,
@ -248,7 +279,12 @@ impl PlayerState {
fn playing_to_paused(&mut self) {
use self::PlayerState::*;
match ::std::mem::replace(self, Invalid) {
Playing { track_id, decoder, end_of_track, normalisation_factor } => {
Playing {
track_id,
decoder,
end_of_track,
normalisation_factor,
} => {
*self = Paused {
track_id: track_id,
decoder: decoder,
@ -265,16 +301,13 @@ impl PlayerInternal {
fn run(mut self) {
loop {
let cmd = if self.state.is_playing() {
if self.sink_running
{
if self.sink_running {
match self.commands.try_recv() {
Ok(cmd) => Some(cmd),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => return,
}
}
else
{
} else {
match self.commands.recv_timeout(Duration::from_secs(5)) {
Ok(cmd) => Some(cmd),
Err(RecvTimeoutError::Timeout) => None,
@ -292,14 +325,19 @@ impl PlayerInternal {
self.handle_command(cmd);
}
if self.state.is_playing() && ! self.sink_running {
if self.state.is_playing() && !self.sink_running {
self.start_sink();
}
if self.sink_running {
let mut current_normalisation_factor: f32 = 1.0;
let packet = if let PlayerState::Playing { ref mut decoder, normalisation_factor, .. } = self.state {
let packet = if let PlayerState::Playing {
ref mut decoder,
normalisation_factor,
..
} = self.state
{
current_normalisation_factor = normalisation_factor;
Some(decoder.next_packet().expect("Vorbis error"))
} else {
@ -369,12 +407,17 @@ impl PlayerInternal {
Some((decoder, normalisation_factor)) => {
if play {
match self.state {
PlayerState::Playing { track_id: old_track_id, ..}
| PlayerState::EndOfTrack { track_id: old_track_id, .. } =>
self.send_event(PlayerEvent::Changed {
old_track_id: old_track_id,
new_track_id: track_id
}),
PlayerState::Playing {
track_id: old_track_id,
..
}
| PlayerState::EndOfTrack {
track_id: old_track_id,
..
} => self.send_event(PlayerEvent::Changed {
old_track_id: old_track_id,
new_track_id: track_id,
}),
_ => self.send_event(PlayerEvent::Started { track_id }),
}
@ -394,12 +437,17 @@ impl PlayerInternal {
normalisation_factor: normalisation_factor,
};
match self.state {
PlayerState::Playing { track_id: old_track_id, ..}
| PlayerState::EndOfTrack { track_id: old_track_id, .. } =>
self.send_event(PlayerEvent::Changed {
old_track_id: old_track_id,
new_track_id: track_id
}),
PlayerState::Playing {
track_id: old_track_id,
..
}
| PlayerState::EndOfTrack {
track_id: old_track_id,
..
} => self.send_event(PlayerEvent::Changed {
old_track_id: old_track_id,
new_track_id: track_id,
}),
_ => (),
}
self.send_event(PlayerEvent::Stopped { track_id });
@ -445,21 +493,19 @@ impl PlayerInternal {
}
}
PlayerCommand::Stop => {
match self.state {
PlayerState::Playing { track_id, .. }
| PlayerState::Paused { track_id, .. }
| PlayerState::EndOfTrack { track_id } => {
self.stop_sink_if_running();
self.send_event(PlayerEvent::Stopped { track_id });
self.state = PlayerState::Stopped;
},
PlayerState::Stopped => {
warn!("Player::stop called from invalid state");
}
PlayerState::Invalid => panic!("invalid state"),
PlayerCommand::Stop => match self.state {
PlayerState::Playing { track_id, .. }
| PlayerState::Paused { track_id, .. }
| PlayerState::EndOfTrack { track_id } => {
self.stop_sink_if_running();
self.send_event(PlayerEvent::Stopped { track_id });
self.state = PlayerState::Stopped;
}
}
PlayerState::Stopped => {
warn!("Player::stop called from invalid state");
}
PlayerState::Invalid => panic!("invalid state"),
},
}
}
@ -471,14 +517,16 @@ impl PlayerInternal {
if track.available {
Some(Cow::Borrowed(track))
} else {
let alternatives = track.alternatives
let alternatives = track
.alternatives
.iter()
.map(|alt_id| {
Track::get(&self.session, *alt_id)
});
.map(|alt_id| Track::get(&self.session, *alt_id));
let alternatives = future::join_all(alternatives).wait().unwrap();
alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned)
alternatives
.into_iter()
.find(|alt| alt.available)
.map(Cow::Owned)
}
}
@ -504,12 +552,19 @@ impl PlayerInternal {
let file_id = match track.files.get(&format) {
Some(&file_id) => file_id,
None => {
warn!("Track \"{}\" is not available in format {:?}", track.name, format);
warn!(
"Track \"{}\" is not available in format {:?}",
track.name, format
);
return None;
}
};
let key = self.session.audio_key().request(track.id, file_id).wait().unwrap();
let key = self.session
.audio_key()
.request(track.id, file_id)
.wait()
.unwrap();
let encrypted_file = AudioFile::open(&self.session, file_id).wait().unwrap();
@ -520,7 +575,7 @@ impl PlayerInternal {
Err(_) => {
warn!("Unable to extract normalisation data, using default value.");
1.0 as f32
},
}
};
let audio_file = Subfile::new(decrypted_file, 0xa7);
@ -547,27 +602,15 @@ impl Drop for PlayerInternal {
impl ::std::fmt::Debug for PlayerCommand {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
PlayerCommand::Load(track, play, position, _) => {
f.debug_tuple("Load")
.field(&track)
.field(&play)
.field(&position)
.finish()
}
PlayerCommand::Play => {
f.debug_tuple("Play").finish()
}
PlayerCommand::Pause => {
f.debug_tuple("Pause").finish()
}
PlayerCommand::Stop => {
f.debug_tuple("Stop").finish()
}
PlayerCommand::Seek(position) => {
f.debug_tuple("Seek")
.field(&position)
.finish()
}
PlayerCommand::Load(track, play, position, _) => f.debug_tuple("Load")
.field(&track)
.field(&play)
.field(&position)
.finish(),
PlayerCommand::Play => f.debug_tuple("Play").finish(),
PlayerCommand::Pause => f.debug_tuple("Pause").finish(),
PlayerCommand::Stop => f.debug_tuple("Stop").finish(),
PlayerCommand::Seek(position) => f.debug_tuple("Seek").field(&position).finish(),
}
}
}

View file

@ -1,5 +1,5 @@
use std::io::prelude::*;
use std::fs::File;
use std::io::prelude::*;
mod files;
@ -7,7 +7,10 @@ fn main() {
for &(path, expected_checksum) in files::FILES {
let actual = cksum_file(path).unwrap();
if expected_checksum != actual {
panic!("Checksum for {:?} does not match. Try running build.sh", path);
panic!(
"Checksum for {:?} does not match. Try running build.sh",
path
);
}
}
}
@ -23,83 +26,51 @@ fn cksum_file<T: AsRef<std::path::Path>>(path: T) -> std::io::Result<u32> {
fn cksum<T: AsRef<[u8]>>(data: T) -> u32 {
let data = data.as_ref();
let mut value = 0u32;
for x in data {
value = (value << 8) ^ CRC_LOOKUP_ARRAY[(*x as u32 ^ (value >> 24)) as usize];
}
let mut value = 0u32;
for x in data {
value = (value << 8) ^ CRC_LOOKUP_ARRAY[(*x as u32 ^ (value >> 24)) as usize];
}
let mut n = data.len();
while n != 0 {
value = (value << 8) ^ CRC_LOOKUP_ARRAY[((n & 0xFF) as u32 ^ (value >> 24)) as usize];
value = (value << 8) ^ CRC_LOOKUP_ARRAY[((n & 0xFF) as u32 ^ (value >> 24)) as usize];
n >>= 8;
}
!value
}
static CRC_LOOKUP_ARRAY : &'static[u32] = &[
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
static CRC_LOOKUP_ARRAY: &'static [u32] = &[
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
];

View file

@ -1,6 +1,6 @@
// Autogenerated by build.sh
pub const FILES : &'static [(&'static str, u32)] = &[
pub const FILES: &'static [(&'static str, u32)] = &[
("proto/authentication.proto", 2098196376),
("proto/keyexchange.proto", 451735664),
("proto/mercury.proto", 709993906),

View file

@ -1,5 +1,4 @@
#![crate_name = "librespot"]
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
extern crate base64;
@ -15,6 +14,6 @@ extern crate url;
pub extern crate librespot_audio as audio;
pub extern crate librespot_connect as connect;
pub extern crate librespot_core as core;
pub extern crate librespot_metadata as metadata;
pub extern crate librespot_playback as playback;
pub extern crate librespot_protocol as protocol;
pub extern crate librespot_metadata as metadata;

View file

@ -1,40 +1,41 @@
#[macro_use] extern crate log;
extern crate crypto;
extern crate env_logger;
extern crate futures;
extern crate getopts;
extern crate librespot;
#[macro_use]
extern crate log;
extern crate rpassword;
extern crate tokio_core;
extern crate tokio_io;
extern crate tokio_signal;
extern crate crypto;
use crypto::digest::Digest;
use crypto::sha1::Sha1;
use env_logger::LogBuilder;
use futures::{Future, Async, Poll, Stream};
use futures::{Async, Future, Poll, Stream};
use futures::sync::mpsc::UnboundedReceiver;
use std::env;
use std::io::{self, stderr, Write};
use std::mem;
use std::path::PathBuf;
use std::process::exit;
use std::str::FromStr;
use tokio_core::reactor::{Handle, Core};
use tokio_core::reactor::{Core, Handle};
use tokio_io::IoStream;
use std::mem;
use crypto::digest::Digest;
use crypto::sha1::Sha1;
use librespot::core::authentication::{get_credentials, Credentials};
use librespot::core::cache::Cache;
use librespot::core::config::{DeviceType, SessionConfig, ConnectConfig};
use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig};
use librespot::core::session::Session;
use librespot::core::version;
use librespot::connect::discovery::{discovery, DiscoveryStream};
use librespot::connect::spirc::{Spirc, SpircTask};
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
use librespot::playback::config::{Bitrate, PlayerConfig};
use librespot::connect::discovery::{discovery, DiscoveryStream};
use librespot::playback::mixer::{self, Mixer};
use librespot::playback::player::{Player, PlayerEvent};
use librespot::connect::spirc::{Spirc, SpircTask};
mod player_event_handler;
use player_event_handler::run_program_on_events;
@ -102,28 +103,80 @@ struct Setup {
fn setup(args: &[String]) -> Setup {
let mut opts = getopts::Options::new();
opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
.optflag("", "disable-audio-cache", "Disable caching of the audio data.")
opts.optopt(
"c",
"cache",
"Path to a directory where files will be cached.",
"CACHE",
).optflag(
"",
"disable-audio-cache",
"Disable caching of the audio data.",
)
.reqopt("n", "name", "Device name", "NAME")
.optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
.optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
.optopt("", "onevent", "Run PROGRAM when playback is about to begin.", "PROGRAM")
.optopt(
"b",
"bitrate",
"Bitrate (96, 160 or 320). Defaults to 160",
"BITRATE",
)
.optopt(
"",
"onevent",
"Run PROGRAM when playback is about to begin.",
"PROGRAM",
)
.optflag("v", "verbose", "Enable verbose output")
.optopt("u", "username", "Username to sign in with", "USERNAME")
.optopt("p", "password", "Password", "PASSWORD")
.optflag("", "disable-discovery", "Disable discovery mode")
.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
.optopt("", "device", "Audio device to use. Use '?' to list options if using portaudio", "DEVICE")
.optopt(
"",
"backend",
"Audio backend to use. Use '?' to list options",
"BACKEND",
)
.optopt(
"",
"device",
"Audio device to use. Use '?' to list options if using portaudio",
"DEVICE",
)
.optopt("", "mixer", "Mixer to use", "MIXER")
.optopt("", "initial-volume", "Initial volume in %, once connected (must be from 0 to 100)", "VOLUME")
.optopt("", "zeroconf-port", "The port the internal server advertised over zeroconf uses.", "ZEROCONF_PORT")
.optflag("", "enable-volume-normalisation", "Play all tracks at the same volume")
.optopt("", "normalisation-pregain", "Pregain (dB) applied by volume normalisation", "PREGAIN");
.optopt(
"",
"initial-volume",
"Initial volume in %, once connected (must be from 0 to 100)",
"VOLUME",
)
.optopt(
"",
"zeroconf-port",
"The port the internal server advertised over zeroconf uses.",
"ZEROCONF_PORT",
)
.optflag(
"",
"enable-volume-normalisation",
"Play all tracks at the same volume",
)
.optopt(
"",
"normalisation-pregain",
"Pregain (dB) applied by volume normalisation",
"PREGAIN",
);
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap();
writeln!(
stderr(),
"error: {}\n{}",
f.to_string(),
usage(&args[0], &opts)
).unwrap();
exit(1);
}
};
@ -131,11 +184,13 @@ fn setup(args: &[String]) -> Setup {
let verbose = matches.opt_present("verbose");
setup_logging(verbose);
info!("librespot {} ({}). Built on {}. Build ID: {}",
version::short_sha(),
version::commit_date(),
version::short_now(),
version::build_id());
info!(
"librespot {} ({}). Built on {}. Build ID: {}",
version::short_sha(),
version::commit_date(),
version::short_now(),
version::build_id()
);
let backend_name = matches.opt_str("backend");
if backend_name == Some("?".into()) {
@ -143,14 +198,12 @@ fn setup(args: &[String]) -> Setup {
exit(0);
}
let backend = audio_backend::find(backend_name)
.expect("Invalid backend");
let backend = audio_backend::find(backend_name).expect("Invalid backend");
let device = matches.opt_str("device");
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");
let initial_volume = matches
.opt_str("initial-volume")
@ -163,17 +216,17 @@ fn setup(args: &[String]) -> Setup {
})
.unwrap_or(0x8000);
let zeroconf_port =
matches.opt_str("zeroconf-port")
.map(|port| port.parse::<u16>().unwrap())
.unwrap_or(0);
let zeroconf_port = matches
.opt_str("zeroconf-port")
.map(|port| port.parse::<u16>().unwrap())
.unwrap_or(0);
let name = matches.opt_str("name").unwrap();
let use_audio_cache = !matches.opt_present("disable-audio-cache");
let cache = matches.opt_str("c").map(|cache_location| {
Cache::new(PathBuf::from(cache_location), use_audio_cache)
});
let cache = matches
.opt_str("c")
.map(|cache_location| Cache::new(PathBuf::from(cache_location), use_audio_cache));
let credentials = {
let cached_credentials = cache.as_ref().and_then(Cache::credentials);
@ -188,7 +241,7 @@ fn setup(args: &[String]) -> Setup {
matches.opt_str("username"),
matches.opt_str("password"),
cached_credentials,
password
password,
)
};
@ -202,21 +255,26 @@ fn setup(args: &[String]) -> Setup {
};
let player_config = {
let bitrate = matches.opt_str("b").as_ref()
let bitrate = matches
.opt_str("b")
.as_ref()
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
.unwrap_or(Bitrate::default());
PlayerConfig {
bitrate: bitrate,
normalisation: matches.opt_present("enable-volume-normalisation"),
normalisation_pregain: matches.opt_str("normalisation-pregain")
normalisation_pregain: matches
.opt_str("normalisation-pregain")
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
.unwrap_or(PlayerConfig::default().normalisation_pregain),
}
};
let connect_config = {
let device_type = matches.opt_str("device-type").as_ref()
let device_type = matches
.opt_str("device-type")
.as_ref()
.map(|device_type| DeviceType::from_str(device_type).expect("Invalid device type"))
.unwrap_or(DeviceType::default());
@ -259,7 +317,7 @@ struct Main {
spirc: Option<Spirc>,
spirc_task: Option<SpircTask>,
connect: Box<Future<Item=Session, Error=io::Error>>,
connect: Box<Future<Item = Session, Error = io::Error>>,
shutdown: bool,
@ -345,9 +403,10 @@ impl Future for Main {
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, move || {
(backend)(device)
});
let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
self.spirc = Some(spirc);

View file

@ -1,6 +1,6 @@
use std::process::Command;
use std::collections::HashMap;
use librespot::playback::player::PlayerEvent;
use std::collections::HashMap;
use std::process::Command;
fn run_program(program: &str, env_vars: HashMap<&str, String>) {
let mut v: Vec<&str> = program.split_whitespace().collect();
@ -15,16 +15,19 @@ fn run_program(program: &str, env_vars: HashMap<&str, String>) {
pub fn run_program_on_events(event: PlayerEvent, onevent: &str) {
let mut env_vars = HashMap::new();
match event {
PlayerEvent::Changed { old_track_id, new_track_id } => {
PlayerEvent::Changed {
old_track_id,
new_track_id,
} => {
env_vars.insert("PLAYER_EVENT", "change".to_string());
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
env_vars.insert("TRACK_ID", new_track_id.to_base16());
},
}
PlayerEvent::Started { track_id } => {
env_vars.insert("PLAYER_EVENT", "start".to_string());
env_vars.insert("TRACK_ID", track_id.to_base16());
}
PlayerEvent::Stopped { track_id } => {
PlayerEvent::Stopped { track_id } => {
env_vars.insert("PLAYER_EVENT", "stop".to_string());
env_vars.insert("TRACK_ID", track_id.to_base16());
}