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_decrypt;
 mod audio_file;
 mod audio_file;
 mod audio_key;
 mod audio_key;
+pub mod audio_sink;
 pub mod authentication;
 pub mod authentication;
 mod connection;
 mod connection;
 mod diffie_hellman;
 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::path::{Path, PathBuf};
 use std::thread;
 use std::thread;
 
 
+use librespot::audio_sink::DefaultSink;
 use librespot::authentication::Credentials;
 use librespot::authentication::Credentials;
 use librespot::discovery::DiscoveryManager;
 use librespot::discovery::DiscoveryManager;
 use librespot::player::Player;
 use librespot::player::Player;
@@ -106,10 +107,14 @@ fn main() {
     let reusable_credentials = session.login(credentials).unwrap();
     let reusable_credentials = session.login(credentials).unwrap();
     reusable_credentials.save_to_file(credentials_path);
     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);
     let spirc = SpircManager::new(session.clone(), player);
     thread::spawn(move || spirc.run());
     thread::spawn(move || spirc.run());
 
 
+    portaudio::terminate().unwrap();
+
     loop {
     loop {
         session.poll();
         session.poll();
     }
     }

+ 16 - 29
src/player.rs

@@ -1,14 +1,14 @@
 use eventual::{self, Async};
 use eventual::{self, Async};
-use portaudio;
 use std::borrow::Cow;
 use std::borrow::Cow;
 use std::sync::{mpsc, Mutex, Arc, MutexGuard};
 use std::sync::{mpsc, Mutex, Arc, MutexGuard};
 use std::thread;
 use std::thread;
 use std::io::{Read, Seek};
 use std::io::{Read, Seek};
 use vorbis;
 use vorbis;
 
 
+use audio_decrypt::AudioDecrypt;
+use audio_sink::Sink;
 use metadata::{FileFormat, Track, TrackRef};
 use metadata::{FileFormat, Track, TrackRef};
 use session::{Bitrate, Session};
 use session::{Bitrate, Session};
-use audio_decrypt::AudioDecrypt;
 use util::{self, SpotifyId, Subfile};
 use util::{self, SpotifyId, Subfile};
 use spirc::PlayStatus;
 use spirc::PlayStatus;
 
 
@@ -71,7 +71,8 @@ enum PlayerCommand {
 }
 }
 
 
 impl Player {
 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 (cmd_tx, cmd_rx) = mpsc::channel();
 
 
         let state = Arc::new(Mutex::new(PlayerState {
         let state = Arc::new(Mutex::new(PlayerState {
@@ -92,7 +93,7 @@ impl Player {
             observers: observers.clone(),
             observers: observers.clone(),
         };
         };
 
 
-        thread::spawn(move || internal.run());
+        thread::spawn(move || internal.run(sink_builder()));
 
 
         Player {
         Player {
             commands: cmd_tx,
             commands: cmd_tx,
@@ -154,15 +155,7 @@ fn apply_volume(volume: u16, data: &[i16]) -> Cow<[i16]> {
 }
 }
 
 
 impl PlayerInternal {
 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;
         let mut decoder = None;
 
 
         loop {
         loop {
@@ -177,7 +170,7 @@ impl PlayerInternal {
                 Some(PlayerCommand::Load(track_id, play, position)) => {
                 Some(PlayerCommand::Load(track_id, play, position)) => {
                     self.update(|state| {
                     self.update(|state| {
                         if state.status == PlayStatus::kPlayStatusPlay {
                         if state.status == PlayStatus::kPlayStatusPlay {
-                            stream.stop().unwrap();
+                            sink.stop().unwrap();
                         }
                         }
                         state.end_of_track = false;
                         state.end_of_track = false;
                         state.status = PlayStatus::kPlayStatusPause;
                         state.status = PlayStatus::kPlayStatusPause;
@@ -221,7 +214,7 @@ impl PlayerInternal {
 
 
                     self.update(|state| {
                     self.update(|state| {
                         state.status = if play {
                         state.status = if play {
-                            stream.start().unwrap();
+                            sink.start().unwrap();
                             PlayStatus::kPlayStatusPlay
                             PlayStatus::kPlayStatusPlay
                         } else {
                         } else {
                             PlayStatus::kPlayStatusPause
                             PlayStatus::kPlayStatusPause
@@ -250,7 +243,7 @@ impl PlayerInternal {
                         true
                         true
                     });
                     });
 
 
-                    stream.start().unwrap();
+                    sink.start().unwrap();
                 }
                 }
                 Some(PlayerCommand::Pause) => {
                 Some(PlayerCommand::Pause) => {
                     self.update(|state| {
                     self.update(|state| {
@@ -261,7 +254,7 @@ impl PlayerInternal {
                         true
                         true
                     });
                     });
 
 
-                    stream.stop().unwrap();
+                    sink.stop().unwrap();
                 }
                 }
                 Some(PlayerCommand::Volume(vol)) => {
                 Some(PlayerCommand::Volume(vol)) => {
                     self.update(|state| {
                     self.update(|state| {
@@ -279,22 +272,20 @@ impl PlayerInternal {
                         true
                         true
                     });
                     });
 
 
-                    stream.stop().unwrap();
+                    sink.stop().unwrap();
                     decoder = None;
                     decoder = None;
                 }
                 }
                 None => (),
                 None => (),
             }
             }
 
 
             if self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay {
             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)) => {
                     Some(Ok(packet)) => {
                         let buffer = apply_volume(self.state.lock().unwrap().volume,
                         let buffer = apply_volume(self.state.lock().unwrap().volume,
                                                   &packet.data);
                                                   &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(vorbis::VorbisError::Hole)) => (),
                     Some(Err(e)) => panic!("Vorbis error {:?}", e),
                     Some(Err(e)) => panic!("Vorbis error {:?}", e),
@@ -305,16 +296,12 @@ impl PlayerInternal {
                             true
                             true
                         });
                         });
 
 
-                        stream.stop().unwrap();
+                        sink.stop().unwrap();
                         decoder = None;
                         decoder = None;
                     }
                     }
                 }
                 }
             }
             }
         }
         }
-
-        drop(stream);
-
-        portaudio::terminate().unwrap();
     }
     }
 
 
     fn update<F>(&self, f: F)
     fn update<F>(&self, f: F)