diff --git a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs index 8f0cea0d..1f6a3f77 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs @@ -15,7 +15,8 @@ public static class AudioManager { private static bool _initialized = false; - private static readonly MMDeviceEnumerator _enumerator = new(); + private static MMDeviceEnumerator _enumerator = new(); + private static MMNotificationClient _notificationClient = null; private static readonly ConcurrentDictionary _devices = new(); private static readonly ConcurrentQueue _devicesToBeRemoved = new(); @@ -23,57 +24,61 @@ public static class AudioManager private static readonly Dictionary _applicationNameCache = new(); - public static void Initialize() + private static void InitializeDevices() { - Log.Debug("[AUDIOMGR] Audio Manager initializing"); - foreach (var device in _enumerator.EnumAudioEndpoints(DataFlow.All, DeviceState.Active)) _devices[device.DeviceID] = new InternalAudioDevice(device); - var nc = new MMNotificationClient(_enumerator); - nc.DeviceAdded += (sender, e) => - { - if (_devices.ContainsKey(e.DeviceId)) - return; + _notificationClient = new MMNotificationClient(_enumerator); + _notificationClient.DeviceAdded += DeviceAdded; + _notificationClient.DeviceRemoved += DeviceRemoved; + _notificationClient.DeviceStateChanged += DeviceStateChanged; - _devicesToBeAdded.Enqueue(e.DeviceId); - }; + _initialized = true; + } - nc.DeviceRemoved += (sender, e) => + private static void DeviceStateChanged(object sender, DeviceStateChangedEventArgs e) + { + switch (e.DeviceState) { - _devicesToBeRemoved.Enqueue(e.DeviceId); - }; + case DeviceState.Active: + if (_devices.ContainsKey(e.DeviceId)) + return; - _initialized = true; + _devicesToBeAdded.Enqueue(e.DeviceId); + break; - nc.DeviceStateChanged += (sender, e) => - { - switch (e.DeviceState) - { - case DeviceState.Active: - if (_devices.ContainsKey(e.DeviceId)) - return; + case DeviceState.NotPresent: + case DeviceState.UnPlugged: + _devicesToBeRemoved.Enqueue(e.DeviceId); + break; - _devicesToBeAdded.Enqueue(e.DeviceId); - break; + default: + break; + } + } - case DeviceState.NotPresent: - case DeviceState.UnPlugged: - _devicesToBeRemoved.Enqueue(e.DeviceId); - break; + private static void DeviceRemoved(object sender, DeviceNotificationEventArgs e) + { + _devicesToBeRemoved.Enqueue(e.DeviceId); + } - default: - break; - } - }; + private static void DeviceAdded(object sender, DeviceNotificationEventArgs e) + { + if (_devices.ContainsKey(e.DeviceId)) + return; - Log.Information("[AUDIOMGR] Audio Manager initialized"); + _devicesToBeAdded.Enqueue(e.DeviceId); } - private static void CheckInitialization() + private static bool CheckInitialization() { if (!_initialized) - throw new InvalidOperationException("AudioManager is not initialized"); + { + Log.Warning("[AUDIOMGR] not yet initialized!"); + + return false; + } while (_devicesToBeRemoved.TryDequeue(out var deviceId)) { @@ -86,6 +91,8 @@ private static void CheckInitialization() var device = _enumerator.GetDevice(deviceId); _devices[deviceId] = new InternalAudioDevice(device); } + + return true; } private static string GetSessionDisplayName(InternalAudioSession session) @@ -155,37 +162,81 @@ private static string GetReadableState(DeviceState state) }; } - public static List GetDevices() + public static void Initialize() { - CheckInitialization(); + Log.Debug("[AUDIOMGR] initializing"); - var audioDevices = new List(); + InitializeDevices(); + + Log.Information("[AUDIOMGR] initialized"); + } + + public static void ReInitialize() + { + if (_initialized) + CleanupDevices(); + + Log.Debug("[AUDIOMGR] re-initializing"); + + _enumerator = new MMDeviceEnumerator(); + InitializeDevices(); + + Log.Information("[AUDIOMGR] re-initialized"); + } + + public static void CleanupDevices() + { + Log.Debug("[AUDIOMGR] starting cleanup"); + _initialized = false; - using var defaultInputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - using var defaultOutputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + foreach (var device in _devices.Values) + device.Dispose(); - var defaultInputDeviceId = defaultInputDevice.DeviceID; - var defaultOutputDeviceId = defaultOutputDevice.DeviceID; + _enumerator.Dispose(); + + _devices.Clear(); + Log.Debug("[AUDIOMGR] cleanup completed"); + } + + public static List GetDevices() + { + var audioDevices = new List(); - foreach (var device in _devices.Values.Where(d => d.MMDevice.DeviceState == DeviceState.Active)) + if (!CheckInitialization()) + return audioDevices; + + try { + using var defaultInputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + using var defaultOutputDevice = _enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - var audioSessions = GetDeviceSessions(device); - var loudestSession = audioSessions.MaxBy(s => s.PeakVolume); + var defaultInputDeviceId = defaultInputDevice.DeviceID; + var defaultOutputDeviceId = defaultOutputDevice.DeviceID; - var audioDevice = new AudioDevice + foreach (var device in _devices.Values.Where(d => d.MMDevice.DeviceState == DeviceState.Active)) { - State = GetReadableState(device.MMDevice.DeviceState), - Type = device.MMDevice.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, - Id = device.DeviceId, - FriendlyName = device.FriendlyName, - Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.GetMasterVolumeLevelScalar() * 100, 0)), - PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, - Sessions = audioSessions, - Default = device.DeviceId == defaultInputDeviceId || device.DeviceId == defaultOutputDeviceId - }; - - audioDevices.Add(audioDevice); + + var audioSessions = GetDeviceSessions(device); + var loudestSession = audioSessions.MaxBy(s => s.PeakVolume); + + var audioDevice = new AudioDevice + { + State = GetReadableState(device.MMDevice.DeviceState), + Type = device.MMDevice.DataFlow == DataFlow.Capture ? DeviceType.Input : DeviceType.Output, + Id = device.DeviceId, + FriendlyName = device.FriendlyName, + Volume = Convert.ToInt32(Math.Round(device.AudioEndpointVolume.GetMasterVolumeLevelScalar() * 100, 0)), + PeakVolume = loudestSession == null ? 0 : loudestSession.PeakVolume, + Sessions = audioSessions, + Default = device.DeviceId == defaultInputDeviceId || device.DeviceId == defaultOutputDeviceId + }; + + audioDevices.Add(audioDevice); + } + } + catch (Exception ex) + { + Log.Debug(ex, "[AUDIOMGR] Failed to retrieve devices: {msg}", ex.Message); } return audioDevices; @@ -193,6 +244,9 @@ public static List GetDevices() public static string GetDefaultDeviceId(DeviceType deviceType, DeviceRole deviceRole) { + if (!CheckInitialization()) + return string.Empty; + var dataFlow = deviceType == DeviceType.Input ? DataFlow.Capture : DataFlow.Render; var role = (Role)deviceRole; @@ -203,6 +257,9 @@ public static string GetDefaultDeviceId(DeviceType deviceType, DeviceRole device public static void Activate(AudioDevice audioDevice) { + if (!CheckInitialization()) + return; + if (!_devices.TryGetValue(audioDevice.Id, out var device)) return; @@ -211,6 +268,9 @@ public static void Activate(AudioDevice audioDevice) public static void SetVolume(AudioDevice audioDevice, int volume) { + if (!CheckInitialization()) + return; + if (!_devices.TryGetValue(audioDevice.Id, out var device)) return; @@ -220,6 +280,9 @@ public static void SetVolume(AudioDevice audioDevice, int volume) public static void SetVolume(AudioSession audioSession, int volume) { + if (!CheckInitialization()) + return; + var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); if (device == null) return; @@ -233,6 +296,9 @@ public static void SetVolume(AudioSession audioSession, int volume) public static void SetMute(AudioDevice audioDevice, bool mute) { + if (!CheckInitialization()) + return; + if (!_devices.TryGetValue(audioDevice.Id, out var device)) return; @@ -241,6 +307,9 @@ public static void SetMute(AudioDevice audioDevice, bool mute) public static void SetMute(AudioSession audioSession, bool mute) { + if (!CheckInitialization()) + return; + var device = _devices.Values.Where(d => d.FriendlyName == audioSession.PlaybackDevice).FirstOrDefault(); if (device == null) return; @@ -253,18 +322,15 @@ public static void SetMute(AudioSession audioSession, bool mute) public static void Shutdown() { - Log.Debug("[AUDIOMGR] Audio Manager shutting down"); + Log.Debug("[AUDIOMGR] shutting down"); try { - foreach(var device in _devices.Values) - device.Dispose(); - - _enumerator.Dispose(); + CleanupDevices(); } catch (Exception ex) { - Log.Fatal(ex, "[AUDIOMGR] Audio Manager shutdown fatal error: {ex}", ex.Message); + Log.Fatal(ex, "[AUDIOMGR] shutdown fatal error: {ex}", ex.Message); } - Log.Debug("[AUDIOMGR] Audio Manager shutdown completed"); + Log.Debug("[AUDIOMGR] shutdown completed"); } } diff --git a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs index 22fbfb1e..a8794708 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using HASS.Agent.Functions; using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Managers.Audio; using Microsoft.Win32; using Serilog; @@ -200,6 +201,9 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Log.Information("[SYSTEMSTATE] Session resuming"); Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync()); LastSystemStateEvent = SystemStateEvent.Resume; + + AudioManager.ReInitialize(); + break; case PowerModes.Suspend: @@ -209,6 +213,9 @@ private static void SystemEventsOnPowerModeChanged(object sender, PowerModeChang Log.Information("[SYSTEMSTATE] Session halting: system suspending"); Task.Run(() => Variables.MqttManager.AnnounceAvailabilityAsync(true)); LastSystemStateEvent = SystemStateEvent.Suspend; + + AudioManager.CleanupDevices(); + break; } }