mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge pull request #165 from librespot-org/rustfmt
Format according to rustfmt guidelines
This commit is contained in:
commit
aa880f8888
23 changed files with 503 additions and 361 deletions
|
@ -22,7 +22,7 @@ before_script:
|
|||
- rustup target add armv7-unknown-linux-gnueabihf
|
||||
- if [[ $TRAVIS_RUST_VERSION == *"nightly"* ]]; then
|
||||
rustup component add rustfmt-preview;
|
||||
cargo fmt --package=librespot-core -- --write-mode=diff;
|
||||
cargo fmt --all -- --write-mode=diff;
|
||||
fi
|
||||
|
||||
script:
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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| {
|
||||
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(()))
|
||||
})
|
||||
.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,
|
||||
fn new(
|
||||
session: Session,
|
||||
shared: Arc<AudioFileShared>,
|
||||
data_rx: ChannelData,
|
||||
output: NamedTempFile,
|
||||
seek_rx: mpsc::UnboundedReceiver<u64>,
|
||||
complete_tx: oneshot::Sender<NamedTempFile>) -> AudioFileFetch
|
||||
{
|
||||
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)) => {
|
||||
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 {
|
||||
|
|
|
@ -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
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
||||
Ok(VorbisDecoder(OggStreamReader::new(input)?))
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
||||
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))
|
||||
|
|
|
@ -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;
|
||||
|
@ -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();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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);
|
||||
|
||||
|
@ -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",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
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>,
|
||||
|
@ -43,14 +44,25 @@ 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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)>> {
|
||||
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)
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ fn find_output(device: &str) -> Option<DeviceIndex> {
|
|||
|
||||
impl<'a> Open for PortAudioSink<'a> {
|
||||
fn open(device: Option<String>) -> PortAudioSink<'a> {
|
||||
|
||||
debug!("Using PortAudio sink");
|
||||
|
||||
portaudio_rs::initialize().unwrap();
|
||||
|
@ -74,13 +73,16 @@ impl <'a> Open 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),
|
||||
self.0 = Some(
|
||||
Stream::open(
|
||||
None,
|
||||
Some(self.1),
|
||||
44100.0,
|
||||
FRAMES_PER_BUFFER_UNSPECIFIED,
|
||||
StreamFlags::empty(),
|
||||
None
|
||||
).unwrap());;
|
||||
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),
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
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,
|
||||
|
@ -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,7 +79,8 @@ impl Sink for PulseAudioSink {
|
|||
if self.s == null_mut() {
|
||||
self.s = call_pulseaudio(
|
||||
|err| unsafe {
|
||||
pa_simple_new(null(), // Use the default server.
|
||||
pa_simple_new(
|
||||
null(), // Use the default server.
|
||||
self.name.as_ptr(), // Our application's name.
|
||||
PA_STREAM_PLAYBACK,
|
||||
null(), // Use the default device.
|
||||
|
@ -86,10 +88,12 @@ impl Sink for PulseAudioSink {
|
|||
&self.ss, // Our sample format.
|
||||
null(), // Use default channel map
|
||||
null(), // Use default buffering attributes.
|
||||
err)
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
pub trait Mixer: Send {
|
||||
fn open() -> Self where Self: Sized;
|
||||
fn open() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn start(&self);
|
||||
fn stop(&self);
|
||||
fn set_volume(&self, volume: u16);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
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,
|
||||
|
@ -299,7 +332,12 @@ impl PlayerInternal {
|
|||
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,11 +407,16 @@ 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 {
|
||||
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
|
||||
new_track_id: track_id,
|
||||
}),
|
||||
_ => self.send_event(PlayerEvent::Started { track_id }),
|
||||
}
|
||||
|
@ -394,11 +437,16 @@ 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 {
|
||||
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
|
||||
new_track_id: track_id,
|
||||
}),
|
||||
_ => (),
|
||||
}
|
||||
|
@ -445,21 +493,19 @@ impl PlayerInternal {
|
|||
}
|
||||
}
|
||||
|
||||
PlayerCommand::Stop => {
|
||||
match self.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")
|
||||
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()
|
||||
}
|
||||
.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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,68 +41,36 @@ fn cksum<T: AsRef<[u8]>>(data: T) -> u32 {
|
|||
}
|
||||
|
||||
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
|
||||
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,
|
||||
];
|
||||
|
|
|
@ -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;
|
||||
|
|
133
src/main.rs
133
src/main.rs
|
@ -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: {}",
|
||||
info!(
|
||||
"librespot {} ({}). Built on {}. Build ID: {}",
|
||||
version::short_sha(),
|
||||
version::commit_date(),
|
||||
version::short_now(),
|
||||
version::build_id());
|
||||
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")
|
||||
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());
|
||||
|
||||
|
@ -345,7 +403,8 @@ 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 || {
|
||||
let (player, event_channel) =
|
||||
Player::new(player_config, session.clone(), audio_filter, move || {
|
||||
(backend)(device)
|
||||
});
|
||||
|
||||
|
|
|
@ -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,11 +15,14 @@ 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());
|
||||
|
|
Loading…
Reference in a new issue