Skip to content

Commit 1e06495

Browse files
committed
videoi fix
1 parent 7d03026 commit 1e06495

File tree

8 files changed

+71
-6
lines changed

8 files changed

+71
-6
lines changed

docs/Features/GoLiveRuntime.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The runtime now owns real browser media outputs for the composed program scene a
2121

2222
- `Go Live` auto-seeds the first available browser camera into the scene when the scene is empty and the browser exposes a real camera list
2323
- the center program stage always shows the currently selected scene camera, while the right preview rail shows the current program source and only marks it live once recording or streaming is active
24-
- `Go Live` builds one browser-side program stream from the scene camera cards by drawing the selected primary camera full-frame and then layering additional included cameras as positioned overlays on a canvas
24+
- `Go Live` builds one browser-side program stream from the scene camera cards by drawing the selected primary camera full-frame; additional included cameras are layered as positioned overlays only when the operator explicitly chooses a multi-source layout instead of the default `Full` layout
2525
- the scene `AudioBus` is mixed into one program audio track through `AudioContext`, delay, and gain nodes before the final program stream is published or recorded
2626
- OBS browser output stays browser-only and exposes the composed program audio inside an OBS Browser Source environment
2727
- `VDO.Ninja` publishing uses the vendored official browser SDK and publishes the same composed `MediaStream` that powers preview, recording, and OBS

src/PrompterOne.Shared/Contracts/UiTestIds.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ public static class GoLive
444444
public const string StreamIncludeCamera = "go-live-stream-include-camera";
445445
public const string StreamTextOverlay = "go-live-stream-text-overlay";
446446
public const string TakeToAir = "go-live-take-to-air";
447+
public const string LayoutFull = "go-live-layout-full";
448+
public const string LayoutSplit = "go-live-layout-split";
449+
public const string LayoutPictureInPicture = "go-live-layout-picture-in-picture";
447450
public const string TwitchKey = "go-live-twitch-key";
448451
public const string TwitchToggle = "go-live-twitch-toggle";
449452
public const string TwitchUrl = "go-live-twitch-url";

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<span class="gl-tools-label">Layout</span>
4040
<button type="button"
4141
class="gl-scene-btn @(ActiveLayout == GoLiveSceneLayout.Full ? "active" : null)"
42+
data-testid="@UiTestIds.GoLive.LayoutFull"
4243
@onclick="() => SelectLayout.InvokeAsync(GoLiveSceneLayout.Full)">
4344
<svg width="14"
4445
height="14"
@@ -52,6 +53,7 @@
5253
</button>
5354
<button type="button"
5455
class="gl-scene-btn @(ActiveLayout == GoLiveSceneLayout.Split ? "active" : null)"
56+
data-testid="@UiTestIds.GoLive.LayoutSplit"
5557
@onclick="() => SelectLayout.InvokeAsync(GoLiveSceneLayout.Split)">
5658
<svg width="14"
5759
height="14"
@@ -66,6 +68,7 @@
6668
</button>
6769
<button type="button"
6870
class="gl-scene-btn @(ActiveLayout == GoLiveSceneLayout.PictureInPicture ? "active" : null)"
71+
data-testid="@UiTestIds.GoLive.LayoutPictureInPicture"
6972
@onclick="() => SelectLayout.InvokeAsync(GoLiveSceneLayout.PictureInPicture)">
7073
<svg width="14"
7174
height="14"

src/PrompterOne.Shared/GoLive/Pages/GoLivePage.Runtime.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using PrompterOne.Core.Models.Media;
2+
using PrompterOne.Shared.Components.GoLive;
23
using PrompterOne.Shared.Services;
34

45
namespace PrompterOne.Shared.Pages;
@@ -9,6 +10,7 @@ private GoLiveOutputRuntimeRequest BuildRuntimeRequest(SceneCameraSource? camera
910
GoLiveOutputRequestFactory.Build(
1011
camera,
1112
MediaSceneService.State,
13+
_activeSceneLayout != GoLiveSceneLayout.Full,
1214
_studioSettings.Streaming,
1315
_recordingPreferences,
1416
_sessionTitle);

src/PrompterOne.Shared/GoLive/Pages/GoLivePage.StudioSurface.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,22 @@ private Task AddSceneAsync()
406406
return Task.CompletedTask;
407407
}
408408

409-
private Task SelectSceneLayoutAsync(GoLiveSceneLayout layout)
409+
private async Task SelectSceneLayoutAsync(GoLiveSceneLayout layout)
410410
{
411411
_activeSceneLayout = layout;
412-
return Task.CompletedTask;
412+
if (!GoLiveOutputRuntime.State.HasActiveOutputs)
413+
{
414+
return;
415+
}
416+
417+
await EnsurePageReadyAsync();
418+
var programCamera = ActiveCamera ?? SelectedCamera;
419+
if (programCamera is null)
420+
{
421+
return;
422+
}
423+
424+
await GoLiveOutputRuntime.UpdateProgramSourceAsync(BuildRuntimeRequest(programCamera));
413425
}
414426

415427
private Task SelectTransitionKindAsync(GoLiveTransitionKind kind)

