diff --git a/examples/boilerplate-solid-ts/dist/index.html b/examples/boilerplate-solid-ts/dist/index.html
index 78026713..40b6af96 100644
--- a/examples/boilerplate-solid-ts/dist/index.html
+++ b/examples/boilerplate-solid-ts/dist/index.html
@@ -5,7 +5,7 @@
- vol: {output.audio?.volume} dev: {output.audio?.currentDevice}
+ {output.audio?.devices[output.audio?.defaultDevice!].name} :
+ {output.audio?.devices[output.audio?.defaultDevice!].volume}
CPU usage: {output.cpu?.usage}
diff --git a/packages/client-api/src/providers/audio/audio-provider-types.ts b/packages/client-api/src/providers/audio/audio-provider-types.ts
index f1a9d229..6c170e61 100644
--- a/packages/client-api/src/providers/audio/audio-provider-types.ts
+++ b/packages/client-api/src/providers/audio/audio-provider-types.ts
@@ -6,8 +6,15 @@ export interface AudioProviderConfig {
export type AudioProvider = Provider
;
-export interface AudioOutput {
+export interface AudioDeviceInfo {
+ deviceId: string;
+ name: string;
volume: number;
- currentDevice: string;
+ isDefault: boolean;
+}
+
+export interface AudioOutput {
+ devices: Record;
+ defaultDevice: string | null;
}
diff --git a/packages/desktop/src/providers/audio/audio_provider.rs b/packages/desktop/src/providers/audio/audio_provider.rs
index 7570a178..8f54e09e 100644
--- a/packages/desktop/src/providers/audio/audio_provider.rs
+++ b/packages/desktop/src/providers/audio/audio_provider.rs
@@ -1,4 +1,7 @@
-use std::sync::{Arc, Mutex, OnceLock};
+use std::{
+ collections::HashMap,
+ sync::{Arc, Mutex, OnceLock},
+};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
@@ -16,12 +19,13 @@ use windows::Win32::{
},
IMMDevice, IMMDeviceEnumerator, IMMNotificationClient,
IMMNotificationClient_Impl, MMDeviceEnumerator, DEVICE_STATE,
+ DEVICE_STATE_ACTIVE,
},
System::Com::{
CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED,
STGM_READ,
},
- UI::Shell::PropertiesSystem::IPropertyStore,
+ UI::Shell::PropertiesSystem::{IPropertyStore, PROPERTYKEY},
};
use windows_core::PCWSTR;
@@ -37,81 +41,32 @@ pub struct AudioProviderConfig {}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
-pub struct AudioOutput {
- pub current_device: String,
+pub struct AudioDeviceInfo {
+ pub name: String,
pub volume: f32,
+ pub is_default: bool,
}
-pub struct AudioProvider {
- _config: AudioProviderConfig,
-}
-
-#[async_trait]
-impl Provider for AudioProvider {
- async fn run(&self, emit_result_tx: Sender) {
- PROVIDER_TX
- .set(emit_result_tx.clone())
- .expect("Error setting provider tx in audio provider");
-
- AUDIO_STATE
- .set(Arc::new(Mutex::new(AudioOutput {
- current_device: "n/a".to_string(),
- volume: 0.0,
- })))
- .expect("Error setting initial audio state");
-
- task::spawn_blocking(move || {
- if let Err(err) = Self::create_audio_manager() {
- emit_result_tx
- .blocking_send(Err(err).into())
- .expect("Error with media provider");
- }
- });
- }
+#[derive(Debug, Clone, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AudioOutput {
+ pub devices: HashMap,
+ pub default_device: Option,
}
-impl AudioProvider {
- pub fn new(config: AudioProviderConfig) -> Self {
- Self { _config: config }
- }
-
- fn emit_volume() {
- if let Some(tx) = PROVIDER_TX.get() {
- let output = AUDIO_STATE.get().unwrap().lock().unwrap().clone();
- let _ = tx.try_send(Ok(ProviderOutput::Audio(output)).into());
+impl AudioOutput {
+ fn new() -> Self {
+ Self {
+ devices: HashMap::new(),
+ default_device: None,
}
}
+}
- fn create_audio_manager() -> anyhow::Result<()> {
- unsafe {
- let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
-
- let enumerator: IMMDeviceEnumerator =
- CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?;
-
- let handler = MediaDeviceEventHandler::new(enumerator.clone());
- let device_notification_callback =
- IMMNotificationClient::from(handler.clone());
-
- let default_device =
- enumerator.GetDefaultAudioEndpoint(eRender, eMultimedia)?;
- if let Ok(name) =
- MediaDeviceEventHandler::get_device_name(&default_device)
- {
- println!("Default audio render device: {}", name);
- }
-
- // Set up initial volume monitoring
- handler.setup_volume_monitoring(&default_device)?;
- enumerator.RegisterEndpointNotificationCallback(
- &device_notification_callback,
- )?;
-
- loop {
- std::thread::sleep(std::time::Duration::from_secs(1));
- }
- }
- }
+#[derive(Clone)]
+struct DeviceInfo {
+ name: String,
+ endpoint_volume: IAudioEndpointVolume,
}
#[derive(Clone)]
@@ -121,19 +76,20 @@ impl AudioProvider {
)]
struct MediaDeviceEventHandler {
enumerator: IMMDeviceEnumerator,
- device_state:
- Arc>>,
+ device_state: Arc>>,
+ current_device: String,
}
impl MediaDeviceEventHandler {
fn new(enumerator: IMMDeviceEnumerator) -> Self {
Self {
enumerator,
- device_state: Arc::new(Mutex::new(Vec::new())),
+ device_state: Arc::new(Mutex::new(HashMap::new())),
+ current_device: String::new(),
}
}
- fn get_device_name(device: &IMMDevice) -> windows_core::Result {
+ fn get_device_name(device: &IMMDevice) -> windows::core::Result {
unsafe {
let store: IPropertyStore = device.OpenPropertyStore(STGM_READ)?;
let value = store.GetValue(&PKEY_Device_FriendlyName)?;
@@ -144,38 +100,136 @@ impl MediaDeviceEventHandler {
fn setup_volume_monitoring(
&self,
device: &IMMDevice,
- ) -> windows_core::Result<()> {
+ ) -> windows::core::Result<(String, String)> {
+ unsafe {
+ let device_id = device.GetId()?.to_string()?;
+ let device_name = Self::get_device_name(device)?;
+ let mut device_state = self.device_state.lock().unwrap();
+
+ if !device_state.contains_key(&device_id) {
+ let endpoint_volume: IAudioEndpointVolume =
+ device.Activate(CLSCTX_ALL, None)?;
+ let mut handler = self.clone();
+ handler.current_device = device_id.clone();
+ let callback = IAudioEndpointVolumeCallback::from(handler);
+ endpoint_volume.RegisterControlChangeNotify(&callback)?;
+ device_state.insert(
+ device_id.clone(),
+ DeviceInfo {
+ name: device_name.clone(),
+ endpoint_volume,
+ },
+ );
+ }
+
+ Ok((device_id, device_name))
+ }
+ }
+
+ fn enumerate_devices(&self) -> windows::core::Result<()> {
unsafe {
- let endpoint_volume: IAudioEndpointVolume =
- device.Activate(CLSCTX_ALL, None)?;
- let handler = MediaDeviceEventHandler::new(self.enumerator.clone());
- let volume_callback = IAudioEndpointVolumeCallback::from(handler);
- endpoint_volume.RegisterControlChangeNotify(&volume_callback)?;
+ let collection = self
+ .enumerator
+ .EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE)?;
+ let mut audio_state = AUDIO_STATE.get().unwrap().lock().unwrap();
+ let mut devices = HashMap::new();
+
+ for i in 0..collection.GetCount()? {
+ if let Ok(device) = collection.Item(i) {
+ let (device_id, device_name) =
+ self.setup_volume_monitoring(&device)?;
+ let volume = self
+ .device_state
+ .lock()
+ .unwrap()
+ .get(&device_id)
+ .map(|d| {
+ d.endpoint_volume
+ .GetMasterVolumeLevelScalar()
+ .unwrap_or(0.0)
+ })
+ .unwrap_or(0.0);
+ let is_default = self
+ .enumerator
+ .GetDefaultAudioEndpoint(eRender, eMultimedia)
+ .ok()
+ .and_then(|d| d.GetId().ok())
+ .and_then(|id| id.to_string().ok())
+ .as_ref()
+ .map(|id| id == &device_id)
+ .unwrap_or(false);
+
+ devices.insert(
+ device_id,
+ AudioDeviceInfo {
+ name: device_name,
+ volume,
+ is_default,
+ },
+ );
+ }
+ }
+
self
.device_state
.lock()
.unwrap()
- .push((volume_callback, endpoint_volume));
+ .retain(|id, _| devices.contains_key(id));
+ audio_state.devices = devices;
+ audio_state.default_device = self
+ .enumerator
+ .GetDefaultAudioEndpoint(eRender, eMultimedia)
+ .ok()
+ .and_then(|d| d.GetId().ok())
+ .and_then(|id| id.to_string().ok());
}
+
+ AudioProvider::emit_volume();
Ok(())
}
}
+impl Drop for MediaDeviceEventHandler {
+ fn drop(&mut self) {
+ unsafe {
+ let mut device_state = self.device_state.lock().unwrap();
+ for (device_id, device_info) in device_state.iter() {
+ device_info
+ .endpoint_volume
+ .UnregisterControlChangeNotify(
+ &IAudioEndpointVolumeCallback::from(self.clone()),
+ )
+ .expect("Failed to unregister volume callback");
+ }
+ }
+ }
+}
+
impl IAudioEndpointVolumeCallback_Impl for MediaDeviceEventHandler_Impl {
fn OnNotify(
&self,
data: *mut windows::Win32::Media::Audio::AUDIO_VOLUME_NOTIFICATION_DATA,
- ) -> windows_core::Result<()> {
- if let Some(data) = unsafe { data.as_ref() } {
- if let Some(state) = AUDIO_STATE.get() {
- if let Ok(mut output) = state.lock() {
- output.volume = data.fMasterVolume;
- println!("Volume update: {}", data.fMasterVolume);
+ ) -> windows::core::Result<()> {
+ unsafe {
+ if let Some(data) = data.as_ref() {
+ let device_id = &*self.current_device;
+ println!("Got notification for device: {}", device_id);
+
+ if let Some(state) = AUDIO_STATE.get() {
+ let mut output = state.lock().unwrap();
+ if let Some(device) = output.devices.get_mut(device_id) {
+ device.volume = data.fMasterVolume;
+ println!(
+ "Volume update for {} (ID: {}): {}",
+ device.name, device_id, data.fMasterVolume
+ );
+ drop(output);
+ AudioProvider::emit_volume();
+ }
}
- AudioProvider::emit_volume();
}
+ Ok(())
}
- Ok(())
}
}
@@ -184,63 +238,101 @@ impl IMMNotificationClient_Impl for MediaDeviceEventHandler_Impl {
&self,
flow: EDataFlow,
role: ERole,
- pwstrDefaultDeviceId: &PCWSTR,
- ) -> windows_core::Result<()> {
- unsafe {
- if flow == eRender && role == eMultimedia {
- let device = self.enumerator.GetDevice(*pwstrDefaultDeviceId)?;
- if let Ok(name) = MediaDeviceEventHandler::get_device_name(&device)
- {
- println!("Default device changed to: {}", name);
- self.setup_volume_monitoring(&device)?;
- if let Ok(mut output) = AUDIO_STATE.get().unwrap().lock() {
- output.current_device = name;
- }
- AudioProvider::emit_volume();
- }
- }
+ _pwstrDefaultDeviceId: &PCWSTR,
+ ) -> windows::core::Result<()> {
+ if flow == eRender && role == eMultimedia {
+ self.enumerate_devices()?;
}
Ok(())
}
- // Unused fns, required for IMMNotificationClient_Impl
fn OnDeviceStateChanged(
&self,
- pwstrDeviceId: &PCWSTR,
- dwNewState: DEVICE_STATE,
- ) -> windows_core::Result<()> {
- let device_id = unsafe { pwstrDeviceId.to_string()? };
- println!(
- "Device state changed: {} - State: {:?}",
- device_id, dwNewState
- );
- Ok(())
+ _pwstrDeviceId: &PCWSTR,
+ _dwNewState: DEVICE_STATE,
+ ) -> windows::core::Result<()> {
+ self.enumerate_devices()
}
fn OnDeviceAdded(
&self,
- pwstrDeviceId: &PCWSTR,
- ) -> windows_core::Result<()> {
- let device_id = unsafe { pwstrDeviceId.to_string()? };
- println!("Device added: {}", device_id);
- Ok(())
+ _pwstrDeviceId: &PCWSTR,
+ ) -> windows::core::Result<()> {
+ self.enumerate_devices()
}
fn OnDeviceRemoved(
&self,
- pwstrDeviceId: &PCWSTR,
- ) -> windows_core::Result<()> {
- let device_id = unsafe { pwstrDeviceId.to_string()? };
- println!("Device removed: {}", device_id);
- Ok(())
+ _pwstrDeviceId: &PCWSTR,
+ ) -> windows::core::Result<()> {
+ self.enumerate_devices()
}
fn OnPropertyValueChanged(
&self,
- pwstrDeviceId: &PCWSTR,
- key: &windows::Win32::UI::Shell::PropertiesSystem::PROPERTYKEY,
- ) -> windows_core::Result<()> {
- let device_id = unsafe { pwstrDeviceId.to_string()? };
+ _pwstrDeviceId: &PCWSTR,
+ _key: &PROPERTYKEY,
+ ) -> windows::core::Result<()> {
Ok(())
}
}
+
+pub struct AudioProvider {
+ _config: AudioProviderConfig,
+}
+
+impl AudioProvider {
+ pub fn new(config: AudioProviderConfig) -> Self {
+ Self { _config: config }
+ }
+
+ fn emit_volume() {
+ if let Some(tx) = PROVIDER_TX.get() {
+ let output = AUDIO_STATE.get().unwrap().lock().unwrap().clone();
+ println!("Emitting audio output: {:#?}", output);
+ let _ = tx.try_send(Ok(ProviderOutput::Audio(output)).into());
+ }
+ }
+
+ fn create_audio_manager() -> anyhow::Result<()> {
+ unsafe {
+ let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
+ let enumerator: IMMDeviceEnumerator =
+ CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?;
+ let handler = MediaDeviceEventHandler::new(enumerator.clone());
+
+ handler.enumerate_devices()?;
+
+ let device_notification_callback =
+ IMMNotificationClient::from(handler.clone());
+ enumerator.RegisterEndpointNotificationCallback(
+ &device_notification_callback,
+ )?;
+
+ loop {
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ }
+ }
+ }
+}
+
+#[async_trait]
+impl Provider for AudioProvider {
+ async fn run(&self, emit_result_tx: Sender) {
+ PROVIDER_TX
+ .set(emit_result_tx.clone())
+ .expect("Error setting provider tx in audio provider");
+
+ AUDIO_STATE
+ .set(Arc::new(Mutex::new(AudioOutput::new())))
+ .expect("Error setting initial audio state");
+
+ task::spawn_blocking(move || {
+ if let Err(err) = Self::create_audio_manager() {
+ emit_result_tx
+ .blocking_send(Err(err).into())
+ .expect("Error with media provider");
+ }
+ });
+ }
+}