Browse Source

Refactor audio output to make it more modular.

This makes the player less hard coded to portaudio, and easier to
experiment with different backends.
Paul Lietar 10 years ago
parent
commit
9274a6bfb3
4 changed files with 69 additions and 30 deletions
  1. 46 0
      src/audio_sink.rs
  2. 1 0
      src/lib.in.rs
  3. 6 1
      src/main.rs
  4. 16 29
      src/player.rs

+ 46 - 0
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();
+    }
+}
+

+ 1 - 0
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;

+ 6 - 1
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();
     }

+ 16 - 29
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<S, F>(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::<i16, i16>::open_default(
-                0, 2, 44100.0,
-                portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED,
-                None
-        ).unwrap();
-
+    fn run<S: Sink>(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<F>(&self, f: F)