ソースを参照

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 9 年 前
コミット
9274a6bfb3
4 ファイル変更69 行追加30 行削除
  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)