mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Refactor audio output to make it more modular.
This makes the player less hard coded to portaudio, and easier to experiment with different backends.
This commit is contained in:
parent
4b73f83c5e
commit
9274a6bfb3
4 changed files with 69 additions and 30 deletions
46
src/audio_sink.rs
Normal file
46
src/audio_sink.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue