mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Make RodioSink
Send
and improve error handling
This commit is contained in:
parent
2f05ddfbc2
commit
b2f1be4374
3 changed files with 78 additions and 43 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1375,6 +1375,7 @@ dependencies = [
|
|||
"rodio",
|
||||
"sdl2",
|
||||
"shell-words",
|
||||
"thiserror",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
|
|
|
@ -29,19 +29,22 @@ libpulse-binding = { version = "2.13", optional = true, default-features
|
|||
libpulse-simple-binding = { version = "2.13", optional = true, default-features = false }
|
||||
jack = { version = "0.6", optional = true }
|
||||
libc = { version = "0.2", optional = true }
|
||||
rodio = { version = "0.13", optional = true, default-features = false }
|
||||
cpal = { version = "0.13", optional = true }
|
||||
sdl2 = { version = "0.34", optional = true }
|
||||
gstreamer = { version = "0.16", optional = true }
|
||||
gstreamer-app = { version = "0.16", optional = true }
|
||||
glib = { version = "0.10", optional = true }
|
||||
zerocopy = { version = "0.3", optional = true }
|
||||
|
||||
# Rodio dependencies
|
||||
rodio = { version = "0.13", optional = true, default-features = false }
|
||||
cpal = { version = "0.13", optional = true }
|
||||
thiserror = { version = "1", optional = true }
|
||||
|
||||
[features]
|
||||
alsa-backend = ["alsa"]
|
||||
portaudio-backend = ["portaudio-rs"]
|
||||
pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"]
|
||||
jackaudio-backend = ["jack"]
|
||||
rodio-backend = ["rodio", "cpal"]
|
||||
rodio-backend = ["rodio", "cpal", "thiserror"]
|
||||
sdl-backend = ["sdl2"]
|
||||
gstreamer-backend = ["gstreamer", "gstreamer-app", "glib", "zerocopy"]
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
use super::{Open, Sink};
|
||||
extern crate cpal;
|
||||
extern crate rodio;
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
use std::process::exit;
|
||||
use std::{convert::Infallible, sync::mpsc};
|
||||
use std::{io, thread, time};
|
||||
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{Open, Sink};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RodioError {
|
||||
#[error("Rodio: no device available")]
|
||||
NoDeviceAvailable,
|
||||
#[error("Rodio: device \"{0}\" is not available")]
|
||||
DeviceNotAvailable(String),
|
||||
#[error("Rodio play error: {0}")]
|
||||
PlayError(#[from] rodio::PlayError),
|
||||
#[error("Rodio stream error: {0}")]
|
||||
StreamError(#[from] rodio::StreamError),
|
||||
#[error("Cannot get audio devices: {0}")]
|
||||
DevicesError(#[from] cpal::DevicesError),
|
||||
}
|
||||
|
||||
pub struct RodioSink {
|
||||
rodio_sink: rodio::Sink,
|
||||
// We have to keep hold of this object, or the Sink can't play...
|
||||
#[allow(dead_code)]
|
||||
stream: rodio::OutputStream,
|
||||
|
||||
// will produce a TryRecvError on the receiver side when it is dropped.
|
||||
_close_tx: mpsc::SyncSender<Infallible>,
|
||||
}
|
||||
|
||||
fn list_formats(ref device: &rodio::Device) {
|
||||
fn list_formats(device: &rodio::Device) {
|
||||
let default_fmt = match device.default_output_config() {
|
||||
Ok(fmt) => cpal::SupportedStreamConfig::from(fmt),
|
||||
Ok(fmt) => fmt,
|
||||
Err(e) => {
|
||||
warn!("Error getting default rodio::Sink config: {}", e);
|
||||
return;
|
||||
|
@ -39,8 +55,8 @@ fn list_formats(ref device: &rodio::Device) {
|
|||
}
|
||||
}
|
||||
|
||||
fn list_outputs() {
|
||||
let default_device = get_default_device();
|
||||
fn list_outputs_and_exit() -> ! {
|
||||
let default_device = get_default_device().unwrap();
|
||||
let default_device_name = default_device.name().expect("cannot get output name");
|
||||
println!("Default Audio Device:\n {}", default_device_name);
|
||||
list_formats(&default_device);
|
||||
|
@ -56,54 +72,69 @@ fn list_outputs() {
|
|||
list_formats(&device);
|
||||
}
|
||||
}
|
||||
|
||||
exit(0)
|
||||
}
|
||||
|
||||
fn get_default_device() -> rodio::Device {
|
||||
fn get_default_device() -> Result<rodio::Device, RodioError> {
|
||||
cpal::default_host()
|
||||
.default_output_device()
|
||||
.expect("no default output device available")
|
||||
.ok_or(RodioError::NoDeviceAvailable)
|
||||
}
|
||||
|
||||
fn match_device(device: Option<String>) -> rodio::Device {
|
||||
match device {
|
||||
fn create_sink(device: Option<String>) -> Result<(rodio::Sink, rodio::OutputStream), RodioError> {
|
||||
let rodio_device = match device {
|
||||
Some(ask) if &ask == "?" => list_outputs_and_exit(),
|
||||
Some(device_name) => {
|
||||
if device_name == "?".to_string() {
|
||||
list_outputs();
|
||||
exit(0)
|
||||
}
|
||||
for d in cpal::default_host()
|
||||
.output_devices()
|
||||
.expect("cannot get list of output devices")
|
||||
{
|
||||
if d.name().expect("cannot get output name") == device_name {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
println!("No output sink matching '{}' found.", device_name);
|
||||
exit(0)
|
||||
}
|
||||
None => return get_default_device(),
|
||||
cpal::default_host()
|
||||
.output_devices()?
|
||||
.find(|d| d.name().ok().map_or(false, |name| name == device_name)) // Ignore devices for which getting name fails
|
||||
.ok_or(RodioError::DeviceNotAvailable(device_name))?
|
||||
}
|
||||
None => get_default_device()?,
|
||||
};
|
||||
|
||||
let name = rodio_device.name().ok();
|
||||
info!(
|
||||
"Using audio device: {}",
|
||||
name.as_deref().unwrap_or("(unknown name)")
|
||||
);
|
||||
|
||||
let (stream, handle) = rodio::OutputStream::try_from_device(&rodio_device)?;
|
||||
let sink = rodio::Sink::try_new(&handle)?;
|
||||
Ok((sink, stream))
|
||||
}
|
||||
|
||||
impl Open for RodioSink {
|
||||
fn open(device: Option<String>) -> RodioSink {
|
||||
debug!(
|
||||
"Using rodio sink with cpal host: {:?}",
|
||||
cpal::default_host().id()
|
||||
cpal::default_host().id().name()
|
||||
);
|
||||
|
||||
let rodio_device = match_device(device);
|
||||
debug!("Using cpal device");
|
||||
let stream = rodio::OutputStream::try_from_device(&rodio_device)
|
||||
.expect("Couldn't open output stream.");
|
||||
debug!("Using rodio stream");
|
||||
let sink = rodio::Sink::try_new(&stream.1).expect("Couldn't create output sink.");
|
||||
debug!("Using rodio sink");
|
||||
let (sink_tx, sink_rx) = mpsc::sync_channel(1);
|
||||
let (close_tx, close_rx) = mpsc::sync_channel(1);
|
||||
|
||||
std::thread::spawn(move || match create_sink(device) {
|
||||
Ok((sink, stream)) => {
|
||||
sink_tx.send(Ok(sink)).unwrap();
|
||||
|
||||
close_rx.recv().unwrap_err(); // This will fail as soon as the sender is dropped
|
||||
debug!("drop rodio::OutputStream");
|
||||
drop(stream);
|
||||
}
|
||||
Err(e) => {
|
||||
sink_tx.send(Err(e)).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// Instead of the second `unwrap`, better error handling could be introduced
|
||||
let sink = sink_rx.recv().unwrap().unwrap();
|
||||
|
||||
debug!("Rodio sink was created");
|
||||
RodioSink {
|
||||
rodio_sink: sink,
|
||||
stream: stream.0,
|
||||
_close_tx: close_tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue