diff --git a/src/audio_sink.rs b/src/audio_sink.rs new file mode 100644 index 00000000..e2e6340b --- /dev/null +++ b/src/audio_sink.rs @@ -0,0 +1,46 @@ +use portaudio; +use std::io; + +pub trait Sink { + fn start(&self) -> io::Result<()>; + fn stop(&self) -> io::Result<()>; + fn write(&self, data: &[i16]) -> io::Result<()>; +} + +pub struct PortAudioSink<'a>(portaudio::stream::Stream<'a, i16, i16>); + +impl <'a> PortAudioSink<'a> { + pub fn open() -> PortAudioSink<'a> { + portaudio::initialize().unwrap(); + + let stream = portaudio::stream::Stream::open_default( + 0, 2, 44100.0, + portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED, + None + ).unwrap(); + + PortAudioSink(stream) + } +} + +impl <'a> Sink for PortAudioSink<'a> { + fn start(&self) -> io::Result<()> { + self.0.start().unwrap(); + Ok(()) + } + fn stop(&self) -> io::Result<()> { + self.0.stop().unwrap(); + Ok(()) + } + fn write(&self, data: &[i16]) -> io::Result<()> { + self.0.write(&data).unwrap(); + Ok(()) + } +} + +impl <'a> Drop for PortAudioSink<'a> { + fn drop(&mut self) { + portaudio::terminate().unwrap(); + } +} + diff --git a/src/lib.in.rs b/src/lib.in.rs index 9c01b6ff..853bbf0e 100644 --- a/src/lib.in.rs +++ b/src/lib.in.rs @@ -2,6 +2,7 @@ mod audio_decrypt; mod audio_file; mod audio_key; +pub mod audio_sink; pub mod authentication; mod connection; mod diffie_hellman; diff --git a/src/main.rs b/src/main.rs index 959108c6..fb574d5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use std::io::{stdout, Read, Write}; use std::path::{Path, PathBuf}; use std::thread; +use librespot::audio_sink::DefaultSink; use librespot::authentication::Credentials; use librespot::discovery::DiscoveryManager; use librespot::player::Player; @@ -106,10 +107,14 @@ fn main() { let reusable_credentials = session.login(credentials).unwrap(); reusable_credentials.save_to_file(credentials_path); - let player = Player::new(session.clone()); + portaudio::initialize().unwrap(); + + let player = Player::new(session.clone(), || DefaultSink::open()); let spirc = SpircManager::new(session.clone(), player); thread::spawn(move || spirc.run()); + portaudio::terminate().unwrap(); + loop { session.poll(); } diff --git a/src/player.rs b/src/player.rs index 82e6e825..8cc326cc 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,14 +1,14 @@ use eventual::{self, Async}; -use portaudio; use std::borrow::Cow; use std::sync::{mpsc, Mutex, Arc, MutexGuard}; use std::thread; use std::io::{Read, Seek}; use vorbis; +use audio_decrypt::AudioDecrypt; +use audio_sink::Sink; use metadata::{FileFormat, Track, TrackRef}; use session::{Bitrate, Session}; -use audio_decrypt::AudioDecrypt; use util::{self, SpotifyId, Subfile}; use spirc::PlayStatus; @@ -71,7 +71,8 @@ enum PlayerCommand { } impl Player { - pub fn new(session: Session) -> Player { + pub fn new(session: Session, sink_builder: F) -> Player + where S: Sink, F: FnOnce() -> S + Send + 'static { let (cmd_tx, cmd_rx) = mpsc::channel(); let state = Arc::new(Mutex::new(PlayerState { @@ -92,7 +93,7 @@ impl Player { observers: observers.clone(), }; - thread::spawn(move || internal.run()); + thread::spawn(move || internal.run(sink_builder())); Player { commands: cmd_tx, @@ -154,15 +155,7 @@ fn apply_volume(volume: u16, data: &[i16]) -> Cow<[i16]> { } impl PlayerInternal { - fn run(self) { - portaudio::initialize().unwrap(); - - let stream = portaudio::stream::Stream::::open_default( - 0, 2, 44100.0, - portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED, - None - ).unwrap(); - + fn run(self, sink: S) { let mut decoder = None; loop { @@ -177,7 +170,7 @@ impl PlayerInternal { Some(PlayerCommand::Load(track_id, play, position)) => { self.update(|state| { if state.status == PlayStatus::kPlayStatusPlay { - stream.stop().unwrap(); + sink.stop().unwrap(); } state.end_of_track = false; state.status = PlayStatus::kPlayStatusPause; @@ -221,7 +214,7 @@ impl PlayerInternal { self.update(|state| { state.status = if play { - stream.start().unwrap(); + sink.start().unwrap(); PlayStatus::kPlayStatusPlay } else { PlayStatus::kPlayStatusPause @@ -250,7 +243,7 @@ impl PlayerInternal { true }); - stream.start().unwrap(); + sink.start().unwrap(); } Some(PlayerCommand::Pause) => { self.update(|state| { @@ -261,7 +254,7 @@ impl PlayerInternal { true }); - stream.stop().unwrap(); + sink.stop().unwrap(); } Some(PlayerCommand::Volume(vol)) => { self.update(|state| { @@ -279,22 +272,20 @@ impl PlayerInternal { true }); - stream.stop().unwrap(); + sink.stop().unwrap(); decoder = None; } None => (), } if self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay { - match decoder.as_mut().unwrap().packets().next() { + let packet = decoder.as_mut().unwrap().packets().next(); + + match packet { Some(Ok(packet)) => { let buffer = apply_volume(self.state.lock().unwrap().volume, &packet.data); - match stream.write(&buffer) { - Ok(_) => (), - Err(portaudio::PaError::OutputUnderflowed) => eprintln!("Underflow"), - Err(e) => panic!("PA Error {}", e), - }; + sink.write(&buffer).unwrap(); } Some(Err(vorbis::VorbisError::Hole)) => (), Some(Err(e)) => panic!("Vorbis error {:?}", e), @@ -305,16 +296,12 @@ impl PlayerInternal { true }); - stream.stop().unwrap(); + sink.stop().unwrap(); decoder = None; } } } } - - drop(stream); - - portaudio::terminate().unwrap(); } fn update(&self, f: F)