mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Format according to rustfmt
This commit is contained in:
parent
c3745a958a
commit
237ef1e4f9
22 changed files with 502 additions and 360 deletions
|
@ -7,8 +7,9 @@ use std::ops::Add;
|
||||||
|
|
||||||
use core::audio_key::AudioKey;
|
use core::audio_key::AudioKey;
|
||||||
|
|
||||||
const AUDIO_AESIV: &'static [u8] = &[0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8,
|
const AUDIO_AESIV: &'static [u8] = &[
|
||||||
0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93];
|
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93
|
||||||
|
];
|
||||||
|
|
||||||
pub struct AudioDecrypt<T: io::Read> {
|
pub struct AudioDecrypt<T: io::Read> {
|
||||||
cipher: Box<SynchronousStreamCipher + 'static>,
|
cipher: Box<SynchronousStreamCipher + 'static>,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use bit_set::BitSet;
|
use bit_set::BitSet;
|
||||||
use byteorder::{ByteOrder, BigEndian, WriteBytesExt};
|
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||||
|
use futures::{Async, Future, Poll};
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use futures::sync::{oneshot, mpsc};
|
use futures::sync::{mpsc, oneshot};
|
||||||
use futures::{Poll, Async, Future};
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::fs;
|
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 std::sync::{Arc, Condvar, Mutex};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
@ -71,7 +71,12 @@ impl AudioFileOpenStreaming {
|
||||||
let (seek_tx, seek_rx) = mpsc::unbounded();
|
let (seek_tx, seek_rx) = mpsc::unbounded();
|
||||||
|
|
||||||
let fetcher = AudioFileFetch::new(
|
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);
|
self.session.spawn(move |_| fetcher);
|
||||||
|
|
||||||
|
@ -148,14 +153,16 @@ impl AudioFile {
|
||||||
|
|
||||||
let session_ = session.clone();
|
let session_ = session.clone();
|
||||||
session.spawn(move |_| {
|
session.spawn(move |_| {
|
||||||
complete_rx.map(move |mut file| {
|
complete_rx
|
||||||
|
.map(move |mut file| {
|
||||||
if let Some(cache) = session_.cache() {
|
if let Some(cache) = session_.cache() {
|
||||||
cache.save_file(file_id, &mut file);
|
cache.save_file(file_id, &mut file);
|
||||||
debug!("File {} complete, saving to cache", file_id);
|
debug!("File {} complete, saving to cache", file_id);
|
||||||
} else {
|
} else {
|
||||||
debug!("File {} complete", file_id);
|
debug!("File {} complete", file_id);
|
||||||
}
|
}
|
||||||
}).or_else(|oneshot::Canceled| Ok(()))
|
})
|
||||||
|
.or_else(|oneshot::Canceled| Ok(()))
|
||||||
});
|
});
|
||||||
|
|
||||||
AudioFileOpen::Streaming(open)
|
AudioFileOpen::Streaming(open)
|
||||||
|
@ -200,11 +207,14 @@ struct AudioFileFetch {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFileFetch {
|
impl AudioFileFetch {
|
||||||
fn new(session: Session, shared: Arc<AudioFileShared>,
|
fn new(
|
||||||
data_rx: ChannelData, output: NamedTempFile,
|
session: Session,
|
||||||
|
shared: Arc<AudioFileShared>,
|
||||||
|
data_rx: ChannelData,
|
||||||
|
output: NamedTempFile,
|
||||||
seek_rx: mpsc::UnboundedReceiver<u64>,
|
seek_rx: mpsc::UnboundedReceiver<u64>,
|
||||||
complete_tx: oneshot::Sender<NamedTempFile>) -> AudioFileFetch
|
complete_tx: oneshot::Sender<NamedTempFile>,
|
||||||
{
|
) -> AudioFileFetch {
|
||||||
AudioFileFetch {
|
AudioFileFetch {
|
||||||
session: session,
|
session: session,
|
||||||
shared: shared,
|
shared: shared,
|
||||||
|
@ -233,8 +243,11 @@ impl AudioFileFetch {
|
||||||
|
|
||||||
let offset = self.index * CHUNK_SIZE;
|
let offset = self.index * CHUNK_SIZE;
|
||||||
|
|
||||||
self.output.as_mut().unwrap()
|
self.output
|
||||||
.seek(SeekFrom::Start(offset as u64)).unwrap();
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.seek(SeekFrom::Start(offset as u64))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let (_headers, data) = request_chunk(&self.session, self.shared.file_id, self.index).split();
|
let (_headers, data) = request_chunk(&self.session, self.shared.file_id, self.index).split();
|
||||||
self.data_rx = data;
|
self.data_rx = data;
|
||||||
|
@ -275,13 +288,20 @@ impl Future for AudioFileFetch {
|
||||||
Ok(Async::Ready(Some(data))) => {
|
Ok(Async::Ready(Some(data))) => {
|
||||||
progress = true;
|
progress = true;
|
||||||
|
|
||||||
self.output.as_mut().unwrap()
|
self.output
|
||||||
.write_all(data.as_ref()).unwrap();
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.write_all(data.as_ref())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
progress = true;
|
progress = true;
|
||||||
|
|
||||||
trace!("chunk {} / {} complete", self.index, self.shared.chunk_count);
|
trace!(
|
||||||
|
"chunk {} / {} complete",
|
||||||
|
self.index,
|
||||||
|
self.shared.chunk_count
|
||||||
|
);
|
||||||
|
|
||||||
let full = {
|
let full = {
|
||||||
let mut bitmap = self.shared.bitmap.lock().unwrap();
|
let mut bitmap = self.shared.bitmap.lock().unwrap();
|
||||||
|
@ -303,7 +323,7 @@ impl Future for AudioFileFetch {
|
||||||
Err(ChannelError) => {
|
Err(ChannelError) => {
|
||||||
warn!("error from channel");
|
warn!("error from channel");
|
||||||
return Ok(Async::Ready(()));
|
return Ok(Async::Ready(()));
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !progress {
|
if !progress {
|
||||||
|
|
|
@ -2,16 +2,17 @@ extern crate lewton;
|
||||||
|
|
||||||
use self::lewton::inside_ogg::OggStreamReader;
|
use self::lewton::inside_ogg::OggStreamReader;
|
||||||
|
|
||||||
use std::io::{Read, Seek};
|
|
||||||
use std::fmt;
|
|
||||||
use std::error;
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
|
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
|
||||||
pub struct VorbisPacket(Vec<i16>);
|
pub struct VorbisPacket(Vec<i16>);
|
||||||
pub struct VorbisError(lewton::VorbisError);
|
pub struct VorbisError(lewton::VorbisError);
|
||||||
|
|
||||||
impl <R> VorbisDecoder<R>
|
impl<R> VorbisDecoder<R>
|
||||||
where R: Read + Seek
|
where
|
||||||
|
R: Read + Seek,
|
||||||
{
|
{
|
||||||
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
||||||
Ok(VorbisDecoder(OggStreamReader::new(input)?))
|
Ok(VorbisDecoder(OggStreamReader::new(input)?))
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
#[macro_use] extern crate log;
|
#[macro_use]
|
||||||
#[macro_use] extern crate futures;
|
extern crate futures;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
extern crate bit_set;
|
extern crate bit_set;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate crypto;
|
extern crate crypto;
|
||||||
extern crate num_traits;
|
|
||||||
extern crate num_bigint;
|
extern crate num_bigint;
|
||||||
|
extern crate num_traits;
|
||||||
extern crate tempfile;
|
extern crate tempfile;
|
||||||
|
|
||||||
extern crate librespot_core as core;
|
extern crate librespot_core as core;
|
||||||
|
|
||||||
mod fetch;
|
|
||||||
mod decrypt;
|
mod decrypt;
|
||||||
|
mod fetch;
|
||||||
|
|
||||||
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
||||||
mod lewton_decoder;
|
mod lewton_decoder;
|
||||||
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
||||||
mod libvorbis_decoder;
|
mod libvorbis_decoder;
|
||||||
|
|
||||||
pub use fetch::{AudioFile, AudioFileOpen};
|
|
||||||
pub use decrypt::AudioDecrypt;
|
pub use decrypt::AudioDecrypt;
|
||||||
|
pub use fetch::{AudioFile, AudioFileOpen};
|
||||||
|
|
||||||
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
#[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"))]
|
#[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")]
|
||||||
#[cfg(feature = "with-tremor")] extern crate tremor as vorbis;
|
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::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
pub struct VorbisDecoder<R: Read + Seek>(vorbis::Decoder<R>);
|
pub struct VorbisDecoder<R: Read + Seek>(vorbis::Decoder<R>);
|
||||||
pub struct VorbisPacket(vorbis::Packet);
|
pub struct VorbisPacket(vorbis::Packet);
|
||||||
pub struct VorbisError(vorbis::VorbisError);
|
pub struct VorbisError(vorbis::VorbisError);
|
||||||
|
|
||||||
impl <R> VorbisDecoder<R>
|
impl<R> VorbisDecoder<R>
|
||||||
where R: Read + Seek
|
where
|
||||||
|
R: Read + Seek,
|
||||||
{
|
{
|
||||||
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
||||||
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))
|
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))
|
||||||
|
|
|
@ -6,9 +6,9 @@ use tokio_core::reactor::Core;
|
||||||
|
|
||||||
use librespot::core::authentication::Credentials;
|
use librespot::core::authentication::Credentials;
|
||||||
use librespot::core::config::SessionConfig;
|
use librespot::core::config::SessionConfig;
|
||||||
use librespot::playback::config::PlayerConfig;
|
|
||||||
use librespot::core::session::Session;
|
use librespot::core::session::Session;
|
||||||
use librespot::core::spotify_id::SpotifyId;
|
use librespot::core::spotify_id::SpotifyId;
|
||||||
|
use librespot::playback::config::PlayerConfig;
|
||||||
|
|
||||||
use librespot::playback::audio_backend;
|
use librespot::playback::audio_backend;
|
||||||
use librespot::playback::player::Player;
|
use librespot::playback::player::Player;
|
||||||
|
@ -20,7 +20,7 @@ fn main() {
|
||||||
let session_config = SessionConfig::default();
|
let session_config = SessionConfig::default();
|
||||||
let player_config = PlayerConfig::default();
|
let player_config = PlayerConfig::default();
|
||||||
|
|
||||||
let args : Vec<_> = env::args().collect();
|
let args: Vec<_> = env::args().collect();
|
||||||
if args.len() != 4 {
|
if args.len() != 4 {
|
||||||
println!("Usage: {} USERNAME PASSWORD TRACK", args[0]);
|
println!("Usage: {} USERNAME PASSWORD TRACK", args[0]);
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,12 @@ fn main() {
|
||||||
let backend = audio_backend::find(None).unwrap();
|
let backend = audio_backend::find(None).unwrap();
|
||||||
|
|
||||||
println!("Connecting ..");
|
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...");
|
println!("Playing...");
|
||||||
core.run(player.load(track, true, 0)).unwrap();
|
core.run(player.load(track, true, 0)).unwrap();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use alsa::{Access, Format, Mode, Stream, PCM};
|
||||||
use std::io;
|
use std::io;
|
||||||
use alsa::{PCM, Stream, Mode, Format, Access};
|
|
||||||
|
|
||||||
pub struct AlsaSink(Option<PCM>, String);
|
pub struct AlsaSink(Option<PCM>, String);
|
||||||
|
|
||||||
|
@ -17,14 +17,22 @@ impl Open for AlsaSink {
|
||||||
impl Sink for AlsaSink {
|
impl Sink for AlsaSink {
|
||||||
fn start(&mut self) -> io::Result<()> {
|
fn start(&mut self) -> io::Result<()> {
|
||||||
if self.0.is_none() {
|
if self.0.is_none() {
|
||||||
match PCM::open(&*self.1,
|
match PCM::open(
|
||||||
Stream::Playback, Mode::Blocking,
|
&*self.1,
|
||||||
Format::Signed16, Access::Interleaved,
|
Stream::Playback,
|
||||||
2, 44100) {
|
Mode::Blocking,
|
||||||
|
Format::Signed16,
|
||||||
|
Access::Interleaved,
|
||||||
|
2,
|
||||||
|
44100,
|
||||||
|
) {
|
||||||
Ok(f) => self.0 = Some(f),
|
Ok(f) => self.0 = Some(f),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Alsa error PCM open {}", 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,11 +1,12 @@
|
||||||
use std::io;
|
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
use jack::prelude::{AudioOutPort, AudioOutSpec, Client, JackControl, ProcessScope, AsyncClient, client_options, ProcessHandler, Port };
|
use jack::prelude::{client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port,
|
||||||
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
|
ProcessHandler, ProcessScope};
|
||||||
|
use std::io;
|
||||||
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||||
|
|
||||||
pub struct JackSink {
|
pub struct JackSink {
|
||||||
send: SyncSender<i16>,
|
send: SyncSender<i16>,
|
||||||
active_client: AsyncClient<(),JackData>,
|
active_client: AsyncClient<(), JackData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct JackData {
|
pub struct JackData {
|
||||||
|
@ -43,14 +44,25 @@ impl Open for JackSink {
|
||||||
|
|
||||||
let client_name = client_name.unwrap_or("librespot".to_string());
|
let client_name = client_name.unwrap_or("librespot".to_string());
|
||||||
let (client, _status) = Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap();
|
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_r = client
|
||||||
let ch_l = client.register_port("out_1", AudioOutSpec::default()).unwrap();
|
.register_port("out_0", AudioOutSpec::default())
|
||||||
|
.unwrap();
|
||||||
|
let ch_l = client
|
||||||
|
.register_port("out_1", AudioOutSpec::default())
|
||||||
|
.unwrap();
|
||||||
// buffer for samples from librespot (~10ms)
|
// buffer for samples from librespot (~10ms)
|
||||||
let (tx, rx) = sync_channel(2*1024*4);
|
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();
|
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;
|
mod pipe;
|
||||||
use self::pipe::StdoutSink;
|
use self::pipe::StdoutSink;
|
||||||
|
|
||||||
pub const BACKENDS : &'static [
|
pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] = &[
|
||||||
(&'static str, fn(Option<String>) -> Box<Sink>)
|
|
||||||
] = &[
|
|
||||||
#[cfg(feature = "alsa-backend")]
|
#[cfg(feature = "alsa-backend")]
|
||||||
("alsa", mk_sink::<AlsaSink>),
|
("alsa", mk_sink::<AlsaSink>),
|
||||||
#[cfg(feature = "portaudio-backend")]
|
#[cfg(feature = "portaudio-backend")]
|
||||||
|
@ -53,8 +51,16 @@ pub const BACKENDS : &'static [
|
||||||
|
|
||||||
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<Sink>> {
|
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<Sink>> {
|
||||||
if let Some(name) = name {
|
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 {
|
} 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<()> {
|
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||||
let data: &[u8] = unsafe {
|
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)?;
|
self.0.write_all(data)?;
|
||||||
|
@ -37,4 +40,3 @@ impl Sink for StdoutSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
use super::{Open, Sink};
|
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::io;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::time::Duration;
|
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 count = portaudio_rs::device::get_count().unwrap();
|
||||||
let devices = (0..count)
|
let devices = (0..count)
|
||||||
.filter_map(|idx| {
|
.filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info)))
|
||||||
portaudio_rs::device::get_info(idx).map(|info| (idx, info))
|
.filter(|&(_, ref info)| info.max_output_channels > 0);
|
||||||
}).filter(|&(_, ref info)| {
|
|
||||||
info.max_output_channels > 0
|
|
||||||
});
|
|
||||||
|
|
||||||
Box::new(devices)
|
Box::new(devices)
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,8 @@ fn find_output(device: &str) -> Option<DeviceIndex> {
|
||||||
.map(|(idx, _)| idx)
|
.map(|(idx, _)| idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a> Open for PortAudioSink<'a> {
|
impl<'a> Open for PortAudioSink<'a> {
|
||||||
fn open(device: Option<String>) -> PortAudioSink<'a> {
|
fn open(device: Option<String>) -> PortAudioSink<'a> {
|
||||||
|
|
||||||
debug!("Using PortAudio sink");
|
debug!("Using PortAudio sink");
|
||||||
|
|
||||||
portaudio_rs::initialize().unwrap();
|
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<()> {
|
fn start(&mut self) -> io::Result<()> {
|
||||||
if self.0.is_none() {
|
if self.0.is_none() {
|
||||||
self.0 = Some(Stream::open(
|
self.0 = Some(
|
||||||
None, Some(self.1),
|
Stream::open(
|
||||||
|
None,
|
||||||
|
Some(self.1),
|
||||||
44100.0,
|
44100.0,
|
||||||
FRAMES_PER_BUFFER_UNSPECIFIED,
|
FRAMES_PER_BUFFER_UNSPECIFIED,
|
||||||
StreamFlags::empty(),
|
StreamFlags::empty(),
|
||||||
None
|
None,
|
||||||
).unwrap());;
|
).unwrap(),
|
||||||
|
);;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.0.as_mut().unwrap().start().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<()> {
|
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||||
match self.0.as_mut().unwrap().write(data) {
|
match self.0.as_mut().unwrap().write(data) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(portaudio_rs::PaError::OutputUnderflowed) =>
|
Err(portaudio_rs::PaError::OutputUnderflowed) => error!("PortAudio write underflow"),
|
||||||
error!("PortAudio write underflow"),
|
|
||||||
Err(e) => panic!("PA Error {}", e),
|
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) {
|
fn drop(&mut self) {
|
||||||
portaudio_rs::terminate().unwrap();
|
portaudio_rs::terminate().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
use super::{Open, Sink};
|
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 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 {
|
pub struct PulseAudioSink {
|
||||||
s : *mut pa_simple,
|
s: *mut pa_simple,
|
||||||
ss : pa_sample_spec,
|
ss: pa_sample_spec,
|
||||||
name : CString,
|
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,
|
T: Copy,
|
||||||
F: Fn(*mut libc::c_int) -> T,
|
F: Fn(*mut libc::c_int) -> T,
|
||||||
FailCheck: Fn(T) -> bool,
|
FailCheck: Fn(T) -> bool,
|
||||||
|
@ -58,7 +59,7 @@ impl Open for PulseAudioSink {
|
||||||
let ss = pa_sample_spec {
|
let ss = pa_sample_spec {
|
||||||
format: PA_SAMPLE_S16LE,
|
format: PA_SAMPLE_S16LE,
|
||||||
channels: 2, // stereo
|
channels: 2, // stereo
|
||||||
rate: 44100
|
rate: 44100,
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = CString::new("librespot").unwrap();
|
let name = CString::new("librespot").unwrap();
|
||||||
|
@ -68,7 +69,7 @@ impl Open for PulseAudioSink {
|
||||||
s: null_mut(),
|
s: null_mut(),
|
||||||
ss: ss,
|
ss: ss,
|
||||||
name: name,
|
name: name,
|
||||||
desc: description
|
desc: description,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +79,8 @@ impl Sink for PulseAudioSink {
|
||||||
if self.s == null_mut() {
|
if self.s == null_mut() {
|
||||||
self.s = call_pulseaudio(
|
self.s = call_pulseaudio(
|
||||||
|err| unsafe {
|
|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.
|
self.name.as_ptr(), // Our application's name.
|
||||||
PA_STREAM_PLAYBACK,
|
PA_STREAM_PLAYBACK,
|
||||||
null(), // Use the default device.
|
null(), // Use the default device.
|
||||||
|
@ -86,10 +88,12 @@ impl Sink for PulseAudioSink {
|
||||||
&self.ss, // Our sample format.
|
&self.ss, // Our sample format.
|
||||||
null(), // Use default channel map
|
null(), // Use default channel map
|
||||||
null(), // Use default buffering attributes.
|
null(), // Use default buffering attributes.
|
||||||
err)
|
err,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|ptr| ptr == null_mut(),
|
|ptr| ptr == null_mut(),
|
||||||
io::ErrorKind::ConnectionRefused)?;
|
io::ErrorKind::ConnectionRefused,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -101,17 +105,18 @@ impl Sink for PulseAudioSink {
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||||
if self.s == null_mut() {
|
if self.s == null_mut() {
|
||||||
Err(io::Error::new(io::ErrorKind::NotConnected, "Not connected to pulseaudio"))
|
Err(io::Error::new(
|
||||||
}
|
io::ErrorKind::NotConnected,
|
||||||
else {
|
"Not connected to pulseaudio",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
let ptr = data.as_ptr() as *const libc::c_void;
|
let ptr = data.as_ptr() as *const libc::c_void;
|
||||||
let len = data.len() as usize * mem::size_of::<i16>();
|
let len = data.len() as usize * mem::size_of::<i16>();
|
||||||
call_pulseaudio(
|
call_pulseaudio(
|
||||||
|err| unsafe {
|
|err| unsafe { pa_simple_write(self.s, ptr, len, err) },
|
||||||
pa_simple_write(self.s, ptr, len, err)
|
|
||||||
},
|
|
||||||
|ret| ret < 0,
|
|ret| ret < 0,
|
||||||
io::ErrorKind::BrokenPipe)?;
|
io::ErrorKind::BrokenPipe,
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#[macro_use] extern crate log;
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
extern crate futures;
|
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
extern crate futures;
|
||||||
|
|
||||||
#[cfg(feature = "alsa-backend")]
|
#[cfg(feature = "alsa-backend")]
|
||||||
extern crate alsa;
|
extern crate alsa;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
pub trait Mixer : Send {
|
pub trait Mixer: Send {
|
||||||
fn open() -> Self where Self: Sized;
|
fn open() -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
fn start(&self);
|
fn start(&self);
|
||||||
fn stop(&self);
|
fn stop(&self);
|
||||||
fn set_volume(&self, volume: u16);
|
fn set_volume(&self, volume: u16);
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use super::Mixer;
|
|
||||||
use super::AudioFilter;
|
use super::AudioFilter;
|
||||||
|
use super::Mixer;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SoftMixer {
|
pub struct SoftMixer {
|
||||||
volume: Arc<AtomicUsize>
|
volume: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixer for SoftMixer {
|
impl Mixer for SoftMixer {
|
||||||
fn open() -> SoftMixer {
|
fn open() -> SoftMixer {
|
||||||
SoftMixer {
|
SoftMixer {
|
||||||
volume: Arc::new(AtomicUsize::new(0xFFFF))
|
volume: Arc::new(AtomicUsize::new(0xFFFF)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn start(&self) {
|
fn start(&self) {}
|
||||||
}
|
fn stop(&self) {}
|
||||||
fn stop(&self) {
|
|
||||||
}
|
|
||||||
fn volume(&self) -> u16 {
|
fn volume(&self) -> u16 {
|
||||||
self.volume.load(Ordering::Relaxed) as u16
|
self.volume.load(Ordering::Relaxed) as u16
|
||||||
}
|
}
|
||||||
|
@ -26,12 +24,14 @@ impl Mixer for SoftMixer {
|
||||||
self.volume.store(volume as usize, Ordering::Relaxed);
|
self.volume.store(volume as usize, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
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 {
|
struct SoftVolumeApplier {
|
||||||
volume: Arc<AtomicUsize>
|
volume: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFilter for SoftVolumeApplier {
|
impl AudioFilter for SoftVolumeApplier {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use futures::sync::oneshot;
|
|
||||||
use futures::{future, Future};
|
|
||||||
use futures;
|
use futures;
|
||||||
|
use futures::{future, Future};
|
||||||
|
use futures::sync::oneshot;
|
||||||
use std;
|
use std;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::io::{Read, Seek, SeekFrom, Result};
|
use std::io::{Read, Result, Seek, SeekFrom};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError};
|
use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -14,10 +14,10 @@ use config::{Bitrate, PlayerConfig};
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::spotify_id::SpotifyId;
|
use core::spotify_id::SpotifyId;
|
||||||
|
|
||||||
use audio_backend::Sink;
|
use audio::{AudioDecrypt, AudioFile};
|
||||||
use audio::{AudioFile, AudioDecrypt};
|
|
||||||
use audio::{VorbisDecoder, VorbisPacket};
|
use audio::{VorbisDecoder, VorbisPacket};
|
||||||
use metadata::{FileFormat, Track, Metadata};
|
use audio_backend::Sink;
|
||||||
|
use metadata::{FileFormat, Metadata, Track};
|
||||||
use mixer::AudioFilter;
|
use mixer::AudioFilter;
|
||||||
|
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
|
@ -58,7 +58,7 @@ pub enum PlayerEvent {
|
||||||
|
|
||||||
Stopped {
|
Stopped {
|
||||||
track_id: SpotifyId,
|
track_id: SpotifyId,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerEventChannel = futures::sync::mpsc::UnboundedReceiver<PlayerEvent>;
|
type PlayerEventChannel = futures::sync::mpsc::UnboundedReceiver<PlayerEvent>;
|
||||||
|
@ -74,7 +74,8 @@ struct NormalisationData {
|
||||||
impl NormalisationData {
|
impl NormalisationData {
|
||||||
fn parse_from_file<T: Read + Seek>(mut file: T) -> Result<NormalisationData> {
|
fn parse_from_file<T: Read + Seek>(mut file: T) -> Result<NormalisationData> {
|
||||||
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
|
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_gain_db = file.read_f32::<LittleEndian>().unwrap();
|
||||||
let track_peak = 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 {
|
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 {
|
if normalisation_factor * data.track_peak > 1.0 {
|
||||||
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
|
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
|
||||||
|
@ -107,10 +111,14 @@ impl NormalisationData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new<F>(config: PlayerConfig, session: Session,
|
pub fn new<F>(
|
||||||
|
config: PlayerConfig,
|
||||||
|
session: Session,
|
||||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||||
sink_builder: F) -> (Player, PlayerEventChannel)
|
sink_builder: F,
|
||||||
where F: FnOnce() -> Box<Sink> + Send + 'static
|
) -> (Player, PlayerEventChannel)
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Box<Sink> + Send + 'static,
|
||||||
{
|
{
|
||||||
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
||||||
let (event_sender, event_receiver) = futures::sync::mpsc::unbounded();
|
let (event_sender, event_receiver) = futures::sync::mpsc::unbounded();
|
||||||
|
@ -133,17 +141,25 @@ impl Player {
|
||||||
internal.run();
|
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) {
|
fn command(&self, cmd: PlayerCommand) {
|
||||||
self.commands.as_ref().unwrap().send(cmd).unwrap();
|
self.commands.as_ref().unwrap().send(cmd).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&self, track: SpotifyId, start_playing: bool, position_ms: u32)
|
pub fn load(
|
||||||
-> oneshot::Receiver<()>
|
&self,
|
||||||
{
|
track: SpotifyId,
|
||||||
|
start_playing: bool,
|
||||||
|
position_ms: u32,
|
||||||
|
) -> oneshot::Receiver<()> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.command(PlayerCommand::Load(track, start_playing, position_ms, tx));
|
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() {
|
if let Some(handle) = self.thread_handle.take() {
|
||||||
match handle.join() {
|
match handle.join() {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(_) => error!("Player thread panicked!")
|
Err(_) => error!("Player thread panicked!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +211,9 @@ enum PlayerState {
|
||||||
end_of_track: oneshot::Sender<()>,
|
end_of_track: oneshot::Sender<()>,
|
||||||
normalisation_factor: f32,
|
normalisation_factor: f32,
|
||||||
},
|
},
|
||||||
EndOfTrack { track_id: SpotifyId },
|
EndOfTrack {
|
||||||
|
track_id: SpotifyId,
|
||||||
|
},
|
||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,8 +231,12 @@ impl PlayerState {
|
||||||
use self::PlayerState::*;
|
use self::PlayerState::*;
|
||||||
match *self {
|
match *self {
|
||||||
Stopped | EndOfTrack { .. } => None,
|
Stopped | EndOfTrack { .. } => None,
|
||||||
Paused { ref mut decoder, .. } |
|
Paused {
|
||||||
Playing { ref mut decoder, .. } => Some(decoder),
|
ref mut decoder, ..
|
||||||
|
}
|
||||||
|
| Playing {
|
||||||
|
ref mut decoder, ..
|
||||||
|
} => Some(decoder),
|
||||||
Invalid => panic!("invalid state"),
|
Invalid => panic!("invalid state"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,18 +244,27 @@ impl PlayerState {
|
||||||
fn playing_to_end_of_track(&mut self) {
|
fn playing_to_end_of_track(&mut self) {
|
||||||
use self::PlayerState::*;
|
use self::PlayerState::*;
|
||||||
match mem::replace(self, Invalid) {
|
match mem::replace(self, Invalid) {
|
||||||
Playing { track_id, end_of_track, ..} => {
|
Playing {
|
||||||
|
track_id,
|
||||||
|
end_of_track,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
let _ = end_of_track.send(());
|
let _ = end_of_track.send(());
|
||||||
*self = EndOfTrack { track_id };
|
*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) {
|
fn paused_to_playing(&mut self) {
|
||||||
use self::PlayerState::*;
|
use self::PlayerState::*;
|
||||||
match ::std::mem::replace(self, Invalid) {
|
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 {
|
*self = Playing {
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
decoder: decoder,
|
decoder: decoder,
|
||||||
|
@ -248,7 +279,12 @@ impl PlayerState {
|
||||||
fn playing_to_paused(&mut self) {
|
fn playing_to_paused(&mut self) {
|
||||||
use self::PlayerState::*;
|
use self::PlayerState::*;
|
||||||
match ::std::mem::replace(self, Invalid) {
|
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 {
|
*self = Paused {
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
decoder: decoder,
|
decoder: decoder,
|
||||||
|
@ -265,16 +301,13 @@ impl PlayerInternal {
|
||||||
fn run(mut self) {
|
fn run(mut self) {
|
||||||
loop {
|
loop {
|
||||||
let cmd = if self.state.is_playing() {
|
let cmd = if self.state.is_playing() {
|
||||||
if self.sink_running
|
if self.sink_running {
|
||||||
{
|
|
||||||
match self.commands.try_recv() {
|
match self.commands.try_recv() {
|
||||||
Ok(cmd) => Some(cmd),
|
Ok(cmd) => Some(cmd),
|
||||||
Err(TryRecvError::Empty) => None,
|
Err(TryRecvError::Empty) => None,
|
||||||
Err(TryRecvError::Disconnected) => return,
|
Err(TryRecvError::Disconnected) => return,
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
match self.commands.recv_timeout(Duration::from_secs(5)) {
|
match self.commands.recv_timeout(Duration::from_secs(5)) {
|
||||||
Ok(cmd) => Some(cmd),
|
Ok(cmd) => Some(cmd),
|
||||||
Err(RecvTimeoutError::Timeout) => None,
|
Err(RecvTimeoutError::Timeout) => None,
|
||||||
|
@ -292,14 +325,19 @@ impl PlayerInternal {
|
||||||
self.handle_command(cmd);
|
self.handle_command(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.state.is_playing() && ! self.sink_running {
|
if self.state.is_playing() && !self.sink_running {
|
||||||
self.start_sink();
|
self.start_sink();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.sink_running {
|
if self.sink_running {
|
||||||
let mut current_normalisation_factor: f32 = 1.0;
|
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;
|
current_normalisation_factor = normalisation_factor;
|
||||||
Some(decoder.next_packet().expect("Vorbis error"))
|
Some(decoder.next_packet().expect("Vorbis error"))
|
||||||
} else {
|
} else {
|
||||||
|
@ -369,11 +407,16 @@ impl PlayerInternal {
|
||||||
Some((decoder, normalisation_factor)) => {
|
Some((decoder, normalisation_factor)) => {
|
||||||
if play {
|
if play {
|
||||||
match self.state {
|
match self.state {
|
||||||
PlayerState::Playing { track_id: old_track_id, ..}
|
PlayerState::Playing {
|
||||||
| PlayerState::EndOfTrack { track_id: old_track_id, .. } =>
|
track_id: old_track_id,
|
||||||
self.send_event(PlayerEvent::Changed {
|
..
|
||||||
|
}
|
||||||
|
| PlayerState::EndOfTrack {
|
||||||
|
track_id: old_track_id,
|
||||||
|
..
|
||||||
|
} => self.send_event(PlayerEvent::Changed {
|
||||||
old_track_id: old_track_id,
|
old_track_id: old_track_id,
|
||||||
new_track_id: track_id
|
new_track_id: track_id,
|
||||||
}),
|
}),
|
||||||
_ => self.send_event(PlayerEvent::Started { track_id }),
|
_ => self.send_event(PlayerEvent::Started { track_id }),
|
||||||
}
|
}
|
||||||
|
@ -394,11 +437,16 @@ impl PlayerInternal {
|
||||||
normalisation_factor: normalisation_factor,
|
normalisation_factor: normalisation_factor,
|
||||||
};
|
};
|
||||||
match self.state {
|
match self.state {
|
||||||
PlayerState::Playing { track_id: old_track_id, ..}
|
PlayerState::Playing {
|
||||||
| PlayerState::EndOfTrack { track_id: old_track_id, .. } =>
|
track_id: old_track_id,
|
||||||
self.send_event(PlayerEvent::Changed {
|
..
|
||||||
|
}
|
||||||
|
| PlayerState::EndOfTrack {
|
||||||
|
track_id: old_track_id,
|
||||||
|
..
|
||||||
|
} => self.send_event(PlayerEvent::Changed {
|
||||||
old_track_id: old_track_id,
|
old_track_id: old_track_id,
|
||||||
new_track_id: track_id
|
new_track_id: track_id,
|
||||||
}),
|
}),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -445,21 +493,19 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerCommand::Stop => {
|
PlayerCommand::Stop => match self.state {
|
||||||
match self.state {
|
|
||||||
PlayerState::Playing { track_id, .. }
|
PlayerState::Playing { track_id, .. }
|
||||||
| PlayerState::Paused { track_id, .. }
|
| PlayerState::Paused { track_id, .. }
|
||||||
| PlayerState::EndOfTrack { track_id } => {
|
| PlayerState::EndOfTrack { track_id } => {
|
||||||
self.stop_sink_if_running();
|
self.stop_sink_if_running();
|
||||||
self.send_event(PlayerEvent::Stopped { track_id });
|
self.send_event(PlayerEvent::Stopped { track_id });
|
||||||
self.state = PlayerState::Stopped;
|
self.state = PlayerState::Stopped;
|
||||||
},
|
}
|
||||||
PlayerState::Stopped => {
|
PlayerState::Stopped => {
|
||||||
warn!("Player::stop called from invalid state");
|
warn!("Player::stop called from invalid state");
|
||||||
}
|
}
|
||||||
PlayerState::Invalid => panic!("invalid state"),
|
PlayerState::Invalid => panic!("invalid state"),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,14 +517,16 @@ impl PlayerInternal {
|
||||||
if track.available {
|
if track.available {
|
||||||
Some(Cow::Borrowed(track))
|
Some(Cow::Borrowed(track))
|
||||||
} else {
|
} else {
|
||||||
let alternatives = track.alternatives
|
let alternatives = track
|
||||||
|
.alternatives
|
||||||
.iter()
|
.iter()
|
||||||
.map(|alt_id| {
|
.map(|alt_id| Track::get(&self.session, *alt_id));
|
||||||
Track::get(&self.session, *alt_id)
|
|
||||||
});
|
|
||||||
let alternatives = future::join_all(alternatives).wait().unwrap();
|
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) {
|
let file_id = match track.files.get(&format) {
|
||||||
Some(&file_id) => file_id,
|
Some(&file_id) => file_id,
|
||||||
None => {
|
None => {
|
||||||
warn!("Track \"{}\" is not available in format {:?}", track.name, format);
|
warn!(
|
||||||
|
"Track \"{}\" is not available in format {:?}",
|
||||||
|
track.name, format
|
||||||
|
);
|
||||||
return None;
|
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();
|
let encrypted_file = AudioFile::open(&self.session, file_id).wait().unwrap();
|
||||||
|
|
||||||
|
@ -520,7 +575,7 @@ impl PlayerInternal {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!("Unable to extract normalisation data, using default value.");
|
warn!("Unable to extract normalisation data, using default value.");
|
||||||
1.0 as f32
|
1.0 as f32
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
||||||
|
@ -547,27 +602,15 @@ impl Drop for PlayerInternal {
|
||||||
impl ::std::fmt::Debug for PlayerCommand {
|
impl ::std::fmt::Debug for PlayerCommand {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
PlayerCommand::Load(track, play, position, _) => {
|
PlayerCommand::Load(track, play, position, _) => f.debug_tuple("Load")
|
||||||
f.debug_tuple("Load")
|
|
||||||
.field(&track)
|
.field(&track)
|
||||||
.field(&play)
|
.field(&play)
|
||||||
.field(&position)
|
.field(&position)
|
||||||
.finish()
|
.finish(),
|
||||||
}
|
PlayerCommand::Play => f.debug_tuple("Play").finish(),
|
||||||
PlayerCommand::Play => {
|
PlayerCommand::Pause => f.debug_tuple("Pause").finish(),
|
||||||
f.debug_tuple("Play").finish()
|
PlayerCommand::Stop => f.debug_tuple("Stop").finish(),
|
||||||
}
|
PlayerCommand::Seek(position) => f.debug_tuple("Seek").field(&position).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::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
mod files;
|
mod files;
|
||||||
|
|
||||||
|
@ -7,7 +7,10 @@ fn main() {
|
||||||
for &(path, expected_checksum) in files::FILES {
|
for &(path, expected_checksum) in files::FILES {
|
||||||
let actual = cksum_file(path).unwrap();
|
let actual = cksum_file(path).unwrap();
|
||||||
if expected_checksum != actual {
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,69 +40,37 @@ fn cksum<T: AsRef<[u8]>>(data: T) -> u32 {
|
||||||
!value
|
!value
|
||||||
}
|
}
|
||||||
|
|
||||||
static CRC_LOOKUP_ARRAY : &'static[u32] = &[
|
static CRC_LOOKUP_ARRAY: &'static [u32] = &[
|
||||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
|
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||||
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||||
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
|
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||||
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
|
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||||
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
|
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||||
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
|
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||||
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
|
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||||
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
|
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||||
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
|
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||||
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
|
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||||
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
|
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||||
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
|
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||||
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
|
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||||
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
|
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||||
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
|
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||||
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
|
||||||
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,6 +1,6 @@
|
||||||
// Autogenerated by build.sh
|
// Autogenerated by build.sh
|
||||||
|
|
||||||
pub const FILES : &'static [(&'static str, u32)] = &[
|
pub const FILES: &'static [(&'static str, u32)] = &[
|
||||||
("proto/authentication.proto", 2098196376),
|
("proto/authentication.proto", 2098196376),
|
||||||
("proto/keyexchange.proto", 451735664),
|
("proto/keyexchange.proto", 451735664),
|
||||||
("proto/mercury.proto", 709993906),
|
("proto/mercury.proto", 709993906),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![crate_name = "librespot"]
|
#![crate_name = "librespot"]
|
||||||
|
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
||||||
|
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
|
@ -15,6 +14,6 @@ extern crate url;
|
||||||
pub extern crate librespot_audio as audio;
|
pub extern crate librespot_audio as audio;
|
||||||
pub extern crate librespot_connect as connect;
|
pub extern crate librespot_connect as connect;
|
||||||
pub extern crate librespot_core as core;
|
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_playback as playback;
|
||||||
pub extern crate librespot_protocol as protocol;
|
pub extern crate librespot_protocol as protocol;
|
||||||
pub extern crate librespot_metadata as metadata;
|
|
||||||
|
|
135
src/main.rs
135
src/main.rs
|
@ -1,40 +1,41 @@
|
||||||
#[macro_use] extern crate log;
|
extern crate crypto;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
extern crate librespot;
|
extern crate librespot;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
extern crate rpassword;
|
extern crate rpassword;
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
extern crate tokio_io;
|
extern crate tokio_io;
|
||||||
extern crate tokio_signal;
|
extern crate tokio_signal;
|
||||||
extern crate crypto;
|
|
||||||
|
|
||||||
|
use crypto::digest::Digest;
|
||||||
|
use crypto::sha1::Sha1;
|
||||||
use env_logger::LogBuilder;
|
use env_logger::LogBuilder;
|
||||||
use futures::{Future, Async, Poll, Stream};
|
use futures::{Async, Future, Poll, Stream};
|
||||||
use futures::sync::mpsc::UnboundedReceiver;
|
use futures::sync::mpsc::UnboundedReceiver;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{self, stderr, Write};
|
use std::io::{self, stderr, Write};
|
||||||
|
use std::mem;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tokio_core::reactor::{Handle, Core};
|
use tokio_core::reactor::{Core, Handle};
|
||||||
use tokio_io::IoStream;
|
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::authentication::{get_credentials, Credentials};
|
||||||
use librespot::core::cache::Cache;
|
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::session::Session;
|
||||||
use librespot::core::version;
|
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::audio_backend::{self, Sink, BACKENDS};
|
||||||
use librespot::playback::config::{Bitrate, PlayerConfig};
|
use librespot::playback::config::{Bitrate, PlayerConfig};
|
||||||
use librespot::connect::discovery::{discovery, DiscoveryStream};
|
|
||||||
use librespot::playback::mixer::{self, Mixer};
|
use librespot::playback::mixer::{self, Mixer};
|
||||||
use librespot::playback::player::{Player, PlayerEvent};
|
use librespot::playback::player::{Player, PlayerEvent};
|
||||||
use librespot::connect::spirc::{Spirc, SpircTask};
|
|
||||||
|
|
||||||
mod player_event_handler;
|
mod player_event_handler;
|
||||||
use player_event_handler::run_program_on_events;
|
use player_event_handler::run_program_on_events;
|
||||||
|
@ -102,28 +103,80 @@ struct Setup {
|
||||||
|
|
||||||
fn setup(args: &[String]) -> Setup {
|
fn setup(args: &[String]) -> Setup {
|
||||||
let mut opts = getopts::Options::new();
|
let mut opts = getopts::Options::new();
|
||||||
opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
|
opts.optopt(
|
||||||
.optflag("", "disable-audio-cache", "Disable caching of the audio data.")
|
"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")
|
.reqopt("n", "name", "Device name", "NAME")
|
||||||
.optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
|
.optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
|
||||||
.optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
|
.optopt(
|
||||||
.optopt("", "onevent", "Run PROGRAM when playback is about to begin.", "PROGRAM")
|
"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")
|
.optflag("v", "verbose", "Enable verbose output")
|
||||||
.optopt("u", "username", "Username to sign in with", "USERNAME")
|
.optopt("u", "username", "Username to sign in with", "USERNAME")
|
||||||
.optopt("p", "password", "Password", "PASSWORD")
|
.optopt("p", "password", "Password", "PASSWORD")
|
||||||
.optflag("", "disable-discovery", "Disable discovery mode")
|
.optflag("", "disable-discovery", "Disable discovery mode")
|
||||||
.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
|
.optopt(
|
||||||
.optopt("", "device", "Audio device to use. Use '?' to list options if using portaudio", "DEVICE")
|
"",
|
||||||
|
"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("", "mixer", "Mixer to use", "MIXER")
|
||||||
.optopt("", "initial-volume", "Initial volume in %, once connected (must be from 0 to 100)", "VOLUME")
|
.optopt(
|
||||||
.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")
|
"initial-volume",
|
||||||
.optopt("", "normalisation-pregain", "Pregain (dB) applied by volume normalisation", "PREGAIN");
|
"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..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(f) => {
|
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);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -131,11 +184,13 @@ fn setup(args: &[String]) -> Setup {
|
||||||
let verbose = matches.opt_present("verbose");
|
let verbose = matches.opt_present("verbose");
|
||||||
setup_logging(verbose);
|
setup_logging(verbose);
|
||||||
|
|
||||||
info!("librespot {} ({}). Built on {}. Build ID: {}",
|
info!(
|
||||||
|
"librespot {} ({}). Built on {}. Build ID: {}",
|
||||||
version::short_sha(),
|
version::short_sha(),
|
||||||
version::commit_date(),
|
version::commit_date(),
|
||||||
version::short_now(),
|
version::short_now(),
|
||||||
version::build_id());
|
version::build_id()
|
||||||
|
);
|
||||||
|
|
||||||
let backend_name = matches.opt_str("backend");
|
let backend_name = matches.opt_str("backend");
|
||||||
if backend_name == Some("?".into()) {
|
if backend_name == Some("?".into()) {
|
||||||
|
@ -143,14 +198,12 @@ fn setup(args: &[String]) -> Setup {
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let backend = audio_backend::find(backend_name)
|
let backend = audio_backend::find(backend_name).expect("Invalid backend");
|
||||||
.expect("Invalid backend");
|
|
||||||
|
|
||||||
let device = matches.opt_str("device");
|
let device = matches.opt_str("device");
|
||||||
|
|
||||||
let mixer_name = matches.opt_str("mixer");
|
let mixer_name = matches.opt_str("mixer");
|
||||||
let mixer = mixer::find(mixer_name.as_ref())
|
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
|
||||||
.expect("Invalid mixer");
|
|
||||||
|
|
||||||
let initial_volume = matches
|
let initial_volume = matches
|
||||||
.opt_str("initial-volume")
|
.opt_str("initial-volume")
|
||||||
|
@ -163,17 +216,17 @@ fn setup(args: &[String]) -> Setup {
|
||||||
})
|
})
|
||||||
.unwrap_or(0x8000);
|
.unwrap_or(0x8000);
|
||||||
|
|
||||||
let zeroconf_port =
|
let zeroconf_port = matches
|
||||||
matches.opt_str("zeroconf-port")
|
.opt_str("zeroconf-port")
|
||||||
.map(|port| port.parse::<u16>().unwrap())
|
.map(|port| port.parse::<u16>().unwrap())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let name = matches.opt_str("name").unwrap();
|
let name = matches.opt_str("name").unwrap();
|
||||||
let use_audio_cache = !matches.opt_present("disable-audio-cache");
|
let use_audio_cache = !matches.opt_present("disable-audio-cache");
|
||||||
|
|
||||||
let cache = matches.opt_str("c").map(|cache_location| {
|
let cache = matches
|
||||||
Cache::new(PathBuf::from(cache_location), use_audio_cache)
|
.opt_str("c")
|
||||||
});
|
.map(|cache_location| Cache::new(PathBuf::from(cache_location), use_audio_cache));
|
||||||
|
|
||||||
let credentials = {
|
let credentials = {
|
||||||
let cached_credentials = cache.as_ref().and_then(Cache::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("username"),
|
||||||
matches.opt_str("password"),
|
matches.opt_str("password"),
|
||||||
cached_credentials,
|
cached_credentials,
|
||||||
password
|
password,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -202,21 +255,26 @@ fn setup(args: &[String]) -> Setup {
|
||||||
};
|
};
|
||||||
|
|
||||||
let player_config = {
|
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"))
|
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
|
||||||
.unwrap_or(Bitrate::default());
|
.unwrap_or(Bitrate::default());
|
||||||
|
|
||||||
PlayerConfig {
|
PlayerConfig {
|
||||||
bitrate: bitrate,
|
bitrate: bitrate,
|
||||||
normalisation: matches.opt_present("enable-volume-normalisation"),
|
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"))
|
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
|
||||||
.unwrap_or(PlayerConfig::default().normalisation_pregain),
|
.unwrap_or(PlayerConfig::default().normalisation_pregain),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let connect_config = {
|
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"))
|
.map(|device_type| DeviceType::from_str(device_type).expect("Invalid device type"))
|
||||||
.unwrap_or(DeviceType::default());
|
.unwrap_or(DeviceType::default());
|
||||||
|
|
||||||
|
@ -259,7 +317,7 @@ struct Main {
|
||||||
|
|
||||||
spirc: Option<Spirc>,
|
spirc: Option<Spirc>,
|
||||||
spirc_task: Option<SpircTask>,
|
spirc_task: Option<SpircTask>,
|
||||||
connect: Box<Future<Item=Session, Error=io::Error>>,
|
connect: Box<Future<Item = Session, Error = io::Error>>,
|
||||||
|
|
||||||
shutdown: bool,
|
shutdown: bool,
|
||||||
|
|
||||||
|
@ -345,7 +403,8 @@ impl Future for Main {
|
||||||
|
|
||||||
let audio_filter = mixer.get_audio_filter();
|
let audio_filter = mixer.get_audio_filter();
|
||||||
let backend = self.backend;
|
let backend = self.backend;
|
||||||
let (player, event_channel) = Player::new(player_config, session.clone(), audio_filter, move || {
|
let (player, event_channel) =
|
||||||
|
Player::new(player_config, session.clone(), audio_filter, move || {
|
||||||
(backend)(device)
|
(backend)(device)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::process::Command;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use librespot::playback::player::PlayerEvent;
|
use librespot::playback::player::PlayerEvent;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
fn run_program(program: &str, env_vars: HashMap<&str, String>) {
|
fn run_program(program: &str, env_vars: HashMap<&str, String>) {
|
||||||
let mut v: Vec<&str> = program.split_whitespace().collect();
|
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) {
|
pub fn run_program_on_events(event: PlayerEvent, onevent: &str) {
|
||||||
let mut env_vars = HashMap::new();
|
let mut env_vars = HashMap::new();
|
||||||
match event {
|
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("PLAYER_EVENT", "change".to_string());
|
||||||
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
|
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
|
||||||
env_vars.insert("TRACK_ID", new_track_id.to_base16());
|
env_vars.insert("TRACK_ID", new_track_id.to_base16());
|
||||||
},
|
}
|
||||||
PlayerEvent::Started { track_id } => {
|
PlayerEvent::Started { track_id } => {
|
||||||
env_vars.insert("PLAYER_EVENT", "start".to_string());
|
env_vars.insert("PLAYER_EVENT", "start".to_string());
|
||||||
env_vars.insert("TRACK_ID", track_id.to_base16());
|
env_vars.insert("TRACK_ID", track_id.to_base16());
|
||||||
|
|
Loading…
Reference in a new issue