Skip to content

Commit

Permalink
audio manager is now reinitialized upon system resume
Browse files Browse the repository at this point in the history
  • Loading branch information
amadeo-alex committed May 17, 2024
1 parent c8cb353 commit 98e7940
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 64 deletions.
194 changes: 130 additions & 64 deletions src/HASS.Agent/HASS.Agent.Shared/Managers/Audio/AudioManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,65 +15,70 @@ 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<string, InternalAudioDevice> _devices = new();
private static readonly ConcurrentQueue<string> _devicesToBeRemoved = new();
private static readonly ConcurrentQueue<string> _devicesToBeAdded = new();

private static readonly Dictionary<int, string> _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))
{
Expand All @@ -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)
Expand Down Expand Up @@ -155,44 +162,91 @@ private static string GetReadableState(DeviceState state)
};
}

public static List<AudioDevice> GetDevices()
public static void Initialize()
{
CheckInitialization();
Log.Debug("[AUDIOMGR] initializing");

var audioDevices = new List<AudioDevice>();
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<AudioDevice> GetDevices()
{
var audioDevices = new List<AudioDevice>();

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;
}

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;

Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand All @@ -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");
}
}
7 changes: 7 additions & 0 deletions src/HASS.Agent/HASS.Agent/Managers/SystemStateManager.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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:
Expand All @@ -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;
}
}
Expand Down

0 comments on commit 98e7940

Please sign in to comment.