feat(webcam): resource lifecycle startup slice — poll-on-start, single-cam lock, persistent Layers alert
The creator couldn't add a webcam to Live (grayed app-wide) and nothing in the app explained why. Ground truth from the live DB: one Webcam identity AND one WebcamSceneConfig in the Chat scene — so the gray was the single-identity rule working, but the reason was unobservable. This slice makes the webcam a resource the app validates and locks, mirroring how OBS reserves its devices. At startup we enumerate the OS once (ValidateWebcamResourceStartupAsync, fired after LoadLayout, stored as WebcamStartupValidationTask for tests to await): - 0 webcams -> app runs on, layer inactive, no alarm - exactly 1 -> attempt CameraManager.AcquireAsync as an app-wide lock; on failure show a persistent red alert at the bottom of the Layers panel (WebcamLockAlert + Retry) that re-polls every 5s and clears itself the moment the camera locks, or on any first real frame - >=2 -> deliberately no auto-lock; camera selection belongs to the App Settings dialog (gear) — next slice CameraManager.IsRunning(deviceId) tells the pass a session already exists (started OR still starting) so loaded identity configs count as the lock and the pass never double-acquires. Test seams mirror LayoutPathOverride: CameraEnumeratorOverride / CameraFrameSourceFactoryOverride so the startup probe never touches real hardware under test. Good Dog test: WebcamStartupResourceTests x3 — single-cam locked + app runs on, zero-cams no-alarm/no-lock, lock-fails -> red alert -> Retry -> clears. Full suite 304/304, build 0 warnings, scope-check passed. Docs in-commit: TASKS Open items + ai.md Webcam section + HANDOFF rewrite. Also corrects the record: 'NVIDIA Broadcast opens the webcam exclusively' was a suspect-list claim (CameraConflictProbe reads process names only, no handles) — not restated as fact.
This commit is contained in:
@@ -171,6 +171,18 @@ public sealed class CameraManager : IDisposable
|
||||
toStop.PreviewBitmap = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True while a session exists for the device — started, or still starting.
|
||||
/// Used by the app's startup resource check to avoid double-acquiring a lock
|
||||
/// that the saved identity's scene configs already hold. A rolled-back (failed)
|
||||
/// session is removed, so this goes false and the startup pass can retry.
|
||||
/// </summary>
|
||||
public bool IsRunning(string deviceId)
|
||||
{
|
||||
lock (_gate)
|
||||
return _sessions.ContainsKey(deviceId);
|
||||
}
|
||||
|
||||
public VideoFrame? GetLatestFrame(string deviceId)
|
||||
{
|
||||
lock (_gate)
|
||||
|
||||
Reference in New Issue
Block a user