Skip to content

Commit efdcdcb

Browse files
committed
feat: do not include virtual webcams with no device id
1 parent c97cc41 commit efdcdcb

6 files changed

Lines changed: 53 additions & 12 deletions

File tree

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
3+
<TargetFramework>net10.0</TargetFramework>
44
<LangVersion>13.0</LangVersion>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>

ObsKit.NET.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObsKit.NET.Sample.ReplayBuf
1313
EndProject
1414
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObsKit.NET.Sample.Streaming", "samples\ObsKit.NET.Sample.Streaming\ObsKit.NET.Sample.Streaming.csproj", "{C3D4E5F6-A7B8-9012-CDEF-345678901234}"
1515
EndProject
16+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObsKit.NET.Sample.Webcam", "samples\ObsKit.NET.Sample.Webcam\ObsKit.NET.Sample.Webcam.csproj", "{D4E5F6A7-B8C9-0123-DEF0-456789012345}"
17+
EndProject
1618
Global
1719
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1820
Debug|Any CPU = Debug|Any CPU
@@ -71,6 +73,18 @@ Global
7173
{C3D4E5F6-A7B8-9012-CDEF-345678901234}.Release|x64.Build.0 = Release|Any CPU
7274
{C3D4E5F6-A7B8-9012-CDEF-345678901234}.Release|x86.ActiveCfg = Release|Any CPU
7375
{C3D4E5F6-A7B8-9012-CDEF-345678901234}.Release|x86.Build.0 = Release|Any CPU
76+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
77+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Debug|Any CPU.Build.0 = Debug|Any CPU
78+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Debug|x64.ActiveCfg = Debug|Any CPU
79+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Debug|x64.Build.0 = Debug|Any CPU
80+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Debug|x86.ActiveCfg = Debug|Any CPU
81+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Debug|x86.Build.0 = Debug|Any CPU
82+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Release|Any CPU.ActiveCfg = Release|Any CPU
83+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Release|Any CPU.Build.0 = Release|Any CPU
84+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Release|x64.ActiveCfg = Release|Any CPU
85+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Release|x64.Build.0 = Release|Any CPU
86+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Release|x86.ActiveCfg = Release|Any CPU
87+
{D4E5F6A7-B8C9-0123-DEF0-456789012345}.Release|x86.Build.0 = Release|Any CPU
7488
EndGlobalSection
7589
GlobalSection(SolutionProperties) = preSolution
7690
HideSolutionNode = FALSE
@@ -79,5 +93,6 @@ Global
7993
{82EA2837-6A53-4DEE-B6EE-A5F2396E7B59} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
8094
{C3D4E5F6-A7B8-9012-CDEF-345678901234} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
8195
{B2C3D4E5-F6A7-8901-BCDE-F23456789012} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
96+
{D4E5F6A7-B8C9-0123-DEF0-456789012345} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
8297
EndGlobalSection
8398
EndGlobal

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ObsKit.NET
22

3-
A modern .NET 9 wrapper for OBS Studio, providing a fluent C# API for video recording, streaming, and replay buffer functionality.
3+
A modern .NET 10 wrapper for OBS Studio, providing a fluent C# API for video recording, streaming, and replay buffer functionality.
44

55
## Features
66

@@ -15,7 +15,7 @@ A modern .NET 9 wrapper for OBS Studio, providing a fluent C# API for video reco
1515

1616
## Requirements
1717

