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:
+19
-3
@@ -17,6 +17,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
@@ -167,15 +168,30 @@
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Visibility="{Binding ShowSourcesEmptyHint, Converter={StaticResource BoolToVis}}"/>
|
||||
|
||||
<!-- Resource alert: pinned to the bottom of the Layers panel.
|
||||
Red when a required resource (the one-and-only webcam)
|
||||
could not be locked; persists and re-polls until resolved. -->
|
||||
<Border Grid.Row="3" Margin="0,8,0,0" Padding="8,6" CornerRadius="4"
|
||||
Background="#3a1216" BorderBrush="#e94560" BorderThickness="1"
|
||||
Visibility="{Binding WebcamLockAlert, Converter={StaticResource NotNullToVis}}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding WebcamLockAlert}" Foreground="#ffb4b4" FontSize="11"
|
||||
TextWrapping="Wrap"/>
|
||||
<Button Content="Retry" Command="{Binding RetryWebcamLockCommand}"
|
||||
Style="{StaticResource YtButtonSecondary}" Padding="10,2"
|
||||
HorizontalAlignment="Right" Margin="0,6,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ELEMENTS panel: per-element config beneath Sources.
|
||||
Collapses entirely when no element is selected. -->
|
||||
<Rectangle Grid.Row="3" Height="1" Fill="#2c3a5c" Margin="0,10,0,2"
|
||||
<Rectangle Grid.Row="4" Height="1" Fill="#2c3a5c" Margin="0,10,0,2"
|
||||
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}"/>
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="0,8,0,0"
|
||||
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="0,8,0,0"
|
||||
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
|
||||
<TextBlock Text="PROPERTIES" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<ScrollViewer x:Name="ElementsScrollViewer" Grid.Row="5" VerticalScrollBarVisibility="Auto" Background="Transparent"
|
||||
<ScrollViewer x:Name="ElementsScrollViewer" Grid.Row="6" VerticalScrollBarVisibility="Auto" Background="Transparent"
|
||||
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
|
||||
<StackPanel x:Name="ElementsPanel" Margin="0,4,0,0">
|
||||
<!-- Opacity + mirror/shape (draggable elements: image, webcam) -->
|
||||
|
||||
Reference in New Issue
Block a user