11import { type ChildProcessByStdio , spawn } from "node:child_process" ;
22import type { Readable , Writable } from "node:stream" ;
33import type { CursorRecordingData } from "../../../src/native/contracts" ;
4+ import {
5+ type CursorPauseRange ,
6+ compactCursorTelemetryPauseRanges ,
7+ } from "../cursor/recording/cursorPauseRanges" ;
48import {
59 NdjsonLineReader ,
610 PipeWireCursorAccumulator ,
@@ -114,6 +118,9 @@ export class LinuxNativeCaptureSession {
114118 private lastError : string | null = null ;
115119 private paused = false ;
116120 private helperOutputText = "" ;
121+ private cursorRecordingStartMs = 0 ;
122+ private cursorPauseStartedAtMs : number | null = null ;
123+ private cursorPauseRanges : CursorPauseRange [ ] = [ ] ;
117124
118125 constructor ( private readonly config : LinuxCaptureConfig ) {
119126 this . cursor = new PipeWireCursorAccumulator ( config . maxCursorSamples ) ;
@@ -134,6 +141,10 @@ export class LinuxNativeCaptureSession {
134141
135142 this . cursor . reset ( Date . now ( ) ) ;
136143 this . lines . reset ( ) ;
144+ this . cursorRecordingStartMs = 0 ;
145+ this . cursorPauseStartedAtMs = null ;
146+ this . cursorPauseRanges = [ ] ;
147+ this . paused = false ;
137148
138149 const request = {
139150 outputPath : this . config . outputPath ,
@@ -305,16 +316,27 @@ export class LinuxNativeCaptureSession {
305316 if ( this . paused ) {
306317 return ;
307318 }
308- this . paused = true ;
309319 this . write ( "pause" ) ;
320+ this . paused = true ;
321+ this . cursorPauseStartedAtMs = Date . now ( ) ;
310322 }
311323
312324 resume ( ) {
313325 if ( ! this . paused ) {
314326 return ;
315327 }
316- this . paused = false ;
317328 this . write ( "resume" ) ;
329+ this . completeCursorPauseRange ( ) ;
330+ this . paused = false ;
331+ }
332+
333+ private completeCursorPauseRange ( endMs = Date . now ( ) ) {
334+ if ( this . cursorPauseStartedAtMs === null || this . cursorRecordingStartMs <= 0 ) return ;
335+ this . cursorPauseRanges . push ( {
336+ startMs : Math . max ( 0 , this . cursorPauseStartedAtMs - this . cursorRecordingStartMs ) ,
337+ endMs : Math . max ( 0 , endMs - this . cursorRecordingStartMs ) ,
338+ } ) ;
339+ this . cursorPauseStartedAtMs = null ;
318340 }
319341
320342 get isPaused ( ) {
@@ -375,6 +397,7 @@ export class LinuxNativeCaptureSession {
375397 throw new Error ( "The Linux capture helper is not running." ) ;
376398 }
377399
400+ this . completeCursorPauseRange ( ) ;
378401 const exited = new Promise < void > ( ( resolve , reject ) => {
379402 this . stoppedResolve = resolve ;
380403 this . stoppedReject = reject ;
@@ -518,6 +541,7 @@ export class LinuxNativeCaptureSession {
518541 // telemetry is re-based onto it and anything from during the
519542 // picker is dropped rather than left pinned to the start.
520543 this . cursor . rebase ( payload . timestampMs ) ;
544+ this . cursorRecordingStartMs = payload . timestampMs ;
521545 this . capturing = true ;
522546 console . info (
523547 "[capture-linux] capture started" ,
@@ -538,7 +562,10 @@ export class LinuxNativeCaptureSession {
538562 durationMs : payload . durationMs ,
539563 frames : payload . frames ,
540564 droppedFrames : payload . dropped ,
541- cursor : this . cursor . toRecordingData ( ) ,
565+ cursor : compactCursorTelemetryPauseRanges (
566+ this . cursor . toRecordingData ( ) ,
567+ this . cursorPauseRanges ,
568+ ) ,
542569 ...( this . sourceKind ? { sourceKind : this . sourceKind } : { } ) ,
543570 ...( this . videoEncoder ? { videoEncoder : this . videoEncoder } : { } ) ,
544571 } ;
0 commit comments