Repository navigation
Expand file tree
/
Copy pathAudioDeviceControlFolderProvider.cs
More file actions
145 lines (125 loc) · 4.87 KB
/
Copy pathAudioDeviceControlFolderProvider.cs
File metadata and controls
145 lines (125 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using LoupixDeck.PluginSdk;
namespace LoupixDeck.Plugin.Audio;
/// <summary>
/// Sub-folder shown after the user picks a specific audio endpoint. Slot 0 shows
/// the live volume, slot 1 toggles mute. The first rotary adjusts volume by 2%.
/// </summary>
public sealed class AudioDeviceControlFolderProvider : FolderProviderBase
{
private const float StepScalar = 0.02f;
private readonly IAudioService _audio;
private readonly AudioEndpointInfo _endpoint;
private readonly AudioEndpointKind _kind;
private readonly AudioAliasStore _aliasStore;
private readonly IPluginHost _host;
private IDisposable? _subscription;
private float _currentVolume;
private bool _currentMute;
private readonly Dictionary<int, RotaryOverride> _rotaries;
public AudioDeviceControlFolderProvider(IAudioService audio, AudioEndpointInfo endpoint,
AudioEndpointKind kind, AudioAliasStore aliasStore, IPluginHost host)
{
_audio = audio;
_endpoint = endpoint;
_kind = kind;
_aliasStore = aliasStore;
_host = host;
_rotaries = new Dictionary<int, RotaryOverride>
{
[0] = new RotaryOverride
{
OnLeft = () => { AdjustVolume(-StepScalar); return Task.CompletedTask; },
OnRight = () => { AdjustVolume(+StepScalar); return Task.CompletedTask; },
OnPress = () => { ToggleMute(); return Task.CompletedTask; }
}
};
}
public override string Title => _aliasStore.Resolve(_endpoint);
public override IReadOnlyDictionary<int, RotaryOverride> RotaryOverrides => _rotaries;
public override void OnEnter()
{
// An endpoint that vanished since the parent folder was built must not take the host down.
Try("read", () =>
{
_currentVolume = _audio.GetVolume(_endpoint.Id);
_currentMute = _audio.GetMute(_endpoint.Id);
});
_subscription = _audio.SubscribeVolumeChanges(_endpoint.Id, (vol, muted) =>
{
_currentVolume = vol;
_currentMute = muted;
RaiseEntriesChanged();
});
_aliasStore.Changed += RaiseEntriesChanged;
}
public override void OnExit()
{
_aliasStore.Changed -= RaiseEntriesChanged;
_subscription?.Dispose();
_subscription = null;
}
public override IReadOnlyList<FolderEntry> BuildEntries()
{
var percent = (int)Math.Round(_currentVolume * 100f);
bool isDefault = IsCurrentDefault();
return new[]
{
new FolderEntry
{
SlotIndex = 0,
Text = $"{percent} %",
BackColor = PluginColor.FromRgb(0x20, 0x40, 0x60),
TextSize = 22,
Bold = true
},
new FolderEntry
{
SlotIndex = 1,
Text = _host.Tr(_currentMute ? "Unmute" : "Mute"),
BackColor = _currentMute
? PluginColor.FromRgb(0x70, 0x20, 0x20)
: PluginColor.FromRgb(0x30, 0x30, 0x30),
TextSize = 16,
OnPress = () => { ToggleMute(); return Task.CompletedTask; }
},
new FolderEntry
{
SlotIndex = 2,
Text = _host.Tr(isDefault ? "Is Default" : "Set Default"),
BackColor = isDefault
? PluginColor.FromRgb(0x20, 0x60, 0x30)
: PluginColor.FromRgb(0x30, 0x30, 0x30),
TextSize = 16,
OnPress = () =>
{
Try("set default", () => { if (!IsCurrentDefault()) _audio.SetDefaultEndpoint(_endpoint.Id); });
RaiseEntriesChanged();
return Task.CompletedTask;
}
}
};
}
/// <summary>
/// Live default check. The captured endpoint record's IsDefault is a snapshot taken
/// when the parent folder was built, so it goes stale the moment the default changes.
/// </summary>
private bool IsCurrentDefault() =>
_audio.GetEndpoints(_kind).Any(e =>
string.Equals(e.Id, _endpoint.Id, StringComparison.Ordinal) && e.IsDefault);
private void AdjustVolume(float delta)
{
var next = Math.Clamp(_currentVolume + delta, 0f, 1f);
Try("set volume", () => _audio.SetVolume(_endpoint.Id, next));
// Don't optimistically update — the volume notification callback refreshes the UI.
}
private void ToggleMute()
{
Try("toggle mute", () => _audio.SetMute(_endpoint.Id, !_currentMute));
// Notification callback will refresh.
}
private void Try(string action, Action body)
{
try { body(); }
catch (Exception ex) { _host.Logger?.Warn($"Audio device '{_endpoint.FriendlyName}': could not {action}: {ex.Message}"); }
}
}