default_device_sink/
lib.rs

1//! A sink that automatically follows changes to the system's default
2//! audio output device.
3//!
4//! This crate provides [`DefaultDeviceSink`], a wrapper around
5//! `rodio::Sink` that recreates the underlying stream and sink when the
6//! default output device changes. It also exposes helper functions for
7//! working with the selected device.
8
9use cpal::traits::{DeviceTrait, HostTrait};
10use once_cell::sync::Lazy;
11use rodio::{OutputStream, Sink, Source};
12use std::collections::VecDeque;
13use std::sync::{
14    atomic::{AtomicUsize, Ordering},
15    Arc, Mutex,
16};
17
18/// Globally selected output device name. `None` means use the system default.
19pub static SELECTED_OUTPUT_DEVICE: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
20
21struct AudioBuffer {
22    channels: u16,
23    sample_rate: u32,
24    data: Arc<Vec<f32>>,
25    pos: Arc<AtomicUsize>,
26}
27
28#[derive(Clone)]
29struct ResumableSource {
30    channels: u16,
31    sample_rate: u32,
32    data: Arc<Vec<f32>>,
33    pos: Arc<AtomicUsize>,
34}
35
36impl Iterator for ResumableSource {
37    type Item = f32;
38    fn next(&mut self) -> Option<Self::Item> {
39        let idx = self.pos.fetch_add(1, Ordering::Relaxed);
40        self.data.get(idx).copied()
41    }
42}
43
44impl Source for ResumableSource {
45    fn current_frame_len(&self) -> Option<usize> {
46        None
47    }
48    fn channels(&self) -> u16 {
49        self.channels
50    }
51    fn sample_rate(&self) -> u32 {
52        self.sample_rate
53    }
54    fn total_duration(&self) -> Option<std::time::Duration> {
55        let len = self.data.len() as f32 / self.channels as f32 / self.sample_rate as f32;
56        Some(std::time::Duration::from_secs_f32(len))
57    }
58}
59
60/// Returns the name of the current default output device, if any.
61pub fn default_device_name() -> Option<String> {
62    cpal::default_host()
63        .default_output_device()
64        .and_then(|d| d.name().ok())
65}
66
67/// Returns a list of available output device names.
68pub fn list_output_devices() -> Vec<String> {
69    let host = cpal::default_host();
70    match host.output_devices() {
71        Ok(devices) => devices.filter_map(|d| d.name().ok()).collect::<Vec<_>>(),
72        Err(_) => Vec::new(),
73    }
74}
75
76/// Set the globally selected output device. Pass `None` to use the system default.
77pub fn set_output_device(device: Option<String>) {
78    *SELECTED_OUTPUT_DEVICE.lock().unwrap() = device;
79}
80
81/// Returns the currently selected output device. `None` means the system default.
82pub fn get_output_device() -> Option<String> {
83    SELECTED_OUTPUT_DEVICE.lock().unwrap().clone()
84}
85
86struct Inner {
87    _stream: OutputStream,
88    sink: Sink,
89    device_name: Option<String>,
90    queue: VecDeque<AudioBuffer>,
91}
92
93/// `DefaultDeviceSink` wraps a `rodio::Sink` and recreates the underlying
94/// stream and sink if the system default output device changes.
95#[derive(Clone)]
96pub struct DefaultDeviceSink {
97    inner: Arc<Mutex<Inner>>,
98}
99
100impl DefaultDeviceSink {
101    fn with_inner<R>(&self, f: impl FnOnce(&mut Inner) -> R) -> R {
102        let mut inner = self.inner.lock().unwrap();
103        Self::ensure_device(&mut inner);
104        f(&mut inner)
105    }
106
107    fn create_stream() -> (OutputStream, Sink, Option<String>) {
108        if let Some(selected) = get_output_device() {
109            let host = cpal::default_host();
110            if let Ok(devices) = host.output_devices() {
111                for device in devices {
112                    if let Ok(device_name) = device.name() {
113                        if device_name == selected {
114                            if let Ok((stream, handle)) = OutputStream::try_from_device(&device) {
115                                let sink = Sink::try_new(&handle).expect("Failed to create Sink");
116                                return (stream, sink, Some(device_name));
117                            }
118                        }
119                    }
120                }
121            }
122        }
123
124        let (stream, handle) =
125            OutputStream::try_default().expect("Failed to open default output stream");
126        let sink = Sink::try_new(&handle).expect("Failed to create Sink");
127        let name = default_device_name();
128        (stream, sink, name)
129    }
130
131    /// Creates a new `DefaultDeviceSink` using the system default output device.
132    pub fn new() -> Self {
133        let (stream, sink, name) = Self::create_stream();
134        DefaultDeviceSink {
135            inner: Arc::new(Mutex::new(Inner {
136                _stream: stream,
137                sink,
138                device_name: name,
139                queue: VecDeque::new(),
140            })),
141        }
142    }
143
144    /// Checks if the default output device has changed. If so, recreates the
145    /// stream and sink so future sounds play on the new device.
146    fn sync_queue(inner: &mut Inner) {
147        while inner.queue.len() > inner.sink.len() {
148            inner.queue.pop_front();
149        }
150    }
151
152    fn ensure_device(inner: &mut Inner) {
153        Self::sync_queue(inner);
154
155        let desired = match get_output_device() {
156            Some(name) => Some(name),
157            None => default_device_name(),
158        };
159
160        if desired != inner.device_name {
161            let volume = inner.sink.volume();
162            let speed = inner.sink.speed();
163            let paused = inner.sink.is_paused();
164
165            let (stream, new_sink, name) = Self::create_stream();
166            new_sink.set_volume(volume);
167            new_sink.set_speed(speed);
168
169            // Restart queued buffers on the new sink at their current positions
170            for buf in &inner.queue {
171                let source = ResumableSource {
172                    channels: buf.channels,
173                    sample_rate: buf.sample_rate,
174                    data: buf.data.clone(),
175                    pos: buf.pos.clone(),
176                };
177                new_sink.append(source);
178            }
179
180            if paused {
181                new_sink.pause();
182            }
183
184            inner.sink.stop();
185            inner._stream = stream;
186            inner.sink = new_sink;
187            inner.device_name = name;
188        }
189    }
190
191    /// Appends a source to the sink, ensuring that the default device is current.
192    pub fn append<T>(&self, input: T)
193    where
194        T: Source + Send + 'static,
195        T::Item: rodio::Sample + Send,
196        f32: cpal::FromSample<T::Item>,
197    {
198        self.with_inner(|inner| {
199            let channels = input.channels();
200            let sample_rate = input.sample_rate();
201            let samples: Vec<f32> = input.convert_samples().collect();
202            let arc = Arc::new(samples);
203            let pos = Arc::new(AtomicUsize::new(0));
204            let buf = AudioBuffer {
205                channels,
206                sample_rate,
207                data: arc.clone(),
208                pos: pos.clone(),
209            };
210            let source = ResumableSource {
211                channels,
212                sample_rate,
213                data: arc,
214                pos,
215            };
216            inner.queue.push_back(buf);
217            inner.sink.append::<ResumableSource>(source);
218        });
219    }
220
221    /// Stops playback and clears queued sounds.
222    pub fn stop(&self) {
223        let mut inner = self.inner.lock().unwrap();
224        inner.sink.stop();
225        inner.queue.clear();
226    }
227
228    pub fn play(&self) {
229        let inner = self.inner.lock().unwrap();
230        inner.sink.play();
231    }
232
233    pub fn pause(&self) {
234        let inner = self.inner.lock().unwrap();
235        inner.sink.pause();
236    }
237
238    pub fn is_paused(&self) -> bool {
239        let inner = self.inner.lock().unwrap();
240        inner.sink.is_paused()
241    }
242
243    pub fn clear(&self) {
244        let mut inner = self.inner.lock().unwrap();
245        inner.sink.clear();
246        inner.queue.clear();
247    }
248
249    pub fn skip_one(&self) {
250        let mut inner = self.inner.lock().unwrap();
251        inner.sink.skip_one();
252        if !inner.queue.is_empty() {
253            inner.queue.pop_front();
254        }
255    }
256
257    pub fn sleep_until_end(&self) {
258        self.with_inner(|inner| inner.sink.sleep_until_end());
259    }
260
261    pub fn empty(&self) -> bool {
262        self.with_inner(|inner| inner.sink.empty())
263    }
264
265    pub fn len(&self) -> usize {
266        self.with_inner(|inner| inner.sink.len())
267    }
268
269    pub fn volume(&self) -> f32 {
270        self.with_inner(|inner| inner.sink.volume())
271    }
272
273    pub fn set_volume(&self, value: f32) {
274        self.with_inner(|inner| inner.sink.set_volume(value));
275    }
276
277    pub fn speed(&self) -> f32 {
278        self.with_inner(|inner| inner.sink.speed())
279    }
280
281    pub fn set_speed(&self, value: f32) {
282        self.with_inner(|inner| inner.sink.set_speed(value));
283    }
284}
285