fix(player): recreate CoreAudio exclusive streams

This commit is contained in:
Vudgekek committed 2026-05-06 23:26:15 -05:00
1 parent e0151af6d9
commit d4a51a5c63
2 files changed
+254 -89

No files matched your search

+147 -69
View File
@@ -454,10 +454,27 @@ fn create_output_stream_with_config(
/// Output stream type - either rodio or ALSA Direct /// Output stream type - either rodio or ALSA Direct
enum StreamType { enum StreamType {
Rodio(MixerDeviceSink), Rodio(MixerDeviceSink),
#[cfg(target_os = "macos")]
CoreAudio {
sink: MixerDeviceSink,
_exclusive_guard: Option<qbz_audio::CoreAudioExclusiveGuard>,
},
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
AlsaDirect(Arc<qbz_audio::AlsaDirectStream>), AlsaDirect(Arc<qbz_audio::AlsaDirectStream>),
} }
impl StreamType {
fn rodio_sink(&self) -> Option<&MixerDeviceSink> {
match self {
StreamType::Rodio(sink) => Some(sink),
#[cfg(target_os = "macos")]
StreamType::CoreAudio { sink, .. } => Some(sink),
#[cfg(target_os = "linux")]
StreamType::AlsaDirect(_) => None,
}
}
}
/// Try to create output stream using the backend system (if configured) /// Try to create output stream using the backend system (if configured)
/// Returns None if backend system is not configured (backend_type = None) /// Returns None if backend system is not configured (backend_type = None)
/// ///
@@ -469,10 +486,10 @@ fn try_init_stream_with_backend(
state: &SharedState, state: &SharedState,
) -> Option<Result<StreamType, String>> { ) -> Option<Result<StreamType, String>> {
// Check if backend system is configured. // Check if backend system is configured.
// On non-Linux, default to SystemDefault when not explicitly set, // On macOS, default to SystemDefault when not explicitly set,
// so macOS gets CoreAudio device probing and sample rate switching. // so CoreAudio device probing and sample rate switching are active.
let backend_type = audio_settings.backend_type.or_else(|| { let backend_type = audio_settings.backend_type.or_else(|| {
if cfg!(not(target_os = "linux")) { if cfg!(target_os = "macos") {
Some(qbz_audio::AudioBackendType::SystemDefault) Some(qbz_audio::AudioBackendType::SystemDefault)
} else { } else {
None None
@@ -540,15 +557,26 @@ fn try_init_stream_with_backend(
} }
// Fallback to regular rodio stream (PipeWire, Pulse, ALSA via CPAL) // Fallback to regular rodio stream (PipeWire, Pulse, ALSA via CPAL)
match backend.create_output_stream(&config) { match backend.create_output_stream_with_exclusive_guard(&config) {
Ok(mixer_sink) => { Ok((mixer_sink, exclusive_guard)) => {
log::info!( log::info!(
"Stream created via {:?} backend at {}Hz", "Stream created via {:?} backend at {}Hz",
backend_type, backend_type,
sample_rate sample_rate
); );
state.set_bit_perfect_mode(Some(BitPerfectMode::Disabled)); state.set_bit_perfect_mode(Some(BitPerfectMode::Disabled));
Some(Ok(StreamType::Rodio(mixer_sink))) #[cfg(target_os = "macos")]
let stream = if backend_type == AudioBackendType::SystemDefault {
StreamType::CoreAudio {
sink: mixer_sink,
_exclusive_guard: exclusive_guard,
}
} else {
StreamType::Rodio(mixer_sink)
};
#[cfg(not(target_os = "macos"))]
let stream = StreamType::Rodio(mixer_sink);
Some(Ok(stream))
} }
Err(e) => { Err(e) => {
log::error!("Backend stream creation failed: {}", e); log::error!("Backend stream creation failed: {}", e);
@@ -970,7 +998,7 @@ impl Player {
-> Option<StreamType> { -> Option<StreamType> {
// Try backend system if configured // Try backend system if configured
if let Ok(settings) = thread_settings.lock() { if let Ok(settings) = thread_settings.lock() {
if settings.backend_type.is_some() { if settings.backend_type.is_some() || cfg!(target_os = "macos") {
// Use provided sample rate/channels to maintain DAC passthrough // Use provided sample rate/channels to maintain DAC passthrough
log::info!( log::info!(
"Initializing backend system with {}Hz/{}ch", "Initializing backend system with {}Hz/{}ch",
@@ -1147,7 +1175,7 @@ impl Player {
let dac_passthrough = thread_settings let dac_passthrough = thread_settings
.lock() .lock()
.ok() .ok()
.map(|s| s.dac_passthrough) .map(|s| cfg!(target_os = "linux") && s.dac_passthrough)
.unwrap_or(false); .unwrap_or(false);
// Check if we need to recreate the stream // Check if we need to recreate the stream
@@ -1162,15 +1190,32 @@ impl Player {
.and_then(|s| s.backend_type) .and_then(|s| s.backend_type)
.map(|b| b == AudioBackendType::Alsa) .map(|b| b == AudioBackendType::Alsa)
.unwrap_or(false); .unwrap_or(false);
let using_coreaudio_exclusive = thread_settings
.lock()
.ok()
.map(|s| {
cfg!(target_os = "macos")
&& s.backend_type.unwrap_or(AudioBackendType::SystemDefault)
== AudioBackendType::SystemDefault
&& s.exclusive_mode
})
.unwrap_or(false);
let needs_new_stream = stream_opt.is_none() let needs_new_stream = stream_opt.is_none()
|| (dac_passthrough && format_changed) || (dac_passthrough && format_changed)
|| (using_alsa_direct && format_changed); || (using_alsa_direct && format_changed)
|| (using_coreaudio_exclusive && format_changed);
if needs_new_stream { if needs_new_stream {
if stream_opt.is_some() { if stream_opt.is_some() {
if (dac_passthrough || using_alsa_direct) && format_changed { if (dac_passthrough
let mode = if using_alsa_direct { || using_alsa_direct
|| using_coreaudio_exclusive)
&& format_changed
{
let mode = if using_coreaudio_exclusive {
"CoreAudio exclusive"
} else if using_alsa_direct {
"ALSA Direct" "ALSA Direct"
} else { } else {
"DAC passthrough" "DAC passthrough"
@@ -1201,9 +1246,10 @@ impl Player {
} }
log::info!( log::info!(
"DAC passthrough: {}, ALSA Direct: {}", "DAC passthrough: {}, ALSA Direct: {}, CoreAudio exclusive: {}",
dac_passthrough, dac_passthrough,
using_alsa_direct using_alsa_direct,
using_coreaudio_exclusive
); );
// Try backend system first (if configured), then fall back to legacy CPAL // Try backend system first (if configured), then fall back to legacy CPAL
@@ -1421,7 +1467,8 @@ impl Player {
// Create PlaybackEngine from StreamType // Create PlaybackEngine from StreamType
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => { Ok(e) => {
*consecutive_sink_failures = 0; *consecutive_sink_failures = 0;
@@ -1485,6 +1532,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0; let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0;
@@ -1607,7 +1657,7 @@ impl Player {
let dac_passthrough = thread_settings let dac_passthrough = thread_settings
.lock() .lock()
.ok() .ok()
.map(|s| s.dac_passthrough) .map(|s| cfg!(target_os = "linux") && s.dac_passthrough)
.unwrap_or(false); .unwrap_or(false);
// Check if we need to recreate the stream // Check if we need to recreate the stream
@@ -1620,15 +1670,32 @@ impl Player {
.and_then(|s| s.backend_type) .and_then(|s| s.backend_type)
.map(|b| b == AudioBackendType::Alsa) .map(|b| b == AudioBackendType::Alsa)
.unwrap_or(false); .unwrap_or(false);
let using_coreaudio_exclusive = thread_settings
.lock()
.ok()
.map(|s| {
cfg!(target_os = "macos")
&& s.backend_type.unwrap_or(AudioBackendType::SystemDefault)
== AudioBackendType::SystemDefault
&& s.exclusive_mode
})
.unwrap_or(false);
let needs_new_stream = stream_opt.is_none() let needs_new_stream = stream_opt.is_none()
|| (dac_passthrough && format_changed) || (dac_passthrough && format_changed)
|| (using_alsa_direct && format_changed); || (using_alsa_direct && format_changed)
|| (using_coreaudio_exclusive && format_changed);
if needs_new_stream { if needs_new_stream {
if stream_opt.is_some() { if stream_opt.is_some() {
if (dac_passthrough || using_alsa_direct) && format_changed { if (dac_passthrough
let mode = if using_alsa_direct { || using_alsa_direct
|| using_coreaudio_exclusive)
&& format_changed
{
let mode = if using_coreaudio_exclusive {
"CoreAudio exclusive"
} else if using_alsa_direct {
"ALSA Direct" "ALSA Direct"
} else { } else {
"DAC passthrough" "DAC passthrough"
@@ -1774,7 +1841,8 @@ impl Player {
// Create PlaybackEngine // Create PlaybackEngine
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => { Ok(e) => {
*consecutive_sink_failures = 0; *consecutive_sink_failures = 0;
@@ -1802,6 +1870,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0; let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0;
@@ -2000,7 +2071,8 @@ impl Player {
}; };
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => e, Ok(e) => e,
Err(e) => { Err(e) => {
@@ -2024,6 +2096,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = let volume =
@@ -2152,12 +2227,8 @@ impl Player {
// playback reach this handler with // playback reach this handler with
// current_audio_data Some and skip the // current_audio_data Some and skip the
// streaming branch entirely (issue #335). // streaming branch entirely (issue #335).
if current_audio_data.is_none() if current_audio_data.is_none() && current_streaming_source.is_none() {
&& current_streaming_source.is_none() log::warn!("Audio thread: cannot seek - no audio data available");
{
log::warn!(
"Audio thread: cannot seek - no audio data available"
);
return; return;
} }
if let Some(ref stream_src) = *current_streaming_source { if let Some(ref stream_src) = *current_streaming_source {
@@ -2210,7 +2281,8 @@ impl Player {
} }
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => e, Ok(e) => e,
Err(e) => { Err(e) => {
@@ -2234,6 +2306,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0; let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0;
@@ -2251,56 +2326,42 @@ impl Player {
// probe the format (e.g., rodio-only MP4/AAC), // probe the format (e.g., rodio-only MP4/AAC),
// preserving existing behavior for those cases. // preserving existing behavior for those cases.
let skip_duration = Duration::from_secs(position_secs); let skip_duration = Duration::from_secs(position_secs);
let skipped_source: Box<dyn Source<Item = f32> + Send> = let skipped_source: Box<dyn Source<Item = f32> + Send> = if let Some(
if let Some(ref stream_src) = *current_streaming_source { ref stream_src,
match IncrementalStreamingSource::new(stream_src.clone()) { ) =
Ok(mut s) => { *current_streaming_source
if let Err(e) = s.seek_to(skip_duration) { {
log::error!( match IncrementalStreamingSource::new(stream_src.clone()) {
"Failed to native-seek streaming source: {}", Ok(mut s) => {
e if let Err(e) = s.seek_to(skip_duration) {
);
return;
}
Box::new(s)
}
Err(e) => {
log::error!( log::error!(
"Failed to create streaming source for seek: {}", "Failed to native-seek streaming source: {}",
e e
); );
return; return;
} }
Box::new(s)
} }
} else { Err(e) => {
let audio_data = current_audio_data log::error!(
.as_ref() "Failed to create streaming source for seek: {}",
.expect("current_audio_data was checked Some above"); e
match InMemorySource::new(audio_data.clone()) { );
Ok(mut s) => match s.seek_to(skip_duration) { return;
Ok(()) => Box::new(s), }
Err(e) => { }
log::warn!( } else {
let audio_data = current_audio_data
.as_ref()
.expect("current_audio_data was checked Some above");
match InMemorySource::new(audio_data.clone()) {
Ok(mut s) => match s.seek_to(skip_duration) {
Ok(()) => Box::new(s),
Err(e) => {
log::warn!(
"Native seek on cached source failed ({}); falling back to skip_duration", "Native seek on cached source failed ({}); falling back to skip_duration",
e e
); );
match decode_with_fallback(audio_data) {
Ok(fb) => Box::new(fb.skip_duration(skip_duration)),
Err(e) => {
log::error!(
"Failed to decode audio for seek: {}",
e
);
return;
}
}
}
},
Err(e) => {
log::warn!(
"InMemorySource probe failed ({}); falling back to skip_duration",
e
);
match decode_with_fallback(audio_data) { match decode_with_fallback(audio_data) {
Ok(fb) => Box::new(fb.skip_duration(skip_duration)), Ok(fb) => Box::new(fb.skip_duration(skip_duration)),
Err(e) => { Err(e) => {
@@ -2312,8 +2373,25 @@ impl Player {
} }
} }
} }
},
Err(e) => {
log::warn!(
"InMemorySource probe failed ({}); falling back to skip_duration",
e
);
match decode_with_fallback(audio_data) {
Ok(fb) => Box::new(fb.skip_duration(skip_duration)),
Err(e) => {
log::error!(
"Failed to decode audio for seek: {}",
e
);
return;
}
}
} }
}; }
};
// Send Reset to analyzer (seek invalidates accumulated samples) // Send Reset to analyzer (seek invalidates accumulated samples)
let _ = analyzer_tx.try_send(AnalyzerMessage::Reset); let _ = analyzer_tx.try_send(AnalyzerMessage::Reset);
+107 -20
View File
@@ -465,10 +465,27 @@ fn create_output_stream_with_config(
/// Output stream type - either rodio or ALSA Direct /// Output stream type - either rodio or ALSA Direct
enum StreamType { enum StreamType {
Rodio(MixerDeviceSink), Rodio(MixerDeviceSink),
#[cfg(target_os = "macos")]
CoreAudio {
sink: MixerDeviceSink,
_exclusive_guard: Option<crate::audio::CoreAudioExclusiveGuard>,
},
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
AlsaDirect(Arc<crate::audio::AlsaDirectStream>), AlsaDirect(Arc<crate::audio::AlsaDirectStream>),
} }
impl StreamType {
fn rodio_sink(&self) -> Option<&MixerDeviceSink> {
match self {
StreamType::Rodio(sink) => Some(sink),
#[cfg(target_os = "macos")]
StreamType::CoreAudio { sink, .. } => Some(sink),
#[cfg(target_os = "linux")]
StreamType::AlsaDirect(_) => None,
}
}
}
/// Try to create output stream using the backend system (if configured) /// Try to create output stream using the backend system (if configured)
/// Returns None if backend system is not configured (backend_type = None) /// Returns None if backend system is not configured (backend_type = None)
/// ///
@@ -478,8 +495,16 @@ fn try_init_stream_with_backend(
sample_rate: u32, sample_rate: u32,
channels: u16, channels: u16,
) -> Option<Result<StreamType, String>> { ) -> Option<Result<StreamType, String>> {
// Check if backend system is configured // Check if backend system is configured. On macOS, default Auto to
let backend_type = audio_settings.backend_type?; // SystemDefault so CoreAudio device probing, rate switching, and Hog Mode
// are available to existing installs with no saved backend.
let backend_type = audio_settings.backend_type.or_else(|| {
if cfg!(target_os = "macos") {
Some(AudioBackendType::SystemDefault)
} else {
None
}
})?;
log::info!( log::info!(
"Using backend system: {:?} (device: {:?}, plugin: {:?})", "Using backend system: {:?} (device: {:?}, plugin: {:?})",
@@ -541,14 +566,25 @@ fn try_init_stream_with_backend(
} }
// Fallback to regular rodio stream (PipeWire, Pulse, ALSA via CPAL) // Fallback to regular rodio stream (PipeWire, Pulse, ALSA via CPAL)
match backend.create_output_stream(&config) { match backend.create_output_stream_with_exclusive_guard(&config) {
Ok(mixer_sink) => { Ok((mixer_sink, exclusive_guard)) => {
log::info!( log::info!(
"Stream created via {:?} backend at {}Hz", "Stream created via {:?} backend at {}Hz",
backend_type, backend_type,
sample_rate sample_rate
); );
Some(Ok(StreamType::Rodio(mixer_sink))) #[cfg(target_os = "macos")]
let stream = if backend_type == AudioBackendType::SystemDefault {
StreamType::CoreAudio {
sink: mixer_sink,
_exclusive_guard: exclusive_guard,
}
} else {
StreamType::Rodio(mixer_sink)
};
#[cfg(not(target_os = "macos"))]
let stream = StreamType::Rodio(mixer_sink);
Some(Ok(stream))
} }
Err(e) => { Err(e) => {
log::error!("Backend stream creation failed: {}", e); log::error!("Backend stream creation failed: {}", e);
@@ -907,7 +943,7 @@ impl Player {
-> Option<StreamType> { -> Option<StreamType> {
// Try backend system if configured // Try backend system if configured
if let Ok(settings) = thread_settings.lock() { if let Ok(settings) = thread_settings.lock() {
if settings.backend_type.is_some() { if settings.backend_type.is_some() || cfg!(target_os = "macos") {
// Use provided sample rate/channels to maintain DAC passthrough // Use provided sample rate/channels to maintain DAC passthrough
log::info!( log::info!(
"Initializing backend system with {}Hz/{}ch", "Initializing backend system with {}Hz/{}ch",
@@ -1078,7 +1114,7 @@ impl Player {
let dac_passthrough = thread_settings let dac_passthrough = thread_settings
.lock() .lock()
.ok() .ok()
.map(|s| s.dac_passthrough) .map(|s| cfg!(target_os = "linux") && s.dac_passthrough)
.unwrap_or(false); .unwrap_or(false);
// Check if we need to recreate the stream // Check if we need to recreate the stream
@@ -1093,15 +1129,32 @@ impl Player {
.and_then(|s| s.backend_type) .and_then(|s| s.backend_type)
.map(|b| b == AudioBackendType::Alsa) .map(|b| b == AudioBackendType::Alsa)
.unwrap_or(false); .unwrap_or(false);
let using_coreaudio_exclusive = thread_settings
.lock()
.ok()
.map(|s| {
cfg!(target_os = "macos")
&& s.backend_type.unwrap_or(AudioBackendType::SystemDefault)
== AudioBackendType::SystemDefault
&& s.exclusive_mode
})
.unwrap_or(false);
let needs_new_stream = stream_opt.is_none() let needs_new_stream = stream_opt.is_none()
|| (dac_passthrough && format_changed) || (dac_passthrough && format_changed)
|| (using_alsa_direct && format_changed); || (using_alsa_direct && format_changed)
|| (using_coreaudio_exclusive && format_changed);
if needs_new_stream { if needs_new_stream {
if stream_opt.is_some() { if stream_opt.is_some() {
if (dac_passthrough || using_alsa_direct) && format_changed { if (dac_passthrough
let mode = if using_alsa_direct { || using_alsa_direct
|| using_coreaudio_exclusive)
&& format_changed
{
let mode = if using_coreaudio_exclusive {
"CoreAudio exclusive"
} else if using_alsa_direct {
"ALSA Direct" "ALSA Direct"
} else { } else {
"DAC passthrough" "DAC passthrough"
@@ -1122,9 +1175,10 @@ impl Player {
} }
log::info!( log::info!(
"DAC passthrough: {}, ALSA Direct: {}", "DAC passthrough: {}, ALSA Direct: {}, CoreAudio exclusive: {}",
dac_passthrough, dac_passthrough,
using_alsa_direct using_alsa_direct,
using_coreaudio_exclusive
); );
// Try backend system first (if configured), then fall back to legacy CPAL // Try backend system first (if configured), then fall back to legacy CPAL
@@ -1334,7 +1388,8 @@ impl Player {
// Create PlaybackEngine from StreamType // Create PlaybackEngine from StreamType
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => { Ok(e) => {
*consecutive_sink_failures = 0; *consecutive_sink_failures = 0;
@@ -1398,6 +1453,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0; let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0;
@@ -1519,7 +1577,7 @@ impl Player {
let dac_passthrough = thread_settings let dac_passthrough = thread_settings
.lock() .lock()
.ok() .ok()
.map(|s| s.dac_passthrough) .map(|s| cfg!(target_os = "linux") && s.dac_passthrough)
.unwrap_or(false); .unwrap_or(false);
// Check if we need to recreate the stream // Check if we need to recreate the stream
@@ -1532,15 +1590,32 @@ impl Player {
.and_then(|s| s.backend_type) .and_then(|s| s.backend_type)
.map(|b| b == AudioBackendType::Alsa) .map(|b| b == AudioBackendType::Alsa)
.unwrap_or(false); .unwrap_or(false);
let using_coreaudio_exclusive = thread_settings
.lock()
.ok()
.map(|s| {
cfg!(target_os = "macos")
&& s.backend_type.unwrap_or(AudioBackendType::SystemDefault)
== AudioBackendType::SystemDefault
&& s.exclusive_mode
})
.unwrap_or(false);
let needs_new_stream = stream_opt.is_none() let needs_new_stream = stream_opt.is_none()
|| (dac_passthrough && format_changed) || (dac_passthrough && format_changed)
|| (using_alsa_direct && format_changed); || (using_alsa_direct && format_changed)
|| (using_coreaudio_exclusive && format_changed);
if needs_new_stream { if needs_new_stream {
if stream_opt.is_some() { if stream_opt.is_some() {
if (dac_passthrough || using_alsa_direct) && format_changed { if (dac_passthrough
let mode = if using_alsa_direct { || using_alsa_direct
|| using_coreaudio_exclusive)
&& format_changed
{
let mode = if using_coreaudio_exclusive {
"CoreAudio exclusive"
} else if using_alsa_direct {
"ALSA Direct" "ALSA Direct"
} else { } else {
"DAC passthrough" "DAC passthrough"
@@ -1670,7 +1745,8 @@ impl Player {
// Create PlaybackEngine // Create PlaybackEngine
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => { Ok(e) => {
*consecutive_sink_failures = 0; *consecutive_sink_failures = 0;
@@ -1698,6 +1774,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0; let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0;
@@ -1896,7 +1975,8 @@ impl Player {
}; };
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => e, Ok(e) => e,
Err(e) => { Err(e) => {
@@ -1920,6 +2000,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = let volume =
@@ -2031,7 +2114,8 @@ impl Player {
} }
let mut engine = match stream { let mut engine = match stream {
StreamType::Rodio(ref mixer_sink) => { stream if stream.rodio_sink().is_some() => {
let mixer_sink = stream.rodio_sink().expect("rodio sink");
match PlaybackEngine::new_rodio(&mixer_sink.mixer()) { match PlaybackEngine::new_rodio(&mixer_sink.mixer()) {
Ok(e) => e, Ok(e) => e,
Err(e) => { Err(e) => {
@@ -2055,6 +2139,9 @@ impl Player {
hardware_volume, hardware_volume,
) )
} }
_ => unreachable!(
"non-rodio stream handled by platform-specific arms"
),
}; };
let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0; let volume = thread_state.volume.load(Ordering::SeqCst) as f32 / 100.0;