src/PrompterOne.Shared/GoLive/Services/GoLiveOutputRequestFactory.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ public static class GoLiveOutputRequestFactory
1919
public static GoLiveOutputRuntimeRequest Build(
2020
SceneCameraSource? primaryCamera,
2121
MediaSceneState scene,
22+
bool allowOverlaySources,
2223
StreamStudioSettings streaming,
2324
SettingsPagePreferences recordingPreferences,
2425
string recordingFileStem)
2526
{
2627
var liveKitDestination = ResolveLiveKitDestination(streaming);
2728
var vdoNinjaDestination = ResolveVdoNinjaDestination(streaming);
2829
var programVideo = ResolveProgramVideo(streaming.OutputResolution);
29-
var videoSources = BuildVideoSources(primaryCamera, scene.Cameras);
30+
var videoSources = BuildVideoSources(primaryCamera, scene.Cameras, allowOverlaySources);
3031
var audioInputs = BuildAudioInputs(scene);
3132
var recording = BuildRecordingExport(recordingPreferences, recordingFileStem);
3233

@@ -171,7 +172,8 @@ private static GoLiveProgramVideoSettings ResolveProgramVideo(StreamingResolutio
171172

172173
private static IReadOnlyList<GoLiveOutputVideoSource> BuildVideoSources(
173174
SceneCameraSource? primaryCamera,
174-
IReadOnlyList<SceneCameraSource> cameras)
175+
IReadOnlyList<SceneCameraSource> cameras,
176+
bool allowOverlaySources)
175177
{
176178
return cameras.Select(camera => new GoLiveOutputVideoSource(
177179
SourceId: camera.SourceId,
@@ -186,13 +188,26 @@ private static IReadOnlyList<GoLiveOutputVideoSource> BuildVideoSources(
186188
camera.Transform.MirrorHorizontal,
187189
camera.Transform.MirrorVertical,
188190
camera.Transform.Visible,
189-
camera.Transform.IncludeInOutput,
191+
ShouldIncludeInOutput(camera, primaryCamera, allowOverlaySources),
190192
camera.Transform.ZIndex,
191193
camera.Transform.Opacity),
192194
IsPrimary: string.Equals(camera.SourceId, primaryCamera?.SourceId, StringComparison.Ordinal)))
193195
.ToList();
194196
}
195197

198+
private static bool ShouldIncludeInOutput(
199+
SceneCameraSource camera,
200+
SceneCameraSource? primaryCamera,
201+
bool allowOverlaySources)
202+
{
203+
if (string.Equals(camera.SourceId, primaryCamera?.SourceId, StringComparison.Ordinal))
204+
{
205+
return true;
206+
}
207+
208+
return allowOverlaySources && camera.Transform.IncludeInOutput;
209+
}
210+
196211
private static int ResolveAudioChannelCount(string channelLabel)
197212
{
198213
return string.Equals(channelLabel, RecordingPreferenceCatalog.AudioChannels.Mono, StringComparison.Ordinal)

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ record => string.Equals(record.Identifier, StartLocalRecordingInteropMethod, Str
283283

284284
Assert.Equal(AppTestData.Camera.FirstSourceId, request.PrimarySourceId);
285285
Assert.Equal(2, request.VideoSources.Count);
286+
Assert.Equal(1, request.VideoSources.Count(source => source.IsRenderable));
286287
Assert.Equal(2, request.AudioInputs.Count);
287288
Assert.Equal(StreamingResolutionLabel, request.ProgramVideo.ResolutionLabel);
288289
Assert.Equal(VideoFrameRateLabel, request.ProgramVideo.FrameRateLabel);
@@ -295,6 +296,34 @@ record => string.Equals(record.Identifier, StartLocalRecordingInteropMethod, Str
295296
Assert.True(request.Recording.PreferFilePicker);
296297
}
297298

299+
[Fact]
300+
public void GoLivePage_StartRecording_WithPictureInPictureLayout_KeepsOverlaySourcesRenderable()
301+
{
302+
SeedSceneState(CreateSceneWithTwoAudioInputs());
303+
SeedStudioSettings(StudioSettings.Default with
304+
{
305+
Streaming = StudioSettings.Default.Streaming with
306+
{
307+
LocalRecordingEnabled = true,
308+
OutputResolution = StreamingResolutionPreset.FullHd1080p30
309+
}
310+
});
311+
312+
Services.GetRequiredService<NavigationManager>().NavigateTo(AppTestData.Routes.GoLiveDemo);
313+
var cut = Render<GoLivePage>();
314+
315+
cut.WaitForAssertion(() => Assert.NotNull(cut.FindByTestId(UiTestIds.GoLive.Page)));
316+
cut.FindByTestId(UiTestIds.GoLive.LayoutPictureInPicture).Click();
317+
cut.FindByTestId(UiTestIds.GoLive.StartRecording).Click();
318+
319+
var invocation = Assert.Single(
320+
_harness.JsRuntime.InvocationRecords,
321+
record => string.Equals(record.Identifier, StartLocalRecordingInteropMethod, StringComparison.Ordinal));
322+
var request = Assert.IsType<GoLiveOutputRuntimeRequest>(invocation.Arguments[1]);
323+
324+
Assert.Equal(2, request.VideoSources.Count(source => source.IsRenderable));
325+
}
326+
298327
private static MediaSceneState CreateTwoCameraScene() =>
299328
new(
300329
[

tests/PrompterOne.App.UITests/GoLive/GoLiveShellSessionFlowTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ await page.WaitForFunctionAsync(
157157
var recordingState = runtimeState.GetProperty("recording");
158158

159159
Assert.Equal(BrowserTestConstants.GoLive.SecondSourceId, programState.GetProperty("primarySourceId").GetString());
160+
Assert.Equal(1, programState.GetProperty("videoSourceCount").GetInt32());
160161
Assert.True(programState.GetProperty("width").GetInt32() > 0);
161162
Assert.True(programState.GetProperty("height").GetInt32() > 0);
162163
Assert.False(string.IsNullOrWhiteSpace(recordingState.GetProperty("fileName").GetString()));

0 commit comments

Comments
 (0)