d5e827112a
- Created PROTOCOL.md to outline the MusicBridge protocol v1, detailing setup, TLS, pairing, state management, and command structure. - Implemented SystemVolume class for managing system audio levels, including methods for setting, reading, changing, and muting volume. - Added unit tests for the MusicBridge agent, covering pairing, identity storage, command validation, and media state management. - Included tests for real-time media reading from Windows and ensured proper handling of artwork caching and retrieval.
97 lines
3.9 KiB
C#
97 lines
3.9 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace MusicBridge.Agent;
|
|
|
|
// Obtain the current default output for each operation, including after device changes.
|
|
internal static class SystemVolume
|
|
{
|
|
public static void Set(float level) => WithEndpoint(endpoint =>
|
|
{
|
|
if (!float.IsFinite(level) || level < 0 || level > 1)
|
|
throw new ArgumentOutOfRangeException(nameof(level));
|
|
var context = Guid.Empty;
|
|
endpoint.SetMasterVolumeLevelScalar(level, ref context);
|
|
return true;
|
|
});
|
|
|
|
public static (float Level, bool Muted) Read() => WithEndpoint(endpoint =>
|
|
{
|
|
endpoint.GetMasterVolumeLevelScalar(out var level);
|
|
endpoint.GetMute(out var muted);
|
|
return (level, muted);
|
|
});
|
|
|
|
public static void Change(float delta) => WithEndpoint(endpoint =>
|
|
{
|
|
endpoint.GetMasterVolumeLevelScalar(out var level);
|
|
var context = Guid.Empty;
|
|
endpoint.SetMasterVolumeLevelScalar(Math.Clamp(level + delta, 0f, 1f), ref context);
|
|
return true;
|
|
});
|
|
|
|
public static void ToggleMute() => WithEndpoint(endpoint =>
|
|
{
|
|
endpoint.GetMute(out var muted);
|
|
var context = Guid.Empty;
|
|
endpoint.SetMute(!muted, ref context);
|
|
return true;
|
|
});
|
|
|
|
private static T WithEndpoint<T>(Func<IAudioEndpointVolume, T> action)
|
|
{
|
|
var enumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
|
IMMDevice? device = null;
|
|
object? volume = null;
|
|
try
|
|
{
|
|
enumerator.GetDefaultAudioEndpoint(0, 1, out device); // Render, Multimedia
|
|
var iid = typeof(IAudioEndpointVolume).GUID;
|
|
device.Activate(ref iid, 23, IntPtr.Zero, out volume);
|
|
return action((IAudioEndpointVolume)volume);
|
|
}
|
|
finally
|
|
{
|
|
if (volume is not null) Marshal.ReleaseComObject(volume);
|
|
if (device is not null) Marshal.ReleaseComObject(device);
|
|
Marshal.ReleaseComObject(enumerator);
|
|
}
|
|
}
|
|
|
|
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
|
private class MMDeviceEnumerator { }
|
|
|
|
// COM declarations must preserve the native vtable order, including unused methods.
|
|
// HRESULT failures are translated to exceptions by the COM interop runtime.
|
|
[ComImport, Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
private interface IMMDeviceEnumerator
|
|
{
|
|
void EnumAudioEndpoints(int flow, uint mask, out IntPtr devices);
|
|
void GetDefaultAudioEndpoint(int flow, int role, out IMMDevice device);
|
|
}
|
|
|
|
[ComImport, Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
private interface IMMDevice
|
|
{
|
|
void Activate(ref Guid iid, uint context, IntPtr activation,
|
|
[MarshalAs(UnmanagedType.IUnknown)] out object instance);
|
|
}
|
|
|
|
[ComImport, Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
private interface IAudioEndpointVolume
|
|
{
|
|
void RegisterControlChangeNotify(IntPtr callback);
|
|
void UnregisterControlChangeNotify(IntPtr callback);
|
|
void GetChannelCount(out uint count);
|
|
void SetMasterVolumeLevel(float level, ref Guid context);
|
|
void SetMasterVolumeLevelScalar(float level, ref Guid context);
|
|
void GetMasterVolumeLevel(out float level);
|
|
void GetMasterVolumeLevelScalar(out float level);
|
|
void SetChannelVolumeLevel(uint channel, float level, ref Guid context);
|
|
void SetChannelVolumeLevelScalar(uint channel, float level, ref Guid context);
|
|
void GetChannelVolumeLevel(uint channel, out float level);
|
|
void GetChannelVolumeLevelScalar(uint channel, out float level);
|
|
void SetMute([MarshalAs(UnmanagedType.Bool)] bool muted, ref Guid context);
|
|
void GetMute([MarshalAs(UnmanagedType.Bool)] out bool muted);
|
|
}
|
|
}
|