ScreenRecordingService Consent Dialog Flow and MP4 Encoding Pipeline in OpenClaw-Windows-Node
The OpenClaw-Windows-Node client enforces a privacy-first screen recording flow that displays a one-time user consent dialog, then captures raw desktop frames through Windows.Graphics.Capture, converts them to NV12, and transcodes the result into a Base-64 MP4 payload using MediaTranscoder.
The openclaw/openclaw-windows-node repository implements this two-stage pipeline across NodeService, RecordingConsentDialog, and ScreenRecordingService. Before any pixels reach the encoder, NodeService verifies and persists user authorization; once approved, ScreenRecordingService manages the capture loop, memory-bound buffering, and hardware-accelerated MP4 encoding. The following sections trace both halves of this pipeline exactly as they appear in the source.
Consent Dialog Flow
Before capture begins, NodeService acts as the gatekeeper by checking stored consent or presenting a modal prompt. This prevents silent screen recording and ensures the decision survives application restarts.
Checking Consent and Queuing the Prompt
In NodeService.cs (lines 1815–1840), the startup path calls EnsureRecordingConsentAsync(RecordingType.Screen) before initializing the capture engine. If no prior consent exists, the service instantiates a TaskCompletionSource<bool> and registers it as _screenConsentInFlight (lines 1849–1855) to block duplicate prompts. It then enqueues ShowRecordingConsentDialogAsync on the dispatcher queue (lines 1863–1872) so the UI construction runs on the correct thread.
RecordingConsentDialog UI and Foreground Handling
The modal itself lives in RecordingConsentDialog.cs. Its constructor (lines 34–74) assembles a WinUI window containing a title, description, privacy note, and Allow / Deny buttons (lines 141–160). Because tray utilities can launch behind other windows, the dialog forces itself forward with SetWindowPos and SetForegroundWindow (lines 179–188). The ShowAsync method (lines 175–194) activates the window and yields a Task<bool> that completes only when the user clicks a button or closes the prompt.
Persisting the Decision
After the user responds, NodeService saves the boolean result into ScreenRecordingConsentGiven and invokes _settings.Save() to flush the flag to %APPDATA%\OpenClawTray\settings.json (lines 1870–1882). On future recordings, HasRecordingConsent (lines 1841–1846) reads this file and skips the dialog entirely. If the user denies permission, the TaskCompletionSource resolves to false and an InvalidOperationException propagates to abort the recording start (lines 1884–1889).
MP4 Encoding Pipeline Architecture
Once consent is confirmed, ScreenRecordingService takes over. It initializes the Windows Graphics Capture stack, buffers frames in a loop, and feeds them to MediaTranscoder for containerized MP4 output.
Capture Initialization and Frame Buffering
The pipeline begins in ScreenRecordingService.cs by creating a GraphicsCaptureItem for the target monitor via CreateCaptureItem, alongside a Direct3D‑11 device from CreateDirect3DDevice. A free-threaded Direct3D11CaptureFramePool is allocated with a capacity of PoolBuffers (2 frames). The FrameArrived handler copies the incoming surface into latestFrame and signals a semaphore named ready to unblock the consumer loop.
Capture Loop and Memory Safety
A timing loop runs for the clamped duration, sleeping intervalMs between iterations. Each tick snapshots latestFrame, converts the surface to a SoftwareBitmap, and extracts raw BGRA bytes through ExtractBitmapBytes. These uncompressed frames accumulate in an in-memory list. To prevent out-of-memory failures, the buffer is guarded by MaxFrameBufferBytes at approximately 500 MiB.
Encoding with MediaTranscoder
When the capture loop exits, EncodeToMp4Async performs the following phases as defined in ScreenRecordingService.cs:
- Prepare input. The method builds an uncompressed
VideoEncodingPropertiesdescriptor for NV12 with even-aligned width and height, then creates aMediaStreamSourcethat fulfillsSampleRequestedevents (lines 1668–1768). - NV12 conversion. A custom
BgraToNv12routine (lines 2310–2525) transforms each BGRA frame into YUV 4:2:0 format, which the transcoder requires. - MP4 profile.
MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto)initializes the container profile, and the code explicitly assigns bitrate, width, height, and frame rate (lines 1912–1998). - Hardware-to-software fallback. The
MediaTranscoderfirst attempts hardware-accelerated encoding by settingHardwareAccelerationEnabled = true. If this attempt fails with a COM error, the pipeline catches the exception and retries in software mode (lines 2023–2129). WhenPrepareMediaStreamSourceTranscodeAsyncreturnsCanTranscode, the operation proceeds viaTranscodeAsync. - Failure handling. If both hardware and software paths fail, the method throws
InvalidOperationException("No encoder available (hardware or software)").
Returning the Encoded Result
The transcoder writes into an InMemoryRandomAccessStream. The code reads the stream back with a DataReader, copies the bytes into an array, and calls Convert.ToBase64String to produce the final payload (lines 2131–2248). The caller receives a ScreenRecordResult object (lines 1338–1389) populated with the format mp4, the Base-64 string, actual duration, FPS, dimensions, and an audio flag set to false.
Practical Usage Example
The following C# snippet demonstrates how to start a screen recording and persist the decoded MP4 bytes to disk:
var args = new ScreenRecordArgs
{
DurationMs = 10_000, // 10 seconds
Fps = 30,
ScreenIndex = 0 // primary monitor
};
var recorder = new ScreenRecordingService(logger);
ScreenRecordResult result = await recorder.RecordAsync(args);
byte[] mp4Bytes = Convert.FromBase64String(result.Base64);
await File.WriteAllBytesAsync(@"C:\Temp\recording.mp4", mp4Bytes);
If the user has never granted consent, RecordAsync automatically triggers the consent dialog through NodeService before the capture begins.
Summary
- Consent is mandatory and persistent.
NodeServiceenforces a one-timeRecordingConsentDialogand stores the decision in%APPDATA%\OpenClawTray\settings.json. - Capture relies on Windows.Graphics.Capture. A
Direct3D11CaptureFramePoolbuffers raw frames, and a semaphore-driven loop collects BGRA bitmaps. - Memory consumption is capped. The pipeline refuses to buffer more than approximately 500 MiB of raw frame data.
- NV12 conversion precedes encode. The custom
BgraToNv12method prepares frames for theMediaTranscoder. - Hardware acceleration is preferred. The encoder falls back to software mode only if the hardware path raises a COM error.
- Output is a Base-64 MP4 payload.
ScreenRecordResultpackages the encoded video with metadata for downstream transmission or storage.
Frequently Asked Questions
How does OpenClaw-Windows-Node handle first-time screen recording consent?
NodeService.EnsureRecordingConsentAsync checks for a stored ScreenRecordingConsentGiven flag before starting any capture. If the flag is missing, it launches a modal RecordingConsentDialog, forces it to the foreground, and waits for the user to select Allow or Deny. The choice is persisted to disk so the prompt does not appear again.
What video format does the ScreenRecordingService encoding pipeline produce?
The pipeline captures raw BGRA frames from the GPU, converts them to NV12 (YUV 4:2:0) using the internal BgraToNv12 method, and then feeds them into MediaTranscoder configured with an MP4 profile. The final deliverable is an MP4 container returned as a Base-64 string inside a ScreenRecordResult object.
Why does the MP4 encoder fall back to software transcoding?
ScreenRecordingService sets HardwareAccelerationEnabled = true on the MediaTranscoder by default for faster encoding. If that call fails with a COM exception, the catch block retries with hardware acceleration disabled. Should both attempts fail, the pipeline raises an InvalidOperationException stating that no encoder is available.
Where is the user's screen recording consent preference stored?
NodeService serializes the consent boolean into a JSON settings file located at %APPDATA%\OpenClawTray\settings.json. The HasRecordingConsent property reads this file on startup, which allows subsequent recording requests to bypass the dialog entirely.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →