Skip to content

Commit 7d03026

Browse files
committed
fixes and GoLive
1 parent 6ad8760 commit 7d03026

File tree

6 files changed

+115
-0
lines changed

6 files changed

+115
-0
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ Repo-specific design rules:
326326
- Go Live local recording MUST capture the same composed program feed that the active live/record session publishes; recording a black frame or a different source than the current program feed is a regression.
327327
- Go Live local recording artifacts MUST contain both decodable video and decodable audio from the real program feed, and their saved resolution/quality must match the active source or chosen output profile instead of silently degrading to a lower-quality fallback.
328328
- Go Live recording status and runtime panels MUST show the real recording details the browser runtime knows, including the resolved output profile and live session/file telemetry when available; blank recording metadata during an active local recording is a regression.
329+
- Go Live default `Full` program layout MUST record and publish only the current active program camera; extra camera overlays may appear only when the operator explicitly chooses a multi-source layout such as split or picture-in-picture.
329330
- Go Live audio meters MUST show real browser audio activity for microphone, program, and recording paths; static placeholder bars or seeded fake levels in the Audio tab are regressions.
330331
- Go Live MUST have one active browser-side broadcast spine per setup: choose LiveKit or VDO.Ninja for the upstream transport, but do not run both as the same session's primary publish path at once.
331332
- Go Live remote fan-out targets such as YouTube, Twitch, and custom RTMP MUST hang off the chosen upstream broadcast spine or its relay/egress layer; the browser runtime must not pretend to publish the same session independently to every platform without a real transport path.
@@ -341,6 +342,7 @@ Repo-specific design rules:
341342
- User preferences persistence MUST sit behind a platform-agnostic user-settings abstraction, with browser storage implemented via local storage and room for other platform-specific implementations; theme, teleprompter layout preferences, camera/scene preferences, and similar saved settings belong there instead of ad-hoc feature stores.
342343
- Streaming destination/platform configuration MUST be user-defined and persisted in settings; Settings and Go Live must not ship hardcoded platform instances, seeded destination accounts, or fixed fake provider rows beyond real runtime capabilities.
343344
- Runtime screens must not keep inline seeded operational data, fake demo rows, or screen-local platform/source presets in page/component code; reusable labels and presets belong in shared contracts or catalogs, while rendered rows must come from persisted settings, workspace state, or live session state.
345+
- Go Live source rails must not render anonymous, unlabeled, or device-less camera cards; stale persisted scene sources without a real `deviceId` must be pruned, and rendered source cards should expose their real source/device identifiers for diagnostics.
344346
- Build quality gates must stay green under `-warnaserror`.
345347
- GitHub Pages is the expected CI publish target for the standalone WebAssembly app; publish automation must keep the app browser-only and Pages-compatible.
346348
- GitHub Actions MUST keep separate, clearly named workflows for pull-request validation and release automation; vague workflow names are forbidden.

src/PrompterOne.Shared/AppShell/Services/AppBootstrapper.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ private static (MediaSceneState State, bool Changed) NormalizeMediaScene(MediaSc
119119
{
120120
var changed = false;
121121
var normalizedCameras = state.Cameras
122+
.Where(camera =>
123+
{
124+
var isValid = IsValidCameraSource(camera);
125+
if (!isValid)
126+
{
127+
changed = true;
128+
}
129+
130+
return isValid;
131+
})
122132
.Select(camera =>
123133
{
124134
var normalizedLabel = MediaDeviceLabelSanitizer.Sanitize(camera.Label);
@@ -177,4 +187,10 @@ state with
177187

178188
return normalized;
179189
}
190+
191+
private static bool IsValidCameraSource(SceneCameraSource camera)
192+
{
193+
return !string.IsNullOrWhiteSpace(camera.SourceId)
194+
&& !string.IsNullOrWhiteSpace(camera.DeviceId);
195+
}
180196
}

src/PrompterOne.Shared/Contracts/UiTestIds.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ public static class GoLive
437437
public const string SourcesCard = "go-live-sources-card";
438438
public const string StartRecording = "go-live-start-recording";
439439
public const string StartStream = "go-live-start-stream";
440+
public const string SourceDeviceIdAttribute = "data-device-id";
441+
public const string SourceIdAttribute = "data-source-id";
440442
public const string SourcePickerEmpty = "go-live-source-picker-empty";
441443
public const string SwitchSelectedSource = "go-live-switch-selected-source";
442444
public const string StreamIncludeCamera = "go-live-stream-include-camera";

src/PrompterOne.Shared/GoLive/Components/GoLiveSourcesCard.razor

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
@foreach (var source in Sources)
3434
{
3535
<article class="@GetSourceCardCssClass(source)"
36+
@attributes="GetSourceDiagnostics(source)"
3637
data-testid="@UiTestIds.GoLive.SourceCamera(source.SourceId)">
3738
<button type="button"
3839
class="gl-cam-main"
@@ -195,4 +196,13 @@
195196
_ => "gl-cam-card"
196197
};
197198
}
199+
200+
private static IReadOnlyDictionary<string, object> GetSourceDiagnostics(SceneCameraSource source)
201+
{
202+
return new Dictionary<string, object>(StringComparer.Ordinal)
203+
{
204+
[UiTestIds.GoLive.SourceIdAttribute] = source.SourceId,
205+
[UiTestIds.GoLive.SourceDeviceIdAttribute] = source.DeviceId
206+
};
207+
}
198208
}

tests/PrompterOne.App.Tests/AppShell/AppBootstrapperMediaSceneTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,50 @@ public async Task AppBootstrapper_NormalizesPersistedMediaSceneLabels_FromBrowse
5555
Assert.Equal(SanitizedMicrophoneLabel, persistedState.AudioBus.Inputs.Single().Label);
5656
}
5757

58+
[Fact]
59+
public async Task AppBootstrapper_PrunesPersistedCameraSources_WithoutDeviceIds()
60+
{
61+
var harness = TestHarnessFactory.Create(this);
62+
var bootstrapper = Services.GetRequiredService<AppBootstrapper>();
63+
var savedScene = new MediaSceneState(
64+
Cameras:
65+
[
66+
new SceneCameraSource(
67+
SourceId: "invalid-source",
68+
DeviceId: string.Empty,
69+
Label: string.Empty,
70+
Transform: new MediaSourceTransform()),
71+
new SceneCameraSource(
72+
SourceId: "cam-source",
73+
DeviceId: "cam-1",
74+
Label: SanitizedCameraLabel,
75+
Transform: new MediaSourceTransform())
76+
],
77+
PrimaryMicrophoneId: "mic-1",
78+
PrimaryMicrophoneLabel: SanitizedMicrophoneLabel,
79+
AudioBus: new AudioBusState(
80+
[
81+
new AudioInputState(
82+
DeviceId: "mic-1",
83+
Label: SanitizedMicrophoneLabel)
84+
]));
85+
86+
harness.JsRuntime.SavedJsonValues[BuildSettingsStorageKey(BrowserAppSettingsKeys.SceneSettings)] =
87+
JsonSerializer.Serialize(savedScene);
88+
89+
await bootstrapper.EnsureReadyAsync();
90+
91+
var restoredState = Services.GetRequiredService<IMediaSceneService>().State;
92+
var restoredCamera = Assert.Single(restoredState.Cameras);
93+
Assert.Equal("cam-source", restoredCamera.SourceId);
94+
Assert.Equal("cam-1", restoredCamera.DeviceId);
95+
96+
var persistedState = harness.JsRuntime.GetSavedValue<MediaSceneState>(BrowserAppSettingsKeys.SceneSettings);
97+
var persistedCamera = Assert.Single(persistedState.Cameras);
98+
Assert.Equal("cam-source", persistedCamera.SourceId);
99+
Assert.Equal("cam-1", persistedCamera.DeviceId);
100+
}
101+
58102
private static string BuildSettingsStorageKey(string key) =>
59103
string.Concat(BrowserStorageKeys.SettingsPrefix, key);
60104
}

tests/PrompterOne.App.Tests/GoLive/GoLivePageTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,47 @@ public void GoLivePage_SelectsSecondCameraForCanvas()
333333
cut.FindByTestId(UiTestIds.GoLive.SelectedSourceLabel).TextContent.Trim()));
334334
}
335335

336+
[Fact]
337+
public void GoLivePage_PrunesAnonymousPersistedSources_AndExposesCameraDiagnostics()
338+
{
339+
var sceneState = new MediaSceneState(
340+
[
341+
new SceneCameraSource(
342+
"invalid-source",
343+
string.Empty,
344+
string.Empty,
345+
new MediaSourceTransform(IncludeInOutput: true)),
346+
new SceneCameraSource(
347+
AppTestData.Camera.FirstSourceId,
348+
AppTestData.Camera.FirstDeviceId,
349+
AppTestData.Camera.FrontCamera,
350+
new MediaSourceTransform(IncludeInOutput: true)),
351+
new SceneCameraSource(
352+
AppTestData.Camera.SecondSourceId,
353+
AppTestData.Camera.SecondDeviceId,
354+
AppTestData.Camera.SideCamera,
355+
new MediaSourceTransform(IncludeInOutput: false))
356+
],
357+
"mic-1",
358+
AppTestData.Scripts.BroadcastMic,
359+
new AudioBusState([new AudioInputState("mic-1", AppTestData.Scripts.BroadcastMic)]));
360+
361+
SeedSceneState(sceneState);
362+
363+
Services.GetRequiredService<NavigationManager>()
364+
.NavigateTo(AppTestData.Routes.GoLiveDemo);
365+
366+
var cut = Render<GoLivePage>();
367+
368+
cut.WaitForAssertion(() =>
369+
{
370+
var sourceCards = cut.FindAll($"article[data-testid^='{UiTestIds.GoLive.SourceCamera(string.Empty)}']");
371+
Assert.Equal(2, sourceCards.Count);
372+
Assert.DoesNotContain(sourceCards, card => string.IsNullOrWhiteSpace(card.GetAttribute(UiTestIds.GoLive.SourceIdAttribute)));
373+
Assert.DoesNotContain(sourceCards, card => string.IsNullOrWhiteSpace(card.GetAttribute(UiTestIds.GoLive.SourceDeviceIdAttribute)));
374+
});
375+
}
376+
336377
[Fact]
337378
public void GoLivePage_RendersStudioParitySurfaceAndRemoteRoomFlow()
338379
{

0 commit comments

Comments
 (0)