refactor to remove CoreAudio nuget package and using microsoft's APIs directly to resolve memory leak
This commit is contained in:
parent
f9b23a62a3
commit
ceb3494e43
@ -1,33 +1,51 @@
|
|||||||
using CoreAudio;
|
using FocusVolumeControl.AudioSessions;
|
||||||
using FocusVolumeControl.AudioSessions;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace FocusVolumeControl;
|
namespace FocusVolumeControl;
|
||||||
|
|
||||||
public class AudioHelper
|
public class AudioHelper
|
||||||
{
|
{
|
||||||
IAudioSession _current;
|
static object _lock = new object();
|
||||||
List<Process> _currentProcesses;
|
List<Process> _currentProcesses;
|
||||||
|
|
||||||
|
public IAudioSession Current { get; private set; }
|
||||||
|
|
||||||
|
public void ResetCache()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IAudioSession FindSession(List<Process> processes)
|
public IAudioSession FindSession(List<Process> processes)
|
||||||
{
|
{
|
||||||
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
|
var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator();
|
||||||
|
|
||||||
using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device);
|
||||||
using var manager = device.AudioSessionManager2;
|
|
||||||
|
|
||||||
var sessions = manager.Sessions;
|
Guid iid = typeof(IAudioSessionManager2).GUID;
|
||||||
|
device.Activate(ref iid, 0, IntPtr.Zero, out var m);
|
||||||
|
var manager = (IAudioSessionManager2)m;
|
||||||
|
|
||||||
var matchingSession = new ActiveAudioSessionWrapper();
|
|
||||||
|
|
||||||
foreach (var session in sessions)
|
manager.GetSessionEnumerator(out var sessionEnumerator);
|
||||||
|
|
||||||
|
var results = new ActiveAudioSessionWrapper();
|
||||||
|
|
||||||
|
sessionEnumerator.GetCount(out var count);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var audioProcess = Process.GetProcessById((int)session.ProcessID);
|
sessionEnumerator.GetSession(i, out var session);
|
||||||
|
|
||||||
if (processes.Any(x => x.Id == session.ProcessID || x.ProcessName == audioProcess?.ProcessName))
|
session.GetProcessId(out var sessionProcessId);
|
||||||
|
var audioProcess = Process.GetProcessById(sessionProcessId);
|
||||||
|
|
||||||
|
if (processes.Any(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -36,31 +54,24 @@ public class AudioHelper
|
|||||||
{
|
{
|
||||||
displayName = audioProcess.ProcessName;
|
displayName = audioProcess.ProcessName;
|
||||||
}
|
}
|
||||||
matchingSession.DisplayName = displayName;
|
results.DisplayName = displayName;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
matchingSession.DisplayName ??= audioProcess.ProcessName;
|
results.DisplayName ??= audioProcess.ProcessName;
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingSession.ExecutablePath ??= audioProcess.MainModule.FileName;
|
results.ExecutablePath ??= audioProcess.MainModule.FileName;
|
||||||
|
|
||||||
//some apps like discord have multiple volume processes.
|
//some apps like discord have multiple volume processes.
|
||||||
matchingSession.AddVolume(session.SimpleAudioVolume);
|
results.AddSession(session);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matchingSession.Any() ? matchingSession : null;
|
|
||||||
|
return results.Any() ? results : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static object _lock = new object();
|
|
||||||
|
|
||||||
public void ResetCache()
|
|
||||||
{
|
|
||||||
lock(_lock)
|
|
||||||
{
|
|
||||||
_current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior)
|
public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior)
|
||||||
{
|
{
|
||||||
@ -70,23 +81,23 @@ public class AudioHelper
|
|||||||
|
|
||||||
if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processes))
|
if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processes))
|
||||||
{
|
{
|
||||||
_current = FindSession(processes);
|
Current = FindSession(processes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_current == null)
|
if (Current == null)
|
||||||
{
|
{
|
||||||
if(fallbackBehavior == FallbackBehavior.SystemSounds && _current is not SystemSoundsAudioSession)
|
if (fallbackBehavior == FallbackBehavior.SystemSounds && Current is not SystemSoundsAudioSession)
|
||||||
{
|
{
|
||||||
_current = GetSystemSounds();
|
Current = GetSystemSounds();
|
||||||
}
|
}
|
||||||
else if(fallbackBehavior == FallbackBehavior.SystemVolume && _current is not SystemVolumeAudioSession)
|
else if (fallbackBehavior == FallbackBehavior.SystemVolume && Current is not SystemVolumeAudioSession)
|
||||||
{
|
{
|
||||||
_current = GetSystemVolume();
|
Current = GetSystemVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentProcesses = processes;
|
_currentProcesses = processes;
|
||||||
return _current;
|
return Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,47 +153,65 @@ public class AudioHelper
|
|||||||
|
|
||||||
public void ResetAll()
|
public void ResetAll()
|
||||||
{
|
{
|
||||||
try
|
var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator();
|
||||||
|
|
||||||
|
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device);
|
||||||
|
|
||||||
|
Guid iid = typeof(IAudioSessionManager2).GUID;
|
||||||
|
device.Activate(ref iid, 0, IntPtr.Zero, out var m);
|
||||||
|
var manager = (IAudioSessionManager2)m;
|
||||||
|
|
||||||
|
|
||||||
|
manager.GetSessionEnumerator(out var sessionEnumerator);
|
||||||
|
|
||||||
|
sessionEnumerator.GetCount(out var count);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
|
sessionEnumerator.GetSession(i, out var session);
|
||||||
|
|
||||||
using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
var volume = (ISimpleAudioVolume)session;
|
||||||
using var manager = device.AudioSessionManager2;
|
var guid = Guid.Empty;
|
||||||
|
volume.SetMasterVolume(1, ref guid);
|
||||||
foreach (var session in manager.Sessions)
|
volume.SetMute(false, ref guid);
|
||||||
{
|
|
||||||
session.SimpleAudioVolume.MasterVolume = 1;
|
|
||||||
session.SimpleAudioVolume.Mute = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAudioSession GetSystemSounds()
|
public IAudioSession GetSystemSounds()
|
||||||
{
|
{
|
||||||
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
|
var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator();
|
||||||
|
|
||||||
using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device);
|
||||||
using var manager = device.AudioSessionManager2;
|
|
||||||
|
|
||||||
var sessions = manager.Sessions;
|
Guid iid = typeof(IAudioSessionManager2).GUID;
|
||||||
|
device.Activate(ref iid, 0, IntPtr.Zero, out var m);
|
||||||
|
var manager = (IAudioSessionManager2)m;
|
||||||
|
|
||||||
foreach (var session in sessions)
|
|
||||||
|
manager.GetSessionEnumerator(out var sessionEnumerator);
|
||||||
|
|
||||||
|
sessionEnumerator.GetCount(out var count);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
if (session.IsSystemSoundsSession)
|
sessionEnumerator.GetSession(i, out var session);
|
||||||
|
|
||||||
|
if (session.IsSystemSoundsSession() == 0)
|
||||||
{
|
{
|
||||||
return new SystemSoundsAudioSession(session.SimpleAudioVolume);
|
return new SystemSoundsAudioSession(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public IAudioSession GetSystemVolume()
|
public IAudioSession GetSystemVolume()
|
||||||
{
|
{
|
||||||
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
|
var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator();
|
||||||
|
|
||||||
using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device);
|
||||||
return new SystemVolumeAudioSession(device.AudioEndpointVolume);
|
|
||||||
|
Guid iid = typeof(IAudioEndpointVolume).GUID;
|
||||||
|
device.Activate(ref iid, 0, IntPtr.Zero, out var o);
|
||||||
|
var endpointVolume = (IAudioEndpointVolume)o;
|
||||||
|
|
||||||
|
return new SystemVolumeAudioSession(endpointVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
using CoreAudio;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BarRaider.SdTools;
|
using BarRaider.SdTools;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace FocusVolumeControl.AudioSessions;
|
namespace FocusVolumeControl.AudioSessions;
|
||||||
|
|
||||||
public class ActiveAudioSessionWrapper : IAudioSession
|
public sealed class ActiveAudioSessionWrapper : IAudioSession
|
||||||
{
|
{
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
public string ExecutablePath { get; set; }
|
public string ExecutablePath { get; set; }
|
||||||
private List<SimpleAudioVolume> Volume { get; } = new List<SimpleAudioVolume>();
|
private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>();
|
||||||
|
private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
|
||||||
|
|
||||||
string _icon;
|
string _icon;
|
||||||
|
|
||||||
@ -36,11 +37,11 @@ public class ActiveAudioSessionWrapper : IAudioSession
|
|||||||
{
|
{
|
||||||
return Volume.Any();
|
return Volume.Any();
|
||||||
}
|
}
|
||||||
public int Count => Volume.Count;
|
public int Count => Sessions.Count;
|
||||||
|
|
||||||
public void AddVolume(SimpleAudioVolume volume)
|
public void AddSession(IAudioSessionControl2 session)
|
||||||
{
|
{
|
||||||
Volume.Add(volume);
|
Sessions.Add(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleMute()
|
public void ToggleMute()
|
||||||
@ -51,28 +52,52 @@ public class ActiveAudioSessionWrapper : IAudioSession
|
|||||||
//when any volumes are unmuted, Volume.All will return false
|
//when any volumes are unmuted, Volume.All will return false
|
||||||
//so we set muted to true (opposite of Volume.All)
|
//so we set muted to true (opposite of Volume.All)
|
||||||
|
|
||||||
var muted = Volume.All(x => x.Mute);
|
var muted = IsMuted();
|
||||||
|
|
||||||
Volume.ForEach(x => x.Mute = !muted);
|
foreach(var v in Volume)
|
||||||
|
{
|
||||||
|
var guid = Guid.Empty;
|
||||||
|
v.SetMute(!muted, ref guid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsMuted()
|
public bool IsMuted()
|
||||||
{
|
{
|
||||||
return Volume.All(x => x.Mute);
|
return Volume.All(x =>
|
||||||
|
{
|
||||||
|
x.GetMute(out var mute);
|
||||||
|
return mute;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void IncrementVolumeLevel(int step, int ticks)
|
public void IncrementVolumeLevel(int step, int ticks)
|
||||||
{
|
{
|
||||||
//if you have more than one volume. they will all get set based on the first volume control
|
//if you have more than one volume. they will all get set based on the first volume control
|
||||||
var level = Volume.FirstOrDefault()?.MasterVolume ?? 0;
|
var volume = Volume.FirstOrDefault();
|
||||||
|
var level = 0f;
|
||||||
|
if (volume != null)
|
||||||
|
{
|
||||||
|
volume.GetMasterVolume(out level);
|
||||||
|
}
|
||||||
|
|
||||||
level = VolumeHelpers.GetAdjustedVolume(level, step, ticks);
|
level = VolumeHelpers.GetAdjustedVolume(level, step, ticks);
|
||||||
Volume.ForEach(x => x.MasterVolume = level);
|
|
||||||
|
foreach(var v in Volume)
|
||||||
|
{
|
||||||
|
var guid = Guid.Empty;
|
||||||
|
v.SetMasterVolume(level, ref guid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetVolumeLevel()
|
public int GetVolumeLevel()
|
||||||
{
|
{
|
||||||
var level = Volume.FirstOrDefault()?.MasterVolume ?? 0;
|
var volume = Volume.FirstOrDefault();
|
||||||
|
var level = 0f;
|
||||||
|
if(volume != null)
|
||||||
|
{
|
||||||
|
volume.GetMasterVolume(out level);
|
||||||
|
}
|
||||||
|
|
||||||
return VolumeHelpers.GetVolumePercentage(level);
|
return VolumeHelpers.GetVolumePercentage(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
306
src/FocusVolumeControl/AudioSessions/CoreAudio.cs
Normal file
306
src/FocusVolumeControl/AudioSessions/CoreAudio.cs
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace FocusVolumeControl.AudioSessions;
|
||||||
|
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
||||||
|
internal class MMDeviceEnumerator
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum EDataFlow
|
||||||
|
{
|
||||||
|
eRender,
|
||||||
|
eCapture,
|
||||||
|
eAll,
|
||||||
|
EDataFlow_enum_count
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum ERole
|
||||||
|
{
|
||||||
|
eConsole,
|
||||||
|
eMultimedia,
|
||||||
|
eCommunications,
|
||||||
|
ERole_enum_count
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface CoreAudio
|
||||||
|
{
|
||||||
|
int NotImpl1();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IMMDevice
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IAudioSessionManager2
|
||||||
|
{
|
||||||
|
int NotImpl1();
|
||||||
|
int NotImpl2();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IAudioSessionEnumerator
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int GetCount(out int SessionCount);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSession(int SessionCount, out IAudioSessionControl2 Session);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface ISimpleAudioVolume
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMasterVolume(float fLevel, ref Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMasterVolume(out float pfLevel);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMute(bool bMute, ref Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMute(out bool pbMute);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
public interface IAudioSessionControl2
|
||||||
|
{
|
||||||
|
// IAudioSessionControl
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl0();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetIconPath([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetGroupingParam(out Guid pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetGroupingParam([MarshalAs(UnmanagedType.LPStruct)] Guid Override, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl1();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl2();
|
||||||
|
|
||||||
|
// IAudioSessionControl2
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetProcessId(out int pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int IsSystemSoundsSession();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetDuckingPreference(bool optOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://netcoreaudio.codeplex.com/SourceControl/latest#trunk/Code/CoreAudio/Interfaces/IAudioEndpointVolume.cs
|
||||||
|
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
public interface IAudioEndpointVolume
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl1();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl2();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a count of the channels in the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelCount">The number of channels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetChannelCount(
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 channelCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the master volume level of the audio stream, in decibels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The new master volume level in decibels.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMasterVolumeLevel(
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the master volume level, expressed as a normalized, audio-tapered value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The new master volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMasterVolumeLevelScalar(
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the master volume level of the audio stream, in decibels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The volume level in decibels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMasterVolumeLevel(
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the master volume level, expressed as a normalized, audio-tapered value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMasterVolumeLevelScalar(
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the volume level, in decibels, of the specified channel of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The channel number.</param>
|
||||||
|
/// <param name="level">The new volume level in decibels.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetChannelVolumeLevel(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the normalized, audio-tapered volume level of the specified channel in the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The channel number.</param>
|
||||||
|
/// <param name="level">The new master volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetChannelVolumeLevelScalar(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the volume level, in decibels, of the specified channel in the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The zero-based channel number.</param>
|
||||||
|
/// <param name="level">The volume level in decibels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetChannelVolumeLevel(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the normalized, audio-tapered volume level of the specified channel of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The zero-based channel number.</param>
|
||||||
|
/// <param name="level">The volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetChannelVolumeLevelScalar(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the muting state of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMuted">True to mute the stream, or false to unmute the stream.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMute(
|
||||||
|
[In][MarshalAs(UnmanagedType.Bool)] Boolean isMuted,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the muting state of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMuted">The muting state. True if the stream is muted, false otherwise.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMute(
|
||||||
|
[Out][MarshalAs(UnmanagedType.Bool)] out Boolean isMuted);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about the current step in the volume range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="step">The current zero-based step index.</param>
|
||||||
|
/// <param name="stepCount">The total number of steps in the volume range.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetVolumeStepInfo(
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 step,
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 stepCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increases the volume level by one step.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int VolumeStepUp(
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decreases the volume level by one step.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int VolumeStepDown(
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries the audio endpoint device for its hardware-supported functions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hardwareSupportMask">A hardware support mask that indicates the capabilities of the endpoint.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int QueryHardwareSupport(
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 hardwareSupportMask);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the volume range of the audio stream, in decibels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="volumeMin">The minimum volume level in decibels.</param>
|
||||||
|
/// <param name="volumeMax">The maximum volume level in decibels.</param>
|
||||||
|
/// <param name="volumeStep">The volume increment level in decibels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetVolumeRange(
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float volumeMin,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float volumeMax,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float volumeStep);
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
namespace FocusVolumeControl.AudioSessions;
|
using System;
|
||||||
|
|
||||||
|
namespace FocusVolumeControl.AudioSessions;
|
||||||
|
|
||||||
public interface IAudioSession
|
public interface IAudioSession
|
||||||
{
|
{
|
||||||
|
@ -1,33 +1,46 @@
|
|||||||
using CoreAudio;
|
using System;
|
||||||
using System;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace FocusVolumeControl.AudioSessions;
|
namespace FocusVolumeControl.AudioSessions;
|
||||||
|
|
||||||
internal class SystemSoundsAudioSession : IAudioSession
|
internal sealed class SystemSoundsAudioSession : IAudioSession
|
||||||
{
|
{
|
||||||
public SystemSoundsAudioSession(SimpleAudioVolume volumeControl)
|
public SystemSoundsAudioSession(IAudioSessionControl2 sessionControl)
|
||||||
{
|
{
|
||||||
_volumeControl = volumeControl;
|
_sessionControl = sessionControl;
|
||||||
|
_volumeControl = (ISimpleAudioVolume)sessionControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleAudioVolume _volumeControl;
|
IAudioSessionControl2 _sessionControl;
|
||||||
|
ISimpleAudioVolume _volumeControl;
|
||||||
|
|
||||||
public string DisplayName => "System sounds";
|
public string DisplayName => "System sounds";
|
||||||
public string GetIcon() => "Images/systemSounds";
|
public string GetIcon() => "Images/systemSounds";
|
||||||
|
|
||||||
public void ToggleMute()
|
public void ToggleMute()
|
||||||
{
|
{
|
||||||
_volumeControl.Mute = !_volumeControl.Mute;
|
var guid = Guid.Empty;
|
||||||
|
_volumeControl.SetMute(!IsMuted(), ref guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsMuted() => _volumeControl.Mute;
|
public bool IsMuted()
|
||||||
|
{
|
||||||
|
_volumeControl.GetMute(out var mute);
|
||||||
|
return mute;
|
||||||
|
}
|
||||||
|
|
||||||
public void IncrementVolumeLevel(int step, int ticks)
|
public void IncrementVolumeLevel(int step, int ticks)
|
||||||
{
|
{
|
||||||
var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolume, step, ticks);
|
_volumeControl.GetMasterVolume(out var level);
|
||||||
_volumeControl.MasterVolume = level;
|
level = VolumeHelpers.GetAdjustedVolume(level, step, ticks);
|
||||||
|
|
||||||
|
var guid = Guid.Empty;
|
||||||
|
_volumeControl.SetMasterVolume(level, ref guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetVolumeLevel() => VolumeHelpers.GetVolumePercentage(_volumeControl.MasterVolume);
|
public int GetVolumeLevel()
|
||||||
|
{
|
||||||
|
_volumeControl.GetMasterVolume(out var level);
|
||||||
|
return VolumeHelpers.GetVolumePercentage(level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,41 @@
|
|||||||
using CoreAudio;
|
using System;
|
||||||
using System;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace FocusVolumeControl.AudioSessions;
|
namespace FocusVolumeControl.AudioSessions;
|
||||||
|
|
||||||
internal class SystemVolumeAudioSession : IAudioSession
|
internal sealed class SystemVolumeAudioSession : IAudioSession
|
||||||
{
|
{
|
||||||
public SystemVolumeAudioSession(AudioEndpointVolume volumeControl)
|
public SystemVolumeAudioSession(IAudioEndpointVolume volumeControl)
|
||||||
{
|
{
|
||||||
_volumeControl = volumeControl;
|
_volumeControl = volumeControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioEndpointVolume _volumeControl;
|
IAudioEndpointVolume _volumeControl;
|
||||||
|
|
||||||
public string DisplayName => "System Volume";
|
public string DisplayName => "System Volume";
|
||||||
public string GetIcon() => "Images/encoderIcon";
|
public string GetIcon() => "Images/encoderIcon";
|
||||||
|
|
||||||
public void ToggleMute()
|
public void ToggleMute()
|
||||||
{
|
{
|
||||||
_volumeControl.Mute = !_volumeControl.Mute;
|
_volumeControl.SetMute(!IsMuted(), Guid.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsMuted() => _volumeControl.Mute;
|
public bool IsMuted()
|
||||||
|
{
|
||||||
|
_volumeControl.GetMute(out var mute);
|
||||||
|
return mute;
|
||||||
|
}
|
||||||
|
|
||||||
public void IncrementVolumeLevel(int step, int ticks)
|
public void IncrementVolumeLevel(int step, int ticks)
|
||||||
{
|
{
|
||||||
var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolumeLevelScalar, step, ticks);
|
_volumeControl.GetMasterVolumeLevelScalar(out var level);
|
||||||
_volumeControl.MasterVolumeLevelScalar = level;
|
level = VolumeHelpers.GetAdjustedVolume(level, step, ticks);
|
||||||
|
_volumeControl.SetMasterVolumeLevelScalar(level, Guid.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetVolumeLevel() => VolumeHelpers.GetVolumePercentage(_volumeControl.MasterVolumeLevelScalar);
|
public int GetVolumeLevel()
|
||||||
|
{
|
||||||
|
_volumeControl.GetMasterVolumeLevelScalar(out var level);
|
||||||
|
return VolumeHelpers.GetVolumePercentage(level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ public class DialAction : EncoderBase
|
|||||||
IntPtr _foregroundWindowChangedEvent;
|
IntPtr _foregroundWindowChangedEvent;
|
||||||
Native.WinEventDelegate _delegate;
|
Native.WinEventDelegate _delegate;
|
||||||
|
|
||||||
IAudioSession _currentAudioSession;
|
|
||||||
AudioHelper _audioHelper = new AudioHelper();
|
AudioHelper _audioHelper = new AudioHelper();
|
||||||
|
|
||||||
Thread _thread;
|
Thread _thread;
|
||||||
@ -48,7 +47,7 @@ public class DialAction : EncoderBase
|
|||||||
if (payload.Settings == null || payload.Settings.Count == 0)
|
if (payload.Settings == null || payload.Settings.Count == 0)
|
||||||
{
|
{
|
||||||
settings = PluginSettings.CreateDefaultSettings();
|
settings = PluginSettings.CreateDefaultSettings();
|
||||||
SaveSettings();
|
_ = SaveSettings();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -69,8 +68,8 @@ public class DialAction : EncoderBase
|
|||||||
_thread.SetApartmentState(ApartmentState.STA);
|
_thread.SetApartmentState(ApartmentState.STA);
|
||||||
_thread.Start();
|
_thread.Start();
|
||||||
|
|
||||||
_currentAudioSession = settings.FallbackBehavior == FallbackBehavior.SystemSounds ? _audioHelper.GetSystemSounds() : _audioHelper.GetSystemVolume();
|
var session = _audioHelper.GetActiveSession(settings.FallbackBehavior);
|
||||||
_ = UpdateStateIfNeeded();
|
_ = UpdateStateIfNeeded(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
@ -123,10 +122,11 @@ public class DialAction : EncoderBase
|
|||||||
{
|
{
|
||||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate");
|
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate");
|
||||||
//dial rotated. ticks positive for right, negative for left
|
//dial rotated. ticks positive for right, negative for left
|
||||||
if (_currentAudioSession != null)
|
var activeSession = _audioHelper.Current;
|
||||||
|
if (activeSession != null)
|
||||||
{
|
{
|
||||||
_currentAudioSession.IncrementVolumeLevel(settings.StepSize, payload.Ticks);
|
activeSession.IncrementVolumeLevel(settings.StepSize, payload.Ticks);
|
||||||
await UpdateStateIfNeeded();
|
await UpdateStateIfNeeded(activeSession);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -159,10 +159,11 @@ public class DialAction : EncoderBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_currentAudioSession != null)
|
var activeSession = _audioHelper.Current;
|
||||||
|
if (activeSession != null)
|
||||||
{
|
{
|
||||||
_currentAudioSession.ToggleMute();
|
activeSession.ToggleMute();
|
||||||
await UpdateStateIfNeeded();
|
await UpdateStateIfNeeded(activeSession);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -184,12 +185,7 @@ public class DialAction : EncoderBase
|
|||||||
//called once every 1000ms and can be used for updating the title/image of the key
|
//called once every 1000ms and can be used for updating the title/image of the key
|
||||||
var activeSession = _audioHelper.GetActiveSession(settings.FallbackBehavior);
|
var activeSession = _audioHelper.GetActiveSession(settings.FallbackBehavior);
|
||||||
|
|
||||||
if (activeSession != null)
|
await UpdateStateIfNeeded(activeSession);
|
||||||
{
|
|
||||||
_currentAudioSession = activeSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
await UpdateStateIfNeeded();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -198,14 +194,14 @@ public class DialAction : EncoderBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateStateIfNeeded()
|
private async Task UpdateStateIfNeeded(IAudioSession audioSession)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_currentAudioSession != null)
|
if (audioSession != null)
|
||||||
{
|
{
|
||||||
|
|
||||||
var uiState = new UIState(_currentAudioSession);
|
var uiState = new UIState(audioSession);
|
||||||
|
|
||||||
if (_previousState != null && uiState != null &&
|
if (_previousState != null && uiState != null &&
|
||||||
uiState.Title == _previousState.Title &&
|
uiState.Title == _previousState.Title &&
|
||||||
@ -241,7 +237,7 @@ public class DialAction : EncoderBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Tools.AutoPopulateSettings(settings, payload.Settings);
|
Tools.AutoPopulateSettings(settings, payload.Settings);
|
||||||
SaveSettings();
|
_ = SaveSettings();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
|
<Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
|
||||||
<Compile Include="AudioHelper.cs" />
|
<Compile Include="AudioHelper.cs" />
|
||||||
|
<Compile Include="AudioSessions\CoreAudio.cs" />
|
||||||
<Compile Include="AudioSessions\VolumeHelpers.cs" />
|
<Compile Include="AudioSessions\VolumeHelpers.cs" />
|
||||||
<Compile Include="AudioSessions\SystemSoundsAudioSession.cs" />
|
<Compile Include="AudioSessions\SystemSoundsAudioSession.cs" />
|
||||||
<Compile Include="AudioSessions\SystemVolumeAudioSession.cs" />
|
<Compile Include="AudioSessions\SystemVolumeAudioSession.cs" />
|
||||||
@ -92,9 +93,6 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CoreAudio">
|
|
||||||
<Version>1.27.0</Version>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="IsExternalInit">
|
<PackageReference Include="IsExternalInit">
|
||||||
<Version>1.0.3</Version>
|
<Version>1.0.3</Version>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
"Name": "Focused Application Volume",
|
"Name": "Focused Application Volume",
|
||||||
"Description": "Control the volume of the focused application",
|
"Description": "Control the volume of the focused application",
|
||||||
"URL": "https://github.com/dlprows/FocusVolumeControl",
|
"URL": "https://github.com/dlprows/FocusVolumeControl",
|
||||||
"Version": "1.1.0",
|
"Version": "1.1.1",
|
||||||
"CodePath": "FocusVolumeControl",
|
"CodePath": "FocusVolumeControl",
|
||||||
"Category": "Volume Control [dlprows]",
|
"Category": "Volume Control [dlprows]",
|
||||||
"Icon": "Images/pluginIcon",
|
"Icon": "Images/pluginIcon",
|
||||||
|
Loading…
Reference in New Issue
Block a user