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_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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
fn run<S: Sink>(self, sink: S) {
|
||||||
portaudio::initialize().unwrap();
|
|
||||||
|
|
||||||
let stream = portaudio::stream::Stream::<i16, i16>::open_default(
|
|
||||||
0, 2, 44100.0,
|
|
||||||
portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED,
|
|
||||||
None
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
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) {
|
sink.write(&buffer).unwrap();
|
||||||
Ok(_) => (),
|
|
||||||
Err(portaudio::PaError::OutputUnderflowed) => eprintln!("Underflow"),
|
|
||||||
Err(e) => panic!("PA Error {}", e),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
Loading…
Reference in a new issue