11using ObsKit . NET . Core ;
2+ using ObsKit . NET . Encoders ;
23using ObsKit . NET . Exceptions ;
34using ObsKit . NET . Native ;
45using ObsKit . NET . Native . Interop ;
56using ObsKit . NET . Native . Types ;
7+ using ObsKit . NET . Outputs ;
68using ObsKit . NET . Scenes ;
79using ObsKit . NET . Sources ;
810
@@ -17,11 +19,31 @@ public static class Obs
1719 private static ObsContext ? _context ;
1820 private static readonly object _lock = new ( ) ;
1921
22+ // Tracking for auto-management
23+ private static readonly Dictionary < uint , Source > _channelSources = new ( ) ;
24+ private static readonly List < Output > _managedOutputs = new ( ) ;
25+
2026 /// <summary>
2127 /// Gets whether OBS is currently initialized.
2228 /// </summary>
2329 public static bool IsInitialized => ObsCore . obs_initialized ( ) ;
2430
31+ /// <summary>
32+ /// Gets or sets whether to automatically dispose sources and outputs on Shutdown.
33+ /// Default is true.
34+ /// </summary>
35+ public static bool AutoDispose { get ; set ; } = true ;
36+
37+ /// <summary>
38+ /// Gets all sources currently assigned to output channels.
39+ /// </summary>
40+ public static IReadOnlyDictionary < uint , Source > ChannelSources => _channelSources ;
41+
42+ /// <summary>
43+ /// Gets all outputs being managed.
44+ /// </summary>
45+ public static IReadOnlyList < Output > ManagedOutputs => _managedOutputs ;
46+
2547 /// <summary>
2648 /// Gets the OBS version string.
2749 /// </summary>
@@ -118,6 +140,7 @@ public static ObsContext Initialize(Action<ObsConfiguration>? configure)
118140
119141 /// <summary>
120142 /// Shuts down OBS and releases all resources.
143+ /// Any remaining outputs will be stopped and sources will be disposed.
121144 /// </summary>
122145 public static void Shutdown ( )
123146 {
@@ -126,6 +149,30 @@ public static void Shutdown()
126149 if ( _context == null )
127150 return ;
128151
152+ // Stop any remaining managed outputs
153+ foreach ( var output in _managedOutputs . ToList ( ) )
154+ {
155+ try
156+ {
157+ if ( output . IsActive )
158+ output . Stop ( ) ;
159+ }
160+ catch { /* Ignore errors during cleanup */ }
161+ }
162+ _managedOutputs . Clear ( ) ;
163+
164+ // Dispose all channel sources
165+ foreach ( var ( channel , source ) in _channelSources . ToList ( ) )
166+ {
167+ try
168+ {
169+ ObsCore . obs_set_output_source ( channel , ObsSourceHandle . Null ) ;
170+ source . Dispose ( ) ;
171+ }
172+ catch { /* Ignore errors during cleanup */ }
173+ }
174+ _channelSources . Clear ( ) ;
175+
129176 _context . Dispose ( ) ;
130177 _context = null ;
131178 }
@@ -154,30 +201,96 @@ public static void SetAudio(Action<AudioSettings> configure)
154201 }
155202
156203 /// <summary>
157- /// Sets a source for an output channel. OBS uses channels 0-5 for different purposes:
204+ /// Sets a source for an output channel. OBS uses channels 0-63 for different purposes:
158205 /// Channel 0: Primary video source (scene/game capture)
159206 /// Channel 1: Secondary video (display capture fallback)
160- /// Channels 2-5 : Audio sources (microphone, desktop audio, etc.)
207+ /// Channels 2+ : Audio sources (microphone, desktop audio, etc.)
161208 /// </summary>
162- /// <param name="channel">The output channel (0-5 ).</param>
209+ /// <param name="channel">The output channel (0-63 ).</param>
163210 /// <param name="source">The source to assign, or null to clear the channel.</param>
164211 public static void SetOutputSource ( uint channel , Source ? source )
165212 {
166213 ThrowIfNotInitialized ( ) ;
214+
215+ lock ( _lock )
216+ {
217+ // Remove existing source from tracking (but don't dispose - user may still want it)
218+ _channelSources . Remove ( channel ) ;
219+
220+ if ( source != null )
221+ {
222+ _channelSources [ channel ] = source ;
223+ source . AssignedChannel = channel ;
224+ }
225+ }
226+
167227 var handle = source != null ? ( ObsSourceHandle ) ( nint ) source . NativeHandle : ObsSourceHandle . Null ;
168228 ObsCore . obs_set_output_source ( channel , handle ) ;
169229 }
170230
171231 /// <summary>
172232 /// Clears a source from an output channel.
173233 /// </summary>
174- /// <param name="channel">The output channel to clear (0-5 ).</param>
234+ /// <param name="channel">The output channel to clear (0-63 ).</param>
175235 public static void ClearOutputSource ( uint channel )
176236 {
177237 ThrowIfNotInitialized ( ) ;
238+
239+ lock ( _lock )
240+ {
241+ if ( _channelSources . TryGetValue ( channel , out var source ) )
242+ {
243+ source . AssignedChannel = null ;
244+ _channelSources . Remove ( channel ) ;
245+ }
246+ }
247+
178248 ObsCore . obs_set_output_source ( channel , ObsSourceHandle . Null ) ;
179249 }
180250
251+ /// <summary>
252+ /// Adds an output to be managed. Managed outputs are tracked and can be auto-disposed on Shutdown.
253+ /// </summary>
254+ /// <typeparam name="T">The output type.</typeparam>
255+ /// <param name="output">The output to manage.</param>
256+ /// <returns>The same output for chaining.</returns>
257+ public static T AddOutput < T > ( T output ) where T : Output
258+ {
259+ lock ( _lock )
260+ {
261+ if ( ! _managedOutputs . Contains ( output ) )
262+ {
263+ _managedOutputs . Add ( output ) ;
264+ }
265+ }
266+ return output ;
267+ }
268+
269+ /// <summary>
270+ /// Called when an output is stopped to remove it from tracking.
271+ /// </summary>
272+ internal static void OnOutputStopped ( Output output )
273+ {
274+ lock ( _lock )
275+ {
276+ _managedOutputs . Remove ( output ) ;
277+ }
278+ }
279+
280+ /// <summary>
281+ /// Called when a source is disposed to remove it from channel tracking.
282+ /// </summary>
283+ internal static void OnSourceDisposed ( Source source )
284+ {
285+ lock ( _lock )
286+ {
287+ if ( source . AssignedChannel . HasValue )
288+ {
289+ _channelSources . Remove ( source . AssignedChannel . Value ) ;
290+ }
291+ }
292+ }
293+
181294 /// <summary>
182295 /// Called by ObsContext when it's disposed directly (not through Obs.Shutdown).
183296 /// </summary>
0 commit comments