Go Live is the browser-only operational studio for PrompterOne.
It owns:
- source switching for local scene cameras and remote guest sources
- one browser-owned program feed
- local recording from that same program feed
- live transport startup and stop for
VDO.NinjaandLiveKit - right-rail runtime telemetry and downstream target status
It does not own:
- provider credential editing
- source inventory or per-device sync offsets
- any PrompterOne-managed relay, ingest, encoder, or media server
Settings is the source of truth for:
ProgramCaptureProfileRecordingProfileTransportConnectionProfileDistributionTargetProfile
The runtime now uses explicit sources + program + sinks layers.
Source modules- local browser scene sources
VDO.Ninjaremote intakeLiveKitremote intake
Program capture- one canonical composed
MediaStream - owns canvas composition, overlay composition, and audio mix
- one canonical composed
Sink modules- local recording
VDO.NinjapublishLiveKitpublish- downstream transport-aware targets
The browser compositor is the single source of truth. All sinks reuse that same program feed.
- There is no legacy local-output path in the runtime architecture.
- There is no backward compatibility for the old local-output settings shape or the old local-output UI concepts.
VDO.NinjaandLiveKitmay both publish concurrently when both transport connections are armed.- Local recording is a first-class sink and is not modeled as a fake external destination.
- Downstream targets such as
YouTube,Twitch, andCustom RTMPare bound to transport connections and are only activatable when the chosen transport exposes that path honestly. - Unsupported downstream paths must be shown as blocked, not silently degraded.
- When the browser cannot capture multiple local cameras concurrently,
Go Livemust fall back to one live local camera preview at a time while preserving fast source switching and keeping remote guest feeds live.
The routed Go Live page keeps the design shell:
- top session bar
- left source rail
- center program monitor
- scene controls bar
- right operational rail
The right rail now renders destination rows from two persisted collections:
TransportConnectionsDistributionTargets
Local recording stays controlled by the REC action and runtime metadata instead of showing up as a fake destination row.
On browsers with single-local-camera capture limits, the operator surface must stay honest:
- only one local camera may render live across the local preview/program surfaces at a time
- selecting another local camera moves the live local preview to that source
- the live-status rail must explain the limitation instead of pretending all armed local cameras are simultaneously live
flowchart LR
Settings["Settings<br/>capture, recording, transport, targets"]
Sources["Source modules<br/>local scene + remote guests"]
Program["Program capture<br/>one browser MediaStream"]
Recording["Recording sink"]
LiveKit["LiveKit output module"]
Vdo["VDO.Ninja output module"]
Targets["Distribution targets"]
GoLive["Go Live rails"]
Settings --> Sources
Settings --> Program
Settings --> Recording
Settings --> LiveKit
Settings --> Vdo
Settings --> Targets
Sources --> Program
Program --> Recording
Program --> LiveKit
Program --> Vdo
LiveKit --> Targets
Vdo --> Targets
GoLive --> Sources
GoLive --> Program
GoLive --> Recording
GoLive --> LiveKit
GoLive --> Vdo
GoLive --> Targets
sequenceDiagram
participant User
participant Settings as "SettingsPage"
participant GoLive as "GoLivePage"
participant Scene as "IMediaSceneService"
participant Runtime as "GoLiveOutputRuntimeService"
participant Browser as "go-live-output.js"
participant Program as "Program capture"
participant Recorder as "Recording sink"
participant LiveKit as "LiveKit transport"
participant Vdo as "VDO.Ninja transport"
participant Targets as "Distribution targets"
User->>Settings: Configure capture, recording, transport connections, targets
Settings->>Scene: Persist local scene sources
Settings->>GoLive: Persist streaming profiles in browser storage
User->>GoLive: Open studio
GoLive->>Scene: Load current scene
GoLive->>Runtime: Build runtime request from scene + streaming settings
Runtime->>Browser: Start or update browser session
Browser->>Program: Compose canvas + audio mix
User->>GoLive: Start recording
Runtime->>Recorder: Record the canonical program stream
User->>GoLive: Arm VDO.Ninja and/or LiveKit
Runtime->>Vdo: Publish the canonical program stream
Runtime->>LiveKit: Publish the canonical program stream
LiveKit-->>Targets: Report relay-capable target state
Vdo-->>Targets: Report direct browser target state when supported
User->>GoLive: Switch source or layout
Browser->>Program: Recompose the same program stream
The browser runtime is driven by these contracts:
IGoLiveProgramCaptureServiceIGoLiveSourceModuleIGoLiveOutputModuleIGoLiveModuleRegistry
The persisted settings model is:
ProgramCaptureProfileRecordingProfileTransportConnectionProfileDistributionTargetProfile
flowchart LR
Page["GoLivePage"]
Registry["IGoLiveModuleRegistry"]
Source["IGoLiveSourceModule"]
Capture["IGoLiveProgramCaptureService"]
Output["IGoLiveOutputModule"]
Request["GoLiveOutputRuntimeRequest"]
Browser["go-live-output.js"]
Vdo["go-live-output-vdo-ninja.js"]
LiveKit["vendored livekit-client"]
Page --> Registry
Registry --> Source
Registry --> Output
Page --> Capture
Page --> Request
Request --> Browser
Browser --> Vdo
Browser --> LiveKit
Capture --> Browser
Output --> Browser
flowchart LR
Scene["Scene cameras + transforms"]
Audio["Audio bus inputs + delay/gain"]
Factory["GoLiveOutputRequestFactory"]
Runtime["GoLiveOutputRuntimeService"]
Support["go-live-output-support.js"]
Browser["go-live-output.js"]
Program["Composed MediaStream"]
Recording["MediaRecorder"]
LiveKit["LiveKit publish session"]
Vdo["VDO.Ninja publish session"]
Targets["Distribution targets"]
Scene --> Factory
Audio --> Factory
Factory --> Runtime
Runtime --> Support
Runtime --> Browser
Browser --> Program
Program --> Recording
Program --> LiveKit
Program --> Vdo
LiveKit --> Targets
Vdo --> Targets
Transport connections- can ingest remote sources, publish the program, or both
- own room, server, token, base URL, publish URL, and view URL fields
Distribution targets- own RTMP-style target data and bound transport connection ids
- do not imply native browser RTMP support by themselves
Current capability model:
LiveKit- can ingest remote sources
- can publish the program
- can expose downstream-target capability
VDO.Ninja- can ingest remote sources
- can publish the program
- supports hosted and self-hosted base/publish/view URL paths
- does not claim generic downstream relay capability in the current implementation
- Browser UI verification is the primary acceptance bar.
- Component and core tests prove settings normalization, routing, and runtime-request shaping.
- Go Live browser scenarios must prove:
- local recording can start from the composed program feed
VDO.Ninjapublish can start from the composed program feedLiveKitpublish can start from the composed program feed- both transports can be active in one session
- source switching updates the live program state
- single-local-camera browsers show an explicit fallback hint and move the live local preview when the operator selects another camera
- blocked downstream targets are shown honestly
Settingsowns source inventory, per-device sync, program-capture defaults, recording defaults, transport connections, and downstream targets.Go Liveoperates those persisted settings; it must not reintroduce inline provider credential editors.- The browser runtime must not pretend to publish generic RTMP directly unless a real transport path exists.
- The browser runtime must not invent telemetry values that the active transport or recorder does not provide.
- Local recording must continue to capture the same composed program feed that live publish uses.
- Remote guest intake and live publish must stay modular; the UI shell should not change when a module is swapped.
dotnet build ./PrompterOne.slnx -warnaserrordotnet test ./tests/PrompterOne.Core.Tests/PrompterOne.Core.Tests.csprojdotnet test ./tests/PrompterOne.Web.Tests/PrompterOne.Web.Tests.csprojdotnet test ./tests/PrompterOne.Web.UITests/PrompterOne.Web.UITests.csproj --filter "FullyQualifiedName~GoLive"