18-
- .NET 9.0 or later
18+
- .NET 10.0 or later
1919
- OBS Studio runtime (see [OBS Runtime Setup](#obs-runtime-setup))
2020

2121
## Quick Start

samples/ObsKit.NET.Sample.Webcam/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
var devices = WebcamCapture.ListDevices();
3030
Console.WriteLine($"Available video capture devices ({devices.Count}):");
3131
for (int i = 0; i < devices.Count; i++)
32-
Console.WriteLine($" [{i}] {devices[i].Name}");
32+
Console.WriteLine($" [{i}] {devices[i].Name}\n id={devices[i].DeviceId}");
3333

3434
if (devices.Count == 0)
3535
{

samples/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ Before running the samples, you must set up the OBS runtime in the sample's outp
1919

2020
```powershell
2121
# For Recording sample
22-
Copy-Item -Recurse .\samples\obs-runtime\* .\samples\ObsKit.NET.Sample.Recording\bin\Debug\net9.0\
22+
Copy-Item -Recurse .\samples\obs-runtime\* .\samples\ObsKit.NET.Sample.Recording\bin\Debug\net10.0\
2323
2424
# For ReplayBuffer sample
25-
Copy-Item -Recurse .\samples\obs-runtime\* .\samples\ObsKit.NET.Sample.ReplayBuffer\bin\Debug\net9.0\
25+
Copy-Item -Recurse .\samples\obs-runtime\* .\samples\ObsKit.NET.Sample.ReplayBuffer\bin\Debug\net10.0\
2626
```
2727

2828
3. Build and run:
@@ -56,7 +56,7 @@ Each sample directory should look like this after setup:
5656

5757
```
5858
ObsKit.NET.Sample.Recording/
59-
├── bin/Debug/net9.0/
59+
├── bin/Debug/net10.0/
6060
│ ├── ObsKit.NET.Sample.Recording.exe
6161
│ ├── obs.dll
6262
│ ├── obs-ffmpeg-mux.exe

src/ObsKit.NET/Sources/WebcamCapture.cs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,13 @@ public WebcamCapture(string name, string? deviceId = null)
7373
/// Returns null if no capture devices are present on the system.
7474
/// </summary>
7575
/// <param name="name">Optional source name.</param>
76-
public static WebcamCapture? FromDefault(string? name = null)
76+
/// <param name="includeVirtualDevices">
77+
/// If true, also consider virtual / proxy entries that have no DirectShow device path
78+
/// (Meta Quest companion app, NVIDIA Broadcast, OBS Virtual Camera, etc.). Default false.
79+
/// </param>
80+
public static WebcamCapture? FromDefault(string? name = null, bool includeVirtualDevices = false)
7781
{
78-
var device = ListDevices().FirstOrDefault();
82+
var device = ListDevices(includeVirtualDevices).FirstOrDefault();
7983
if (device == null)
8084
return null;
8185
return new WebcamCapture(name ?? device.Name, device.DeviceId);
@@ -87,10 +91,14 @@ public WebcamCapture(string name, string? deviceId = null)
8791
/// </summary>
8892
/// <param name="nameSubstring">Substring to match against device names.</param>
8993
/// <param name="name">Optional source name; defaults to the matched device's name.</param>
94+
/// <param name="includeVirtualDevices">
95+
/// If true, virtual / proxy entries (Meta Quest, NVIDIA Broadcast, OBS Virtual Camera, ...)
96+
/// are also eligible for matching. Default false.
97+
/// </param>
9098
/// <returns>A configured WebcamCapture, or null if no device matches.</returns>
91-
public static WebcamCapture? FromDeviceName(string nameSubstring, string? name = null)
99+
public static WebcamCapture? FromDeviceName(string nameSubstring, string? name = null, bool includeVirtualDevices = false)
92100
{
93-
var device = ListDevices().FirstOrDefault(d =>
101+
var device = ListDevices(includeVirtualDevices).FirstOrDefault(d =>
94102
d.Name.Contains(nameSubstring, StringComparison.OrdinalIgnoreCase));
95103
if (device == null)
96104
return null;
@@ -102,7 +110,12 @@ public WebcamCapture(string name, string? deviceId = null)
102110
/// a temporary dshow_input source and asking OBS to populate its property list, which is
103111
/// the same code path the OBS UI uses for the device dropdown.
104112
/// </summary>
105-
public static IReadOnlyList<WebcamDeviceInfo> ListDevices()
113+
/// <param name="includeVirtualDevices">
114+
/// If true, also return virtual / proxy entries that have no DirectShow device path —
115+
/// e.g. Meta Quest companion app, NVIDIA Broadcast, OBS Virtual Camera. These are usually
116+
/// not openable in a headless app, so they are excluded by default.
117+
/// </param>
118+
public static IReadOnlyList<WebcamDeviceInfo> ListDevices(bool includeVirtualDevices = false)
106119
{
107120
// The dshow plugin only populates the device list when an instance exists, so we
108121
// create a private (un-saved) source just for the property query and dispose it.
@@ -114,6 +127,19 @@ public static IReadOnlyList<WebcamDeviceInfo> ListDevices()
114127
{
115128
if (string.IsNullOrEmpty(itemValue))
116129
continue;
130+
131+
// OBS encodes device ids as "Name:Path" (the dshow plugin escapes any literal
132+
// ':' and '#' inside the components, so the first ':' is always the separator).
133+
// Entries with an empty path component are virtual / proxy devices (Meta Quest
134+
// companion app, NVIDIA Broadcast, OBS Virtual Camera, ...). Skip them unless
135+
// the caller opts in.
136+
if (!includeVirtualDevices && OperatingSystem.IsWindows())
137+
{
138+
var sep = itemValue.IndexOf(':');
139+
if (sep < 0 || sep == itemValue.Length - 1)
140+
continue;
141+
}
142+
117143
result.Add(new WebcamDeviceInfo(itemName, itemValue));
118144
}
119145
return result;

0 commit comments

Comments
 (0)