Skip to content

Commit be37402

Browse files
authored
Expose possible mixer opening errors (#1488)
* playback: handle errors when opening mixer * chore: update CHANGELOG.md * fix tests and typo
1 parent 80c27ec commit be37402

File tree

7 files changed

+71
-39
lines changed

7 files changed

+71
-39
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- [connect] Moved all public items to the highest level (breaking)
1818
- [connect] Replaced Mercury usage in `Spirc` with Dealer
1919
- [metadata] Replaced `AudioFileFormat` with own enum. (breaking)
20+
- [playback] Changed trait `Mixer::open` to return `Result<Self, Error>` instead of `Self` (breaking)
21+
- [playback] Changed type alias `MixerFn` to return `Result<Arc<dyn Mixer>, Error>` instead of `Arc<dyn Mixer>` (breaking)
2022

2123
### Added
2224

connect/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async fn create_basic_spirc() -> Result<(), Error> {
5555
session,
5656
credentials,
5757
player,
58-
mixer(MixerConfig::default())
58+
mixer(MixerConfig::default())?
5959
).await?;
6060

6161
Ok(())

examples/play_connect.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async fn main() -> Result<(), Error> {
5050
})?;
5151

5252
let session = Session::new(session_config, Some(cache));
53-
let mixer = mixer_builder(mixer_config);
53+
let mixer = mixer_builder(mixer_config)?;
5454

5555
let player = Player::new(
5656
player_config,

playback/src/mixer/alsamixer.rs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use super::{Mixer, MixerConfig, VolumeCtrl};
55

66
use alsa::ctl::{ElemId, ElemIface};
77
use alsa::mixer::{MilliBel, SelemChannelId, SelemId};
8+
use alsa::Error as AlsaError;
89
use alsa::{Ctl, Round};
910

10-
use std::ffi::CString;
11+
use librespot_core::Error;
12+
use std::ffi::{CString, NulError};
13+
use thiserror::Error;
1114

1215
#[derive(Clone)]
1316
#[allow(dead_code)]
@@ -29,8 +32,30 @@ pub struct AlsaMixer {
2932
const SND_CTL_TLV_DB_GAIN_MUTE: MilliBel = MilliBel(-9999999);
3033
const ZERO_DB: MilliBel = MilliBel(0);
3134

35+
#[derive(Debug, Error)]
36+
enum AlsaMixerError {
37+
#[error("Could not open Alsa mixer. {0}")]
38+
CouldNotOpen(AlsaError),
39+
#[error("Could not find Alsa mixer control")]
40+
CouldNotFindController,
41+
#[error("Could not open Alsa softvol with that device. {0}")]
42+
CouldNotOpenWithDevice(AlsaError),
43+
#[error("Could not open Alsa softvol with that name. {0}")]
44+
CouldNotOpenWithName(NulError),
45+
#[error("Could not get Alsa softvol dB range. {0}")]
46+
NoDbRange(AlsaError),
47+
#[error("Could not convert Alsa raw volume to dB volume. {0}")]
48+
CouldNotConvertRaw(AlsaError),
49+
}
50+
51+
impl From<AlsaMixerError> for Error {
52+
fn from(value: AlsaMixerError) -> Self {
53+
Error::failed_precondition(value)
54+
}
55+
}
56+
3257
impl Mixer for AlsaMixer {
33-
fn open(config: MixerConfig) -> Self {
58+
fn open(config: MixerConfig) -> Result<Self, Error> {
3459
info!(
3560
"Mixing with Alsa and volume control: {:?} for device: {} with mixer control: {},{}",
3661
config.volume_ctrl, config.device, config.control, config.index,
@@ -39,10 +64,10 @@ impl Mixer for AlsaMixer {
3964
let mut config = config; // clone
4065

4166
let mixer =
42-
alsa::mixer::Mixer::new(&config.device, false).expect("Could not open Alsa mixer");
67+
alsa::mixer::Mixer::new(&config.device, false).map_err(AlsaMixerError::CouldNotOpen)?;
4368
let simple_element = mixer
4469
.find_selem(&SelemId::new(&config.control, config.index))
45-
.expect("Could not find Alsa mixer control");
70+
.ok_or(AlsaMixerError::CouldNotFindController)?;
4671

4772
// Query capabilities
4873
let has_switch = simple_element.has_playback_switch();
@@ -57,17 +82,17 @@ impl Mixer for AlsaMixer {
5782
// Query dB volume range -- note that Alsa exposes a different
5883
// API for hardware and software mixers
5984
let (min_millibel, max_millibel) = if is_softvol {
60-
let control = Ctl::new(&config.device, false)
61-
.expect("Could not open Alsa softvol with that device");
85+
let control =
86+
Ctl::new(&config.device, false).map_err(AlsaMixerError::CouldNotOpenWithDevice)?;
6287
let mut element_id = ElemId::new(ElemIface::Mixer);
6388
element_id.set_name(
6489
&CString::new(config.control.as_str())
65-
.expect("Could not open Alsa softvol with that name"),
90+
.map_err(AlsaMixerError::CouldNotOpenWithName)?,
6691
);
6792
element_id.set_index(config.index);
6893
let (min_millibel, mut max_millibel) = control
6994
.get_db_range(&element_id)
70-
.expect("Could not get Alsa softvol dB range");
95+
.map_err(AlsaMixerError::NoDbRange)?;
7196

7297
// Alsa can report incorrect maximum volumes due to rounding
7398
// errors. e.g. Alsa rounds [-60.0..0.0] in range [0..255] to
@@ -97,7 +122,7 @@ impl Mixer for AlsaMixer {
97122
debug!("Alsa mixer reported minimum dB as mute, trying workaround");
98123
min_millibel = simple_element
99124
.ask_playback_vol_db(min + 1)
100-
.expect("Could not convert Alsa raw volume to dB volume");
125+
.map_err(AlsaMixerError::CouldNotConvertRaw)?;
101126
}
102127
(min_millibel, max_millibel)
103128
};
@@ -150,7 +175,7 @@ impl Mixer for AlsaMixer {
150175
);
151176
debug!("Alsa forcing linear dB mapping: {}", use_linear_in_db);
152177

153-
Self {
178+
Ok(Self {
154179
config,
155180
min,
156181
max,
@@ -161,7 +186,7 @@ impl Mixer for AlsaMixer {
161186
has_switch,
162187
is_softvol,
163188
use_linear_in_db,
164-
}
189+
})
165190
}
166191

167192
fn volume(&self) -> u16 {

playback/src/mixer/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
use std::sync::Arc;
2-
31
use crate::config::VolumeCtrl;
2+
use librespot_core::Error;
3+
use std::sync::Arc;
44

55
pub mod mappings;
66
use self::mappings::MappedCtrl;
77

88
pub struct NoOpVolume;
99

1010
pub trait Mixer: Send + Sync {
11-
fn open(config: MixerConfig) -> Self
11+
fn open(config: MixerConfig) -> Result<Self, Error>
1212
where
1313
Self: Sized;
1414

15-
fn set_volume(&self, volume: u16);
1615
fn volume(&self) -> u16;
16+
fn set_volume(&self, volume: u16);
1717

1818
fn get_soft_volume(&self) -> Box<dyn VolumeGetter + Send> {
1919
Box::new(NoOpVolume)
@@ -57,10 +57,10 @@ impl Default for MixerConfig {
5757
}
5858
}
5959

60-
pub type MixerFn = fn(MixerConfig) -> Arc<dyn Mixer>;
60+
pub type MixerFn = fn(MixerConfig) -> Result<Arc<dyn Mixer>, Error>;
6161

62-
fn mk_sink<M: Mixer + 'static>(config: MixerConfig) -> Arc<dyn Mixer> {
63-
Arc::new(M::open(config))
62+
fn mk_sink<M: Mixer + 'static>(config: MixerConfig) -> Result<Arc<dyn Mixer>, Error> {
63+
Ok(Arc::new(M::open(config)?))
6464
}
6565

6666
pub const MIXERS: &[(&str, MixerFn)] = &[

playback/src/mixer/softmixer.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use portable_atomic::AtomicU64;
2-
use std::sync::atomic::Ordering;
3-
use std::sync::Arc;
4-
51
use super::VolumeGetter;
62
use super::{MappedCtrl, VolumeCtrl};
73
use super::{Mixer, MixerConfig};
4+
use librespot_core::Error;
5+
use portable_atomic::AtomicU64;
6+
use std::sync::atomic::Ordering;
7+
use std::sync::Arc;
88

99
#[derive(Clone)]
1010
pub struct SoftMixer {
@@ -15,14 +15,14 @@ pub struct SoftMixer {
1515
}
1616

1717
impl Mixer for SoftMixer {
18-
fn open(config: MixerConfig) -> Self {
18+
fn open(config: MixerConfig) -> Result<Self, Error> {
1919
let volume_ctrl = config.volume_ctrl;
2020
info!("Mixing with softvol and volume control: {:?}", volume_ctrl);
2121

22-
Self {
22+
Ok(Self {
2323
volume: Arc::new(AtomicU64::new(f64::to_bits(0.5))),
2424
volume_ctrl,
25-
}
25+
})
2626
}
2727

2828
fn volume(&self) -> u16 {

src/main.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,3 @@
1-
use std::{
2-
env,
3-
fs::create_dir_all,
4-
ops::RangeInclusive,
5-
path::{Path, PathBuf},
6-
pin::Pin,
7-
process::exit,
8-
str::FromStr,
9-
time::{Duration, Instant},
10-
};
11-
121
use data_encoding::HEXLOWER;
132
use futures_util::StreamExt;
143
#[cfg(feature = "alsa-backend")]
@@ -33,6 +22,16 @@ use librespot::{
3322
use librespot_oauth::OAuthClientBuilder;
3423
use log::{debug, error, info, trace, warn};
3524
use sha1::{Digest, Sha1};
25+
use std::{
26+
env,
27+
fs::create_dir_all,
28+
ops::RangeInclusive,
29+
path::{Path, PathBuf},
30+
pin::Pin,
31+
process::exit,
32+
str::FromStr,
33+
time::{Duration, Instant},
34+
};
3635
use sysinfo::{ProcessesToUpdate, System};
3736
use thiserror::Error;
3837
use url::Url;
@@ -1943,7 +1942,13 @@ async fn main() {
19431942
}
19441943

19451944
let mixer_config = setup.mixer_config.clone();
1946-
let mixer = (setup.mixer)(mixer_config);
1945+
let mixer = match (setup.mixer)(mixer_config) {
1946+
Ok(mixer) => mixer,
1947+
Err(why) => {
1948+
error!("{why}");
1949+
exit(1)
1950+
}
1951+
};
19471952
let player_config = setup.player_config.clone();
19481953

19491954
let soft_volume = mixer.get_soft_volume();

0 commit comments

Comments
 (0)