fix: gracefully exit when misconfigured or unavailable audio backend
When the user has an error in their audio backend configuration or doesn't have audio backends available, gracefully exit instead of panicking.
This commit is contained in:
committed by
Henrik Friedrichsen
parent
15515c27b5
commit
38010b4c76
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
|
||||
- Crash on Android (Termux) due to unknown user runtime directory
|
||||
- Crash due to misconfigured or unavailable audio backend
|
||||
|
||||
## [1.0.0] - 2023-12-16
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
@@ -83,7 +84,7 @@ impl Application {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `configuration_file_path` - Relative path to the configuration file inside the base path
|
||||
pub fn new(configuration_file_path: Option<String>) -> Result<Self, String> {
|
||||
pub fn new(configuration_file_path: Option<String>) -> Result<Self, Box<dyn Error>> {
|
||||
// Things here may cause the process to abort; we must do them before creating curses
|
||||
// windows otherwise the error message will not be seen by a user
|
||||
|
||||
@@ -115,7 +116,7 @@ impl Application {
|
||||
let event_manager = EventManager::new(cursive.cb_sink().clone());
|
||||
|
||||
let spotify =
|
||||
spotify::Spotify::new(event_manager.clone(), credentials, configuration.clone());
|
||||
spotify::Spotify::new(event_manager.clone(), credentials, configuration.clone())?;
|
||||
|
||||
let library = Arc::new(Library::new(
|
||||
event_manager.clone(),
|
||||
@@ -252,7 +253,16 @@ impl Application {
|
||||
Event::Queue(event) => {
|
||||
self.queue.handle_event(event);
|
||||
}
|
||||
Event::SessionDied => self.spotify.start_worker(None),
|
||||
Event::SessionDied => {
|
||||
if self.spotify.start_worker(None).is_err() {
|
||||
let data: UserData = self
|
||||
.cursive
|
||||
.user_data()
|
||||
.cloned()
|
||||
.expect("user data should be set");
|
||||
data.cmd.handle(&mut self.cursive, Command::Quit);
|
||||
};
|
||||
}
|
||||
Event::IpcInput(input) => match command::parse(&input) {
|
||||
Ok(commands) => {
|
||||
if let Some(data) = self.cursive.user_data::<UserData>().cloned() {
|
||||
|
||||
17
src/main.rs
17
src/main.rs
@@ -3,10 +3,11 @@ extern crate cursive;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, process::exit};
|
||||
|
||||
use application::{setup_logging, Application};
|
||||
use config::set_configuration_base_path;
|
||||
use log::error;
|
||||
use ncspot::program_arguments;
|
||||
|
||||
mod application;
|
||||
@@ -60,10 +61,20 @@ fn main() -> Result<(), String> {
|
||||
Some((_, _)) => unreachable!(),
|
||||
None => {
|
||||
// Create the application.
|
||||
let mut application = Application::new(matches.get_one::<String>("config").cloned())?;
|
||||
let mut application =
|
||||
match Application::new(matches.get_one::<String>("config").cloned()) {
|
||||
Ok(application) => application,
|
||||
Err(error) => {
|
||||
eprintln!("{error}");
|
||||
error!("{error}");
|
||||
exit(-1);
|
||||
}
|
||||
};
|
||||
|
||||
// Start the application event loop.
|
||||
application.run()
|
||||
}
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -60,7 +60,11 @@ pub struct Spotify {
|
||||
}
|
||||
|
||||
impl Spotify {
|
||||
pub fn new(events: EventManager, credentials: Credentials, cfg: Arc<config::Config>) -> Self {
|
||||
pub fn new(
|
||||
events: EventManager,
|
||||
credentials: Credentials,
|
||||
cfg: Arc<config::Config>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let mut spotify = Self {
|
||||
events,
|
||||
credentials,
|
||||
@@ -73,7 +77,7 @@ impl Spotify {
|
||||
};
|
||||
|
||||
let (user_tx, user_rx) = oneshot::channel();
|
||||
spotify.start_worker(Some(user_tx));
|
||||
spotify.start_worker(Some(user_tx))?;
|
||||
let user = ASYNC_RUNTIME.get().unwrap().block_on(user_rx).ok();
|
||||
let volume = cfg.state().volume;
|
||||
spotify.set_volume(volume);
|
||||
@@ -83,30 +87,35 @@ impl Spotify {
|
||||
|
||||
spotify.api.set_user(user);
|
||||
|
||||
spotify
|
||||
Ok(spotify)
|
||||
}
|
||||
|
||||
/// Start the worker thread. If `user_tx` is given, it will receive the username of the logged
|
||||
/// in user.
|
||||
pub fn start_worker(&self, user_tx: Option<oneshot::Sender<String>>) {
|
||||
pub fn start_worker(
|
||||
&self,
|
||||
user_tx: Option<oneshot::Sender<String>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
*self.channel.write().unwrap() = Some(tx);
|
||||
{
|
||||
let worker_channel = self.channel.clone();
|
||||
let cfg = self.cfg.clone();
|
||||
let events = self.events.clone();
|
||||
let volume = self.volume();
|
||||
let credentials = self.credentials.clone();
|
||||
ASYNC_RUNTIME.get().unwrap().spawn(Self::worker(
|
||||
worker_channel,
|
||||
events,
|
||||
rx,
|
||||
cfg,
|
||||
credentials,
|
||||
user_tx,
|
||||
volume,
|
||||
));
|
||||
}
|
||||
let worker_channel = self.channel.clone();
|
||||
let cfg = self.cfg.clone();
|
||||
let events = self.events.clone();
|
||||
let volume = self.volume();
|
||||
let credentials = self.credentials.clone();
|
||||
let backend_name = cfg.values().backend.clone();
|
||||
let backend = Self::init_backend(backend_name)?;
|
||||
ASYNC_RUNTIME.get().unwrap().spawn(Self::worker(
|
||||
worker_channel,
|
||||
events,
|
||||
rx,
|
||||
cfg,
|
||||
credentials,
|
||||
user_tx,
|
||||
volume,
|
||||
backend,
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate the librespot [SessionConfig] used when creating a [Session].
|
||||
@@ -161,14 +170,19 @@ impl Spotify {
|
||||
}
|
||||
|
||||
/// Create and initialize the requested audio backend.
|
||||
fn init_backend(desired_backend: Option<String>) -> Option<SinkBuilder> {
|
||||
fn init_backend(desired_backend: Option<String>) -> Result<SinkBuilder, Box<dyn Error>> {
|
||||
let backend = if let Some(name) = desired_backend {
|
||||
audio_backend::BACKENDS
|
||||
.iter()
|
||||
.find(|backend| name == backend.0)
|
||||
.ok_or(format!(
|
||||
r#"configured audio backend "{name}" can't be found"#
|
||||
))?
|
||||
} else {
|
||||
audio_backend::BACKENDS.first()
|
||||
}?;
|
||||
audio_backend::BACKENDS
|
||||
.first()
|
||||
.ok_or("no available audio backends found")?
|
||||
};
|
||||
|
||||
let backend_name = backend.0;
|
||||
|
||||
@@ -179,10 +193,11 @@ impl Spotify {
|
||||
env::set_var("PULSE_PROP_media.role", "music");
|
||||
}
|
||||
|
||||
Some(backend.1)
|
||||
Ok(backend.1)
|
||||
}
|
||||
|
||||
/// Create and run the worker thread.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn worker(
|
||||
worker_channel: Arc<RwLock<Option<mpsc::UnboundedSender<WorkerCommand>>>>,
|
||||
events: EventManager,
|
||||
@@ -191,6 +206,7 @@ impl Spotify {
|
||||
credentials: Credentials,
|
||||
user_tx: Option<oneshot::Sender<String>>,
|
||||
volume: u16,
|
||||
backend: SinkBuilder,
|
||||
) {
|
||||
let bitrate_str = cfg.values().bitrate.unwrap_or(320).to_string();
|
||||
let bitrate = Bitrate::from_str(&bitrate_str);
|
||||
@@ -216,9 +232,6 @@ impl Spotify {
|
||||
let mixer = create_mixer(MixerConfig::default());
|
||||
mixer.set_volume(volume);
|
||||
|
||||
let backend_name = cfg.values().backend.clone();
|
||||
let backend =
|
||||
Self::init_backend(backend_name).expect("Could not find an audio playback backend");
|
||||
let audio_format: librespot_playback::config::AudioFormat = Default::default();
|
||||
let (player, player_events) = Player::new(
|
||||
player_config,
|
||||
|
||||
Reference in New Issue
Block a user