Skip to content

Commit abf8a72

Browse files
committed
fix(pipewire): only auto-reroute default devices
1 parent bc101ac commit abf8a72

5 files changed

Lines changed: 44 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818

1919
- Timestamps now stay monotonic across device and graph changes.
2020
- **ALSA**: A nonzero but sub-millisecond stream timeout is no longer treated as a non-blocking poll.
21+
- **PipeWire**: Streams for a specific device no longer auto-reroute if it disappears.
2122
- **visionOS**: The CoreAudio backend now builds.
2223
- **WASAPI**: Reported buffer sizes are no longer off by one frame.
2324
- **WASAPI**: `Stream::drop`, `play`, and `pause` no longer panic when the device is lost.

src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ pub enum ErrorKind {
1414

1515
/// The active audio route changed and the stream was automatically rerouted.
1616
/// The stream remains active and no rebuild is required.
17+
///
18+
/// Only fires for a stream built from the default device, on backends that support following
19+
/// it. A stream built from a specific device does not follow a replacement; if that device
20+
/// disappears, it reports [`DeviceNotAvailable`] instead.
21+
///
22+
/// [`DeviceNotAvailable`]: ErrorKind::DeviceNotAvailable
1723
DeviceChanged,
1824

1925
/// The requested audio device is not available.

src/host/pipewire/device.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,10 @@ impl DeviceTrait for Device {
399399
last_quantum: last_quantum_clone,
400400
start,
401401
connect_automatically: device.connect_automatically.load(Ordering::Relaxed),
402+
is_default_device: matches!(
403+
device.class(),
404+
Class::DefaultSink | Class::DefaultInput | Class::DefaultOutput
405+
),
402406
},
403407
data_callback,
404408
error_callback,
@@ -423,7 +427,6 @@ impl DeviceTrait for Device {
423427
error_callback,
424428
pending_device_changed,
425429
invalidated,
426-
is_default_device,
427430
} = stream_data;
428431

429432
let default_monitor = if let Some(key) = device.default_metadata_key() {
@@ -446,7 +449,6 @@ impl DeviceTrait for Device {
446449
} else {
447450
None
448451
};
449-
is_default_device.store(default_monitor.is_some(), Ordering::Relaxed);
450452
let stream_clone = stream.clone();
451453
let mainloop_rc1 = mainloop.clone();
452454
let error_callback_cmd = error_callback.clone();
@@ -575,6 +577,10 @@ impl DeviceTrait for Device {
575577
last_quantum: last_quantum_clone,
576578
start,
577579
connect_automatically: device.connect_automatically.load(Ordering::Relaxed),
580+
is_default_device: matches!(
581+
device.class(),
582+
Class::DefaultSink | Class::DefaultInput | Class::DefaultOutput
583+
),
578584
},
579585
data_callback,
580586
error_callback,
@@ -599,7 +605,6 @@ impl DeviceTrait for Device {
599605
error_callback,
600606
pending_device_changed,
601607
invalidated,
602-
is_default_device,
603608
} = stream_data;
604609

605610
let default_monitor = if let Some(key) = device.default_metadata_key() {
@@ -622,7 +627,6 @@ impl DeviceTrait for Device {
622627
} else {
623628
None
624629
};
625-
is_default_device.store(default_monitor.is_some(), Ordering::Relaxed);
626630
let stream_clone = stream.clone();
627631
let mainloop_rc1 = mainloop.clone();
628632
let error_callback_cmd = error_callback.clone();

src/host/pipewire/stream.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ pub struct UserData<D> {
225225
format: AudioInfoRaw,
226226
last_quantum: Arc<AtomicU64>,
227227
start: Instant,
228-
is_default_device: Arc<AtomicBool>,
228+
is_default_device: bool,
229229
has_connected: bool,
230230
invalidated: Arc<AtomicBool>,
231231
pending_device_changed: Arc<AtomicBool>,
@@ -253,7 +253,7 @@ impl<D> UserData<D> {
253253
StreamState::Unconnected => {
254254
// Let the metadata monitor fire for default-device streams
255255
if self.has_connected
256-
&& !self.is_default_device.load(Ordering::Relaxed)
256+
&& !self.is_default_device
257257
&& !self.invalidated.swap(true, Ordering::Relaxed)
258258
{
259259
emit_error(
@@ -376,7 +376,6 @@ pub struct StreamData<D> {
376376
pub error_callback: ErrorCallbackArc,
377377
pub pending_device_changed: Arc<AtomicBool>,
378378
pub invalidated: Arc<AtomicBool>,
379-
pub is_default_device: Arc<AtomicBool>,
380379
}
381380

382381
/// Fallback timestamp using elapsed time since stream creation.
@@ -525,6 +524,7 @@ pub struct ConnectParams {
525524
pub last_quantum: Arc<AtomicU64>,
526525
pub start: Instant,
527526
pub connect_automatically: bool,
527+
pub is_default_device: bool,
528528
}
529529

530530
pub fn connect_output<D, E>(
@@ -543,6 +543,7 @@ where
543543
last_quantum,
544544
start,
545545
connect_automatically,
546+
is_default_device,
546547
} = params;
547548

548549
let mainloop = MainLoopRc::new(None)?;
@@ -553,7 +554,6 @@ where
553554
let invalidated = Arc::new(AtomicBool::new(false));
554555

555556
let pending_device_changed = Arc::new(AtomicBool::new(false));
556-
let is_default_device = Arc::new(AtomicBool::new(false));
557557

558558
let core_monitor = {
559559
let invalidated_core = invalidated.clone();
@@ -582,7 +582,7 @@ where
582582
last_quantum,
583583
start,
584584
invalidated: invalidated.clone(),
585-
is_default_device: is_default_device.clone(),
585+
is_default_device,
586586
has_connected: false,
587587
pending_device_changed: pending_device_changed.clone(),
588588
#[cfg(feature = "realtime")]
@@ -756,6 +756,9 @@ where
756756
if connect_automatically {
757757
flags |= StreamFlags::AUTOCONNECT;
758758
}
759+
if !is_default_device {
760+
flags |= StreamFlags::DONT_RECONNECT;
761+
}
759762

760763
stream.connect(Direction::Output, None, flags, &mut params)?;
761764

@@ -769,7 +772,6 @@ where
769772
error_callback: error_callback_out,
770773
pending_device_changed,
771774
invalidated,
772-
is_default_device,
773775
})
774776
}
775777

@@ -789,6 +791,7 @@ where
789791
last_quantum,
790792
start,
791793
connect_automatically,
794+
is_default_device,
792795
} = params;
793796

794797
let mainloop = MainLoopRc::new(None)?;
@@ -799,7 +802,6 @@ where
799802
let invalidated = Arc::new(AtomicBool::new(false));
800803

801804
let pending_device_changed = Arc::new(AtomicBool::new(false));
802-
let is_default_device = Arc::new(AtomicBool::new(false));
803805

804806
let core_monitor = {
805807
let invalidated_core = invalidated.clone();
@@ -828,7 +830,7 @@ where
828830
last_quantum,
829831
start,
830832
invalidated: invalidated.clone(),
831-
is_default_device: is_default_device.clone(),
833+
is_default_device,
832834
has_connected: false,
833835
pending_device_changed: pending_device_changed.clone(),
834836
#[cfg(feature = "realtime")]
@@ -985,6 +987,9 @@ where
985987
if connect_automatically {
986988
flags |= StreamFlags::AUTOCONNECT;
987989
}
990+
if !is_default_device {
991+
flags |= StreamFlags::DONT_RECONNECT;
992+
}
988993

989994
stream.connect(Direction::Input, None, flags, &mut params)?;
990995

@@ -998,6 +1003,5 @@ where
9981003
error_callback: error_callback_out,
9991004
pending_device_changed,
10001005
invalidated,
1001-
is_default_device,
10021006
})
10031007
}

src/traits.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,27 @@ pub trait HostTrait {
7373
/// The default input audio device on the system.
7474
///
7575
/// Returns `None` if no input device is available.
76+
///
77+
/// Some backends reroute a stream built from this device to the new default device when it
78+
/// changes; capture continues there, and [`ErrorKind::DeviceChanged`] is reported. Other
79+
/// backends report [`ErrorKind::DeviceNotAvailable`] instead, and the caller must rebuild the
80+
/// stream.
81+
///
82+
/// [`ErrorKind::DeviceChanged`]: crate::ErrorKind::DeviceChanged
83+
/// [`ErrorKind::DeviceNotAvailable`]: crate::ErrorKind::DeviceNotAvailable
7684
fn default_input_device(&self) -> Option<Self::Device>;
7785

7886
/// The default output audio device on the system.
7987
///
8088
/// Returns `None` if no output device is available.
89+
///
90+
/// Some backends reroute a stream built from this device to the new default device when it
91+
/// changes; playback continues there, and [`ErrorKind::DeviceChanged`] is reported. Other
92+
/// backends report [`ErrorKind::DeviceNotAvailable`] instead, and the caller must rebuild the
93+
/// stream.
94+
///
95+
/// [`ErrorKind::DeviceChanged`]: crate::ErrorKind::DeviceChanged
96+
/// [`ErrorKind::DeviceNotAvailable`]: crate::ErrorKind::DeviceNotAvailable
8197
fn default_output_device(&self) -> Option<Self::Device>;
8298

8399
/// An iterator yielding all `Device`s currently available to the system that support one or more

0 commit comments

Comments
 (0)