Skip to content

feat(webapp): add @devolutions/web-recorder session capture library#1824

Draft
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 4 commits into
masterfrom
feat/web-recorder
Draft

feat(webapp): add @devolutions/web-recorder session capture library#1824
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 4 commits into
masterfrom
feat/web-recorder

Conversation

@irvingoujAtDevolution

Copy link
Copy Markdown
Contributor

Summary

Adds @devolutions/web-recorder, a framework-agnostic browser session-recording capture library, alongside the existing playback packages (shadow-player, multi-video-player) in the webapp workspace.

Motivated by DVLS-14621: the browser RDP/terminal recorder is currently copy-pasted (~500 lines, ~92–94% identical) between DVLS web and Hub web and has started to diverge. This package is intended as the single source of truth so fixes land once.

What's in it

  • WebMRecorder — canvas captureStream + MediaRecorder → WebM, streamed over WS to /jet/jrec/push. Telemetry is an optional injected hook (onTelemetry) so the library stays framework-agnostic.
  • AsciiCastV2Recorder — terminal asciicast v2 capture.
  • IRecordableSession — minimal capture contract.
  • Built with Vite lib mode + vite-plugin-dts, mirroring shadow-player/multi-video-player; added one row to the publish-libraries.yml npm matrix.

Not included (follow-ups)

  • Wiring DVLS web and Hub web to consume the package and deleting their local copies.
  • The DVLS-14621 Edge 149 fix (replace the drawEmpty/globalAlpha keepalive with captureStream(0) + videoTrack.requestFrame()), which will land here once this is the shared source.

Testing

Builds clean (pnpm --filter @devolutions/web-recorder builddist/index.js + bundled .d.ts). Exercised end-to-end through a local capture harness against a real Gateway + tokengen (harness not part of this PR). Draft pending review and consumer migration.

Framework-agnostic browser session-recording capture (WebM video via
canvas.captureStream + MediaRecorder, plus asciicast terminal) that pushes
to the Gateway /jet/jrec/push endpoint. It lives beside the playback
packages (shadow-player, multi-video-player) and ships on the same
publish-libraries workflow.

Intended as the single source of truth to replace the duplicated recorder
copies currently in DVLS web and Hub web (consumer migration is a follow-up).

Ref: DVLS-14621
@github-actions

Copy link
Copy Markdown

Let maintainers know that an action is required on their side

  • Add the label release-required Please cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module) when you request a maintainer to cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module)

  • Add the label release-blocker Follow-up is required before cutting a new release if a follow-up is required before cutting a new release

  • Add the label publish-required Please publish libraries (`Devolutions.Gateway.Utils`, OpenAPI clients, etc) when you request a maintainer to publish libraries (Devolutions.Gateway.Utils, OpenAPI clients, etc.)

  • Add the label publish-blocker Follow-up is required before publishing libraries if a follow-up is required before publishing libraries

…ureStream keepalive

Use canvas.captureStream(0) (manual-frame mode) plus a timer-driven requestFrame()
to push frames at a deterministic cadence, replacing the captureStream(8) +
transparent-pixel ("drawEmpty") keepalive. The old keepalive relied on canvas
dirty-tracking that stopped emitting frames on Edge 149 (empty/near-empty WebM ->
recording-policy session kills) and also mutated the shared 2D context
(globalAlpha=0). requestFrame() captures the current pixels regardless of change,
so static desktops still produce a continuous stream and the frame count is
controlled explicitly.

Ref: DVLS-14621
…ty keepalive

captureStream(0) + requestFrame() (manual mode) does not feed MediaRecorder in
Chromium -- it yields zero frames (verified empirically: 0 bytes vs 68 KB for auto
mode), which surfaced as the recorder hanging on "waiting for the first frame".

Revert to captureStream(8) (automatic capture) and replace the static-content
keepalive's zero-alpha no-op (elided by Edge 149 dirty-tracking -> empty WebM) with
a real 1px value change, wrapped in save()/restore() so it cannot leak globalAlpha
onto the shared 2D context.

Ref: DVLS-14621
captureStream only captures a frame when the canvas is composited, and compositing
is driven by the rAF/vsync loop -- NOT by setInterval. With static content (no app
animation) a setInterval keepalive marks the canvas dirty but it is never presented,
so zero frames are captured (verified empirically on a static canvas: setInterval ->
0 frames, rAF -> 15 frames/2s). Run the 1px dirty-nudge inside requestAnimationFrame
so the compositor ticks and frames flow even when the recorded canvas is static.

Ref: DVLS-14621
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant