using NAudio.CoreAudioApi; using RemSound.Core; namespace RemSound.App; /// /// The "Use Windows default audio device, follows Windows changes" choice, shared by the main window /// and the lock-screen service so both offer — and resolve — it identically. It is NOT a real endpoint: /// it's a sentinel that both persist in SelectedWasapiSendOutputs, and that the send-spec /// builders resolve to the CURRENT default render endpoint each time capture is (re)built. When Windows' /// default output changes, the device-change notifier drives a rebuild and the new default is picked up. /// /// Kept in one place because the service must track the main app: the main window already had this /// (its DefaultLoopbackSendFollower + ResolveDefaultDeviceId); the service reuses the very /// same sentinel and resolver rather than inventing a parallel one that would diverge. /// internal static class AudioDefaultFollower { /// Sentinel device id for "follow the Windows default OUTPUT (loopback send)". Namespaced so /// it can never collide with a real WASAPI endpoint id (which look like "{0.0.0.00000000}.{guid}"). internal const string LoopbackSendId = "__use-default-loopback-send__"; internal static bool IsLoopbackSend(string? deviceId) => string.Equals(deviceId, LoopbackSendId, StringComparison.OrdinalIgnoreCase); /// The follower list entry for the "WASAPI outputs to send" list. A fresh instance each call /// (list controls take ownership of their items). internal static AudioDeviceChoice LoopbackSendChoice() => new("Use Windows default audio device, follows Windows changes", LoopbackSendId, CaptureKind.Loopback) { IsDefaultFollower = true }; /// The current Windows default OUTPUT (render) endpoint id, or null if none. Convenience for /// callers that only follow the default output and shouldn't have to name a NAudio DataFlow. internal static string? ResolveDefaultRenderId() => ResolveDefaultDeviceId(DataFlow.Render); /// The current Windows default endpoint id for the given direction, or null if there isn't /// one (or it can't be read). Render = default speakers/output; Capture = default mic/input. internal static string? ResolveDefaultDeviceId(DataFlow flow) { try { using var enumerator = new MMDeviceEnumerator(); if (!enumerator.HasDefaultAudioEndpoint(flow, Role.Multimedia)) return null; using var device = enumerator.GetDefaultAudioEndpoint(flow, Role.Multimedia); return device.ID; } catch { return null; } } }