|
@@ -2,8 +2,10 @@ use super::{Open, Sink};
|
|
|
use std::io;
|
|
|
use libpulse_sys::*;
|
|
|
use std::ptr::{null, null_mut};
|
|
|
-use std::mem::{transmute};
|
|
|
use std::ffi::CString;
|
|
|
+use std::ffi::CStr;
|
|
|
+use std::mem;
|
|
|
+use libc;
|
|
|
|
|
|
pub struct PulseAudioSink {
|
|
|
s : *mut pa_simple,
|
|
@@ -12,6 +14,39 @@ pub struct PulseAudioSink {
|
|
|
desc : CString
|
|
|
}
|
|
|
|
|
|
+fn call_pulseaudio<T, F, FailCheck>(f: F, fail_check: FailCheck, kind: io::ErrorKind) -> io::Result<T> where
|
|
|
+ T: Copy,
|
|
|
+ F: Fn(*mut libc::c_int) -> T,
|
|
|
+ FailCheck: Fn(T) -> bool,
|
|
|
+{
|
|
|
+ let mut error: libc::c_int = 0;
|
|
|
+ let ret = f(&mut error);
|
|
|
+ if fail_check(ret) {
|
|
|
+ let err_cstr = unsafe { CStr::from_ptr(pa_strerror(error)) };
|
|
|
+ let errstr = err_cstr.to_string_lossy().into_owned();
|
|
|
+ Err(io::Error::new(kind, errstr))
|
|
|
+ } else {
|
|
|
+ Ok(ret)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl PulseAudioSink {
|
|
|
+ fn free_connection(&mut self) {
|
|
|
+ if self.s != null_mut() {
|
|
|
+ unsafe {
|
|
|
+ pa_simple_free(self.s);
|
|
|
+ }
|
|
|
+ self.s = null_mut();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Drop for PulseAudioSink {
|
|
|
+ fn drop(&mut self) {
|
|
|
+ self.free_connection();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
impl Open for PulseAudioSink {
|
|
|
fn open(device: Option<String>) -> PulseAudioSink {
|
|
|
debug!("Using PulseAudio sink");
|
|
@@ -27,7 +62,7 @@ impl Open for PulseAudioSink {
|
|
|
};
|
|
|
|
|
|
let name = CString::new("librespot").unwrap();
|
|
|
- let description = CString::new("A spoty client library").unwrap();
|
|
|
+ let description = CString::new("Spotify endpoint").unwrap();
|
|
|
|
|
|
PulseAudioSink {
|
|
|
s: null_mut(),
|
|
@@ -41,38 +76,43 @@ impl Open for PulseAudioSink {
|
|
|
impl Sink for PulseAudioSink {
|
|
|
fn start(&mut self) -> io::Result<()> {
|
|
|
if self.s == null_mut() {
|
|
|
- self.s = unsafe {
|
|
|
- pa_simple_new(null(), // Use the default server.
|
|
|
- self.name.as_ptr(), // Our application's name.
|
|
|
- PA_STREAM_PLAYBACK,
|
|
|
- null(), // Use the default device.
|
|
|
- self.desc.as_ptr(), // desc of our stream.
|
|
|
- &self.ss, // Our sample format.
|
|
|
- null(), // Use default channel map
|
|
|
- null(), // Use default buffering attributes.
|
|
|
- null_mut(), // Ignore error code.
|
|
|
- )
|
|
|
- };
|
|
|
- assert!(self.s != null_mut());
|
|
|
+ self.s = call_pulseaudio(
|
|
|
+ |err| unsafe {
|
|
|
+ pa_simple_new(null(), // Use the default server.
|
|
|
+ self.name.as_ptr(), // Our application's name.
|
|
|
+ PA_STREAM_PLAYBACK,
|
|
|
+ null(), // Use the default device.
|
|
|
+ self.desc.as_ptr(), // desc of our stream.
|
|
|
+ &self.ss, // Our sample format.
|
|
|
+ null(), // Use default channel map
|
|
|
+ null(), // Use default buffering attributes.
|
|
|
+ err)
|
|
|
+ },
|
|
|
+ |ptr| ptr == null_mut(),
|
|
|
+ io::ErrorKind::ConnectionRefused)?;
|
|
|
}
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
fn stop(&mut self) -> io::Result<()> {
|
|
|
- unsafe {
|
|
|
- pa_simple_free(self.s);
|
|
|
- }
|
|
|
- self.s = null_mut();
|
|
|
+ self.free_connection();
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
|
|
- unsafe {
|
|
|
- let ptr = transmute(data.as_ptr());
|
|
|
- let bytes = data.len() as usize * 2;
|
|
|
- pa_simple_write(self.s, ptr, bytes, null_mut());
|
|
|
- };
|
|
|
-
|
|
|
- Ok(())
|
|
|
+ if self.s == null_mut() {
|
|
|
+ Err(io::Error::new(io::ErrorKind::NotConnected, "Not connected to pulseaudio"))
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ let ptr = data.as_ptr() as *const libc::c_void;
|
|
|
+ let len = data.len() as usize * mem::size_of::<i16>();
|
|
|
+ call_pulseaudio(
|
|
|
+ |err| unsafe {
|
|
|
+ pa_simple_write(self.s, ptr, len, err)
|
|
|
+ },
|
|
|
+ |ret| ret < 0,
|
|
|
+ io::ErrorKind::BrokenPipe)?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
}
|
|
|
}
|