@@ -51,7 +51,7 @@ export class VideoRecorder {
5151 const outputFile = options . fileName ?? path . join ( this . _screencast . page . browserContext . _browser . options . artifactsDir , createGuid ( ) + '.webm' ) ;
5252
5353 this . _client = {
54- onFrame : frame => this . _videoRecorder ! . writeFrame ( frame . buffer , frame . frameSwapWallTime / 1000 ) ,
54+ onFrame : frame => this . _videoRecorder ! . writeFrame ( frame . buffer , frame . frameSwapWallTime ) ,
5555 gracefulClose : ( ) => this . stop ( ) ,
5656 dispose : ( ) => this . stop ( ) . catch ( e => debugLogger . log ( 'error' , `Failed to stop video recorder: ${ String ( e ) } ` ) ) ,
5757 size : options . size ,
@@ -100,8 +100,8 @@ class FfmpegVideoRecorder {
100100 private _size : types . Size ;
101101 private _process : ChildProcess | null = null ;
102102 private _gracefullyClose : ( ( ) => Promise < void > ) | null = null ;
103- private _firstFrameTimestamp : number = 0 ;
104- private _lastFrame : { timestamp : number , frameNumber : number , buffer : Buffer } | null = null ;
103+ private _creationTimeMs : number ;
104+ private _lastFrame : { timestamp : number , buffer : Buffer } | null = null ;
105105 private _lastWriteNodeTime : number = 0 ;
106106 private _isStopped = false ;
107107 private _ffmpegPath : string ;
@@ -114,6 +114,7 @@ class FfmpegVideoRecorder {
114114 this . _outputFile = outputFile ;
115115 this . _ffmpegPath = ffmpegPath ;
116116 this . _size = size ;
117+ this . _creationTimeMs = Date . now ( ) ;
117118 this . _launchPromise = this . _launch ( page ) . catch ( e => e ) ;
118119 }
119120
@@ -165,6 +166,7 @@ class FfmpegVideoRecorder {
165166 const h = this . _size . height ;
166167 const videoFilterArgs = page . getFFmpegVideoFilterArgs ?.( { width : w , height : h } ) ?? `pad=${ w } :${ h } :0:0:gray,crop=${ w } :${ h } :0:0` ;
167168 const args = `-loglevel error -f matroska -fpsprobesize 0 -probesize 32 -analyzeduration 0 -i pipe:0 -y -an -r ${ fps } -c:v vp8 -qmin 0 -qmax 50 -crf 8 -deadline realtime -speed 8 -b:v 1M -threads 1 -vf ${ videoFilterArgs } ` . split ( ' ' ) ;
169+ args . push ( '-metadata' , `creation_time=${ new Date ( this . _creationTimeMs ) . toISOString ( ) } ` ) ;
168170 args . push ( this . _outputFile ) ;
169171
170172 const { launchedProcess, gracefullyClose } = await launchProcess ( {
@@ -205,20 +207,13 @@ class FfmpegVideoRecorder {
205207 if ( this . _isStopped )
206208 return ;
207209
208- if ( ! this . _firstFrameTimestamp )
209- this . _firstFrameTimestamp = timestamp ;
210-
211- const frameNumber = Math . floor ( ( timestamp - this . _firstFrameTimestamp ) * fps ) ;
212- if ( this . _lastFrame && frameNumber !== this . _lastFrame . frameNumber )
213- this . _emitFrame ( this . _lastFrame . buffer , this . _lastFrame . frameNumber ) ;
214-
215- this . _lastFrame = { buffer : frame , timestamp, frameNumber } ;
210+ this . _emitFrame ( frame , timestamp - this . _creationTimeMs ) ;
211+ this . _lastFrame = { buffer : frame , timestamp } ;
216212 this . _lastWriteNodeTime = monotonicTime ( ) ;
217213 }
218214
219- private _emitFrame ( frame : Buffer , frameNumber : number ) {
220- const timestampMs = Math . max ( 0 , Math . round ( frameNumber * 1000 / fps ) ) ;
221- this . _process ! . stdin ! . write ( writeClusterHeader ( timestampMs , frame . length ) ) ;
215+ private _emitFrame ( frame : Buffer , timestampMs : number ) {
216+ this . _process ! . stdin ! . write ( writeClusterHeader ( Math . max ( 0 , Math . round ( timestampMs ) ) , frame . length ) ) ;
222217 this . _process ! . stdin ! . write ( frame ) ;
223218 }
224219
@@ -231,15 +226,10 @@ class FfmpegVideoRecorder {
231226 return ;
232227 if ( ! this . _lastFrame ) {
233228 // ffmpeg only creates a file upon some non-empty input.
234- this . _writeFrame ( createWhiteImage ( this . _size . width , this . _size . height ) , monotonicTime ( ) / 1000 ) ;
229+ this . _writeFrame ( createWhiteImage ( this . _size . width , this . _size . height ) , Date . now ( ) ) ;
235230 }
236- // Emit the last received frame at its own slot, then repeat it at the end so it stays visible
237- // for at least 1s. This also ensures non-empty videos with 1 frame and gives the output stream
238- // a final timestamp.
239- this . _emitFrame ( this . _lastFrame ! . buffer , this . _lastFrame ! . frameNumber ) ;
240- const addTime = Math . max ( ( monotonicTime ( ) - this . _lastWriteNodeTime ) / 1000 , 1 ) ;
241- const endFrameNumber = Math . floor ( ( this . _lastFrame ! . timestamp + addTime - this . _firstFrameTimestamp ) * fps ) ;
242- this . _emitFrame ( this . _lastFrame ! . buffer , endFrameNumber ) ;
231+ const addTimeMs = Math . max ( monotonicTime ( ) - this . _lastWriteNodeTime , 1000 ) ;
232+ this . _emitFrame ( this . _lastFrame ! . buffer , this . _lastFrame ! . timestamp + addTimeMs - this . _creationTimeMs ) ;
243233 this . _isStopped = true ;
244234 try {
245235 await this . _gracefullyClose ! ( ) ;
0 commit comments