Skip to content

webRTC ClientWindow - #491

Merged
gtong-nv merged 6 commits into
mainfrom
dev/gtong/webrtc-api-2
Aug 20, 2026
Merged

webRTC ClientWindow#491
gtong-nv merged 6 commits into
mainfrom
dev/gtong/webrtc-api-2

Conversation

@gtong-nv

@gtong-nv gtong-nv commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add v2 ApplicationRunner and shared client-window factory.
  • Add a WebRTC client window with buffered browser input and video delivery.
  • Add a browser-accessible red-screen demo with keyboard and button controls.
  • Package HTML and JavaScript as separate web resources.
  • Add CPU tests for lifecycle, window selection, input, and video delivery.

Workflow

Browser
  │ keyboard/button events
  ▼
WebRTCServer
  │ callback
  ▼
WebRTCClientWindow input queue
  │ UserInputEvents
  ▼
ApplicationRunner → run_session → RedScreenSession.step()
                                  │
                                  ▼
                              StepResult
                                  │
                                  ▼
Browser video ← WebRTCServer ← WebRTCClientWindow.write()

Provide v2 browser input buffering and video delivery without depending on the existing serving stack.
@copy-pr-bot

copy-pr-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Keep transport callbacks internal while preserving the polling interface used by the session runner.
@gtong-nv
gtong-nv force-pushed the dev/gtong/webrtc-api-2 branch from 5231021 to 49de20e Compare August 20, 2026 01:01
Wire application lifecycle and WebRTC presentation end to end, including browser controls and responsive red-screen output.
Keep browser markup and behavior independently editable while packaging and serving both assets with the runtime.
Centralize presentation mode selection so integrations reuse one runtime-owned factory.
@gtong-nv
gtong-nv marked this pull request as ready for review August 20, 2026 05:30
<video id="video" autoplay playsinline></video>
<button id="activate" type="button">Activate</button>
<button id="reset" type="button">Reset</button>
<script src="/app.js"></script>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to demo the functionality. Can be cleaned up. In the production, only the video track and <script src="/app.js"></script> for registering browser event handlers are needed



def _result_to_rgb_frames(
result: StepResult, session_desc: SessionDesc

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified once we have proper shared util classes

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a v2 application runner and WebRTC client-window implementation, packages its browser resources, and extends the red-screen integration into a browser-accessible demonstration.

  • Adds lifecycle and client-window factories for the v2 runtime.
  • Adds WebRTC signaling, buffered browser controls, and video delivery.
  • Adds a red-screen CLI, intensity controls, packaging metadata, and CPU tests.

Confidence Score: 1/5

The PR is not yet safe to merge because buffered controls are still dropped and both remote-input and video-frame queues can grow without bound.

The current code still selects only the last event from each buffered batch, accepts unlimited remote control events into a SimpleQueue, and places every generated frame into an unbounded paced-playback queue; these previously reported failures remain reachable.

Files Needing Attention: flashdreams/flashdreams/runtime_v2/webrtc_client_window.py, flashdreams/flashdreams/runtime_v2/serving/webrtc_server.py, integrations_v2/red_screen/red_screen/app.py

Important Files Changed

Filename Overview
flashdreams/flashdreams/runtime_v2/serving/webrtc_server.py Adds HTTP/WebRTC signaling, browser-message conversion, and paced video transport; the previously reported frame-queue issue remains unresolved.
flashdreams/flashdreams/runtime_v2/webrtc_client_window.py Adds the v2 WebRTC window and cross-thread input buffering; the previously reported input-queue issue remains unresolved.
integrations_v2/red_screen/red_screen/app.py Adds the runnable WebRTC demo and intensity controls; the previously reported buffered-edge handling defect remains unresolved.
flashdreams/flashdreams/runtime_v2/application_runner.py Adds a concise application lifecycle wrapper that delegates session execution and closes the application in a finally block.
flashdreams/test_v2/test_webrtc_client_window.py Adds CPU coverage for signaling, browser-event buffering, packaged resources, and delivery of a video frame.

Sequence Diagram

sequenceDiagram
  participant Browser
  participant Server as WebRTCServer
  participant Window as WebRTCClientWindow
  participant Runner as ApplicationRunner
  participant Session as RedScreenSession
  Browser->>Server: keyboard/button message
  Server->>Window: timestamped UserInputEvent
  Window-->>Runner: buffered UserInputEvents
  Runner->>Session: step(events)
  Session-->>Runner: StepResult
  Runner->>Window: write(result)
  Window->>Server: enqueue video frames
  Server-->>Browser: paced WebRTC video
Loading

Reviews (2): Last reviewed commit: "Document WebRTC client-window protocol m..." | Re-trigger Greptile

Comment on lines 100 to +112
def _apply_events(self, events: UserInputEvents) -> None:
# Events are edges, not levels: a key stays held across steps that carry
# no events for it, so only the last edge per step changes the state.
for event in events.get_events():
data = event.get_event_data()
if (
isinstance(data, KeyboardUserInputEventData)
and data.key == self._config.activation_key
):
self._key_held = data.pressed
received_events = events.get_events()
if not received_events:
return
data = received_events[-1].get_event_data()
if not isinstance(data, KeyboardUserInputEventData):
return
if data.key == self._config.activation_key:
self._key_held = data.pressed
elif data.pressed and data.key.lower() == "w":
self._color_intensity = min(1.0, self._color_intensity + 0.1)
elif data.pressed and data.key.lower() == "s":
self._color_intensity = max(0.0, self._color_intensity - 0.1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Buffered input edges are discarded

When multiple input edges arrive during one polling interval, _apply_events processes only the final event, causing quick activation taps and repeated intensity changes to be lost.

Suggested change
def _apply_events(self, events: UserInputEvents) -> None:
# Events are edges, not levels: a key stays held across steps that carry
# no events for it, so only the last edge per step changes the state.
for event in events.get_events():
data = event.get_event_data()
if (
isinstance(data, KeyboardUserInputEventData)
and data.key == self._config.activation_key
):
self._key_held = data.pressed
received_events = events.get_events()
if not received_events:
return
data = received_events[-1].get_event_data()
if not isinstance(data, KeyboardUserInputEventData):
return
if data.key == self._config.activation_key:
self._key_held = data.pressed
elif data.pressed and data.key.lower() == "w":
self._color_intensity = min(1.0, self._color_intensity + 0.1)
elif data.pressed and data.key.lower() == "s":
self._color_intensity = max(0.0, self._color_intensity - 0.1)
def _apply_events(self, events: UserInputEvents) -> None:
for event in events.get_events():
data = event.get_event_data()
if not isinstance(data, KeyboardUserInputEventData):
continue
if data.key == self._config.activation_key:
self._key_held = data.pressed
elif data.pressed and data.key.lower() == "w":
self._color_intensity = min(1.0, self._color_intensity + 0.1)
elif data.pressed and data.key.lower() == "s":
self._color_intensity = max(0.0, self._color_intensity - 0.1)

port: Listening port. Zero asks the operating system to choose one.
startup_timeout_seconds: Maximum time to wait for server startup.
"""
self._input_events: queue.SimpleQueue[UserInputEvent] = queue.SimpleQueue()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Remote input queue is unbounded

When a connected client sends valid control messages faster than the UI loop drains them, every event is retained in an unbounded SimpleQueue, causing process memory exhaustion and denial of service. How this was verified: The data-channel callback reaches an unconditional SimpleQueue.put, and the changed path contains no capacity, rate, or coalescing guard before the UI-rate drain.

Knowledge Base Used: WebRTC Serving Flow

Comment on lines +49 to +51
self._frames: asyncio.Queue[np.ndarray[Any, np.dtype[np.uint8]] | None] = (
asyncio.Queue()
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Video playback queue grows unbounded

When a session supplies frames faster than WebRTC playback consumes them, _VideoTrack.enqueue retains every full-resolution CPU frame in an unbounded queue while recv removes only one paced frame at a time, causing memory exhaustion during sustained high-throughput sessions.

Knowledge Base Used: WebRTC Serving Flow

parser.add_argument(
"application_args",
nargs=argparse.REMAINDER,
help="Arguments after -- are passed to the red-screen application.",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of these should be parsed by flashdreams-run

Make each implementation method's InputSource or OutputSink responsibility explicit.
@gtong-nv
gtong-nv force-pushed the dev/gtong/webrtc-api-2 branch from 1f8fb87 to cc7ff22 Compare August 20, 2026 18:10
@gtong-nv

Copy link
Copy Markdown
Collaborator Author

/ok to test cc7ff22

@@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just leaving a note that I did not use this for the Mp4ClientWindow, as I assume it will be superseded by the new flashdreams-run, so we can remove this file when that happens. Is my understanding right?

@gtong-nv
gtong-nv enabled auto-merge August 20, 2026 18:29
@gtong-nv
gtong-nv added this pull request to the merge queue Aug 20, 2026
Merged via the queue into main with commit 3cad1b8 Aug 20, 2026
7 checks passed
@gtong-nv
gtong-nv deleted the dev/gtong/webrtc-api-2 branch August 20, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants