16
16
using System . Collections . Generic ;
17
17
using System . Diagnostics ;
18
18
using System . Linq ;
19
+ using System . Reactive . Linq ;
20
+ using System . Reactive . Subjects ;
19
21
using System . Runtime . InteropServices ;
20
22
using System . Threading ;
21
23
using System . Threading . Tasks ;
22
24
using NAudio . CoreAudioApi ;
23
25
using Serilog ;
26
+ using SoundSwitch . Audio . Manager ;
27
+ using SoundSwitch . Audio . Manager . Interop . Enum ;
24
28
using SoundSwitch . Common . Framework . Audio . Collection ;
25
29
using SoundSwitch . Common . Framework . Audio . Device ;
26
- using SoundSwitch . Common . Framework . Dispose ;
27
- using SoundSwitch . Framework . Audio . Lister . Job ;
28
- using SoundSwitch . Framework . NotificationManager ;
29
- using SoundSwitch . Framework . Threading ;
30
30
using SoundSwitch . Model ;
31
31
32
32
namespace SoundSwitch . Framework . Audio . Lister
33
33
{
34
34
public class CachedAudioDeviceLister : IAudioDeviceLister
35
35
{
36
36
/// <inheritdoc />
37
- private DeviceFullInfo [ ] PlaybackDevices { get ; set ; } = Array . Empty < DeviceFullInfo > ( ) ;
37
+ private Dictionary < string , DeviceFullInfo > PlaybackDevices { get ; set ; } = new ( ) ;
38
38
39
39
/// <inheritdoc />
40
- private DeviceFullInfo [ ] RecordingDevices { get ; set ; } = Array . Empty < DeviceFullInfo > ( ) ;
40
+ private Dictionary < string , DeviceFullInfo > RecordingDevices { get ; set ; } = new ( ) ;
41
+
42
+ private readonly ISubject < DefaultDevicePayload > _defaultDeviceChanged = new Subject < DefaultDevicePayload > ( ) ;
43
+ public IObservable < DefaultDevicePayload > DefaultDeviceChanged => _defaultDeviceChanged . AsObservable ( ) ;
41
44
42
45
/// <summary>
43
46
/// Get devices per type and state
@@ -50,8 +53,8 @@ public DeviceReadOnlyCollection<DeviceFullInfo> GetDevices(DataFlow type, Device
50
53
{
51
54
return type switch
52
55
{
53
- DataFlow . Render => new DeviceReadOnlyCollection < DeviceFullInfo > ( PlaybackDevices . Where ( info => state . HasFlag ( info . State ) ) , type ) ,
54
- DataFlow . Capture => new DeviceReadOnlyCollection < DeviceFullInfo > ( RecordingDevices . Where ( info => state . HasFlag ( info . State ) ) , type ) ,
56
+ DataFlow . Render => new DeviceReadOnlyCollection < DeviceFullInfo > ( PlaybackDevices . Values . Where ( info => state . HasFlag ( info . State ) ) , type ) ,
57
+ DataFlow . Capture => new DeviceReadOnlyCollection < DeviceFullInfo > ( RecordingDevices . Values . Where ( info => state . HasFlag ( info . State ) ) , type ) ,
55
58
_ => throw new ArgumentOutOfRangeException ( nameof ( type ) , type , null )
56
59
} ;
57
60
}
@@ -80,14 +83,109 @@ private set
80
83
public CachedAudioDeviceLister ( DeviceState state )
81
84
{
82
85
_state = state ;
83
- MMNotificationClient . Instance . DevicesChanged += DeviceChanged ;
84
86
_context = Log . ForContext ( "State" , _state ) ;
85
87
}
86
88
87
- private void DeviceChanged ( object sender , DeviceChangedEventBase e )
89
+ private void DisposeDevice ( DeviceFullInfo deviceFullInfo )
88
90
{
89
- _context . Verbose ( "Device Changed received, triggering job" ) ;
90
- JobScheduler . Instance . ScheduleJob ( new DebounceRefreshJob ( _state , this , _context ) , e . Token ) ;
91
+ _ = AudioSwitcher . Instance . InteractWithDevice ( deviceFullInfo , device =>
92
+ {
93
+ device . Dispose ( ) ;
94
+ return device ;
95
+ } ) ;
96
+ }
97
+
98
+ /// <summary>
99
+ /// Process device updates
100
+ /// </summary>
101
+ /// <param name="deviceChangedEvents"></param>
102
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
103
+ public void ProcessDeviceUpdates ( IEnumerable < DeviceChangedEvent > deviceChangedEvents )
104
+ {
105
+ bool GetDevice ( DeviceChangedEvent deviceChangedEvent , out DeviceFullInfo device )
106
+ {
107
+ device = AudioSwitcher . Instance . GetAudioEndpoint ( deviceChangedEvent . DeviceId ) ;
108
+ if ( device == null )
109
+ {
110
+ _context . Warning ( "Can't get device {deviceId}" , deviceChangedEvent . DeviceId ) ;
111
+ return true ;
112
+ }
113
+
114
+ return false ;
115
+ }
116
+
117
+ void UpdateDeviceCache ( DeviceChangedEvent deviceChangedEvent )
118
+ {
119
+ if ( GetDevice ( deviceChangedEvent , out var device ) ) return ;
120
+
121
+ switch ( device . Type )
122
+ {
123
+ case DataFlow . Render :
124
+ if ( PlaybackDevices . TryGetValue ( device . Id , out var oldPlaybackDevice ) )
125
+ {
126
+ DisposeDevice ( oldPlaybackDevice ) ;
127
+ }
128
+
129
+ PlaybackDevices [ device . Id ] = device ;
130
+ break ;
131
+ case DataFlow . Capture :
132
+ if ( RecordingDevices . TryGetValue ( device . Id , out var oldRecordingDevice ) )
133
+ {
134
+ DisposeDevice ( oldRecordingDevice ) ;
135
+ }
136
+
137
+ RecordingDevices [ device . Id ] = device ;
138
+ break ;
139
+ case DataFlow . All :
140
+ break ;
141
+ default :
142
+ throw new ArgumentOutOfRangeException ( ) ;
143
+ }
144
+
145
+ _context . Information ( "Updated device {deviceId} in cache" , device . Id ) ;
146
+ }
147
+
148
+ foreach ( var deviceChangedEvent in deviceChangedEvents )
149
+ {
150
+ try
151
+ {
152
+ switch ( deviceChangedEvent . Action )
153
+ {
154
+ case EventType . Removed :
155
+ if ( PlaybackDevices . Remove ( deviceChangedEvent . DeviceId , out var playbackDevice ) )
156
+ {
157
+ DisposeDevice ( playbackDevice ) ;
158
+ }
159
+
160
+ if ( RecordingDevices . Remove ( deviceChangedEvent . DeviceId , out var recordingDevice ) )
161
+ {
162
+ DisposeDevice ( recordingDevice ) ;
163
+ }
164
+
165
+ break ;
166
+ case EventType . Added :
167
+ case EventType . StateChanged :
168
+ case EventType . PropertyChanged :
169
+ UpdateDeviceCache ( deviceChangedEvent ) ;
170
+ break ;
171
+ case EventType . DefaultChanged :
172
+ if ( ! PlaybackDevices . TryGetValue ( deviceChangedEvent . DeviceId , out var device ) && ! RecordingDevices . TryGetValue ( deviceChangedEvent . DeviceId , out device ) )
173
+ {
174
+ _context . Warning ( "Can't get device {deviceId}" , deviceChangedEvent . DeviceId ) ;
175
+ break ;
176
+ }
177
+
178
+ _defaultDeviceChanged . OnNext ( new DefaultDevicePayload ( device , ( ( DefaultDeviceChangedEvent ) deviceChangedEvent ) . Role ) ) ;
179
+ break ;
180
+ default :
181
+ throw new ArgumentOutOfRangeException ( ) ;
182
+ }
183
+ }
184
+ catch ( Exception e )
185
+ {
186
+ _context . Warning ( e , "Couldn't process event: {event} for device {deviceId}" , deviceChangedEvent . Action , deviceChangedEvent . DeviceId ) ;
187
+ }
188
+ }
91
189
}
92
190
93
191
public void Refresh ( CancellationToken cancellationToken = default )
@@ -116,46 +214,31 @@ public void Refresh(CancellationToken cancellationToken = default)
116
214
try
117
215
{
118
216
logContext . Information ( "Refreshing all devices" ) ;
119
- var enumerator = new MMDeviceEnumerator ( ) ;
120
- using var _ = enumerator . DisposeOnCancellation ( cancellationToken ) ;
121
- foreach ( var endPoint in enumerator . EnumerateAudioEndPoints ( DataFlow . All , _state ) )
217
+ foreach ( var deviceInfo in AudioSwitcher . Instance . GetAudioEndpoints ( ( EDataFlow ) DataFlow . All , ( EDeviceState ) _state ) )
122
218
{
123
219
cancellationToken . ThrowIfCancellationRequested ( ) ;
124
- try
220
+ switch ( deviceInfo . Type )
125
221
{
126
- var deviceInfo = new DeviceFullInfo ( endPoint ) ;
127
- if ( string . IsNullOrEmpty ( deviceInfo . Name ) )
128
- {
129
- continue ;
130
- }
131
-
132
- switch ( deviceInfo . Type )
133
- {
134
- case DataFlow . Render :
135
- playbackDevices . Add ( deviceInfo . Id , deviceInfo ) ;
136
- break ;
137
- case DataFlow . Capture :
138
- recordingDevices . Add ( deviceInfo . Id , deviceInfo ) ;
139
- break ;
140
- case DataFlow . All :
141
- break ;
142
- default :
143
- throw new ArgumentOutOfRangeException ( ) ;
144
- }
145
- }
146
- catch ( Exception e )
147
- {
148
- logContext . Warning ( e , "Can't get name of device {device}" , endPoint . ID ) ;
222
+ case DataFlow . Render :
223
+ playbackDevices . Add ( deviceInfo . Id , deviceInfo ) ;
224
+ break ;
225
+ case DataFlow . Capture :
226
+ recordingDevices . Add ( deviceInfo . Id , deviceInfo ) ;
227
+ break ;
228
+ case DataFlow . All :
229
+ break ;
230
+ default :
231
+ throw new ArgumentOutOfRangeException ( ) ;
149
232
}
150
233
}
151
234
152
235
foreach ( var device in PlaybackDevices . Union ( RecordingDevices ) )
153
236
{
154
- device . Dispose ( ) ;
237
+ DisposeDevice ( device . Value ) ;
155
238
}
156
239
157
- PlaybackDevices = playbackDevices . Values . ToArray ( ) ;
158
- RecordingDevices = recordingDevices . Values . ToArray ( ) ;
240
+ PlaybackDevices = playbackDevices ;
241
+ RecordingDevices = recordingDevices ;
159
242
160
243
161
244
logContext . Information ( "Refreshed all devices in {@StopTime}. {@Recording}/rec, {@Playback}/play" , stopWatch . Elapsed , recordingDevices . Count , playbackDevices . Count ) ;
@@ -179,11 +262,9 @@ public void Refresh(CancellationToken cancellationToken = default)
179
262
180
263
public void Dispose ( )
181
264
{
182
- MMNotificationClient . Instance . DevicesChanged -= DeviceChanged ;
183
-
184
265
foreach ( var device in PlaybackDevices . Union ( RecordingDevices ) )
185
266
{
186
- device . Dispose ( ) ;
267
+ DisposeDevice ( device . Value ) ;
187
268
}
188
269
189
270
_refreshCancellationTokenSource . Dispose ( ) ;
0 commit comments