feat(webapp): add @devolutions/web-recorder session capture library#1824
feat(webapp): add @devolutions/web-recorder session capture library#1824irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 4 commits into
Conversation
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
Let maintainers know that an action is required on their side
|
…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
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— canvascaptureStream+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.vite-plugin-dts, mirroringshadow-player/multi-video-player; added one row to thepublish-libraries.ymlnpm matrix.Not included (follow-ups)
drawEmpty/globalAlphakeepalive withcaptureStream(0)+videoTrack.requestFrame()), which will land here once this is the shared source.Testing
Builds clean (
pnpm --filter @devolutions/web-recorder build→dist/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.