16
16
17
17
import { ChildProcess } from 'child_process' ;
18
18
import { ffmpegExecutable } from '../../utils/binaryPaths' ;
19
- import { assert } from '../../utils/utils' ;
19
+ import { assert , monotonicTime } from '../../utils/utils' ;
20
20
import { launchProcess } from '../processLauncher' ;
21
21
import { Progress , ProgressController } from '../progress' ;
22
22
import * as types from '../types' ;
@@ -26,11 +26,13 @@ const fps = 25;
26
26
export class VideoRecorder {
27
27
private _process : ChildProcess | null = null ;
28
28
private _gracefullyClose : ( ( ) => Promise < void > ) | null = null ;
29
- private _lastWritePromise : Promise < void > | undefined ;
29
+ private _lastWritePromise : Promise < void > = Promise . resolve ( ) ;
30
30
private _lastFrameTimestamp : number = 0 ;
31
31
private _lastFrameBuffer : Buffer | null = null ;
32
32
private _lastWriteTimestamp : number = 0 ;
33
33
private readonly _progress : Progress ;
34
+ private _frameQueue : Buffer [ ] = [ ] ;
35
+ private _isStopped = false ;
34
36
35
37
static async launch ( options : types . PageScreencastOptions ) : Promise < VideoRecorder > {
36
38
if ( ! options . outputFile . endsWith ( '.webm' ) )
@@ -50,7 +52,6 @@ export class VideoRecorder {
50
52
}
51
53
52
54
private async _launch ( options : types . PageScreencastOptions ) {
53
- assert ( ! this . _isRunning ( ) ) ;
54
55
const w = options . width ;
55
56
const h = options . height ;
56
57
const args = `-loglevel error -f image2pipe -c:v mjpeg -i - -y -an -r ${ fps } -c:v vp8 -qmin 0 -qmax 50 -crf 8 -b:v 1M -vf pad=${ w } :${ h } :0:0:gray,crop=${ w } :${ h } :0:0` . split ( ' ' ) ;
@@ -84,58 +85,43 @@ export class VideoRecorder {
84
85
this . _gracefullyClose = gracefullyClose ;
85
86
}
86
87
87
- async writeFrame ( frame : Buffer , timestamp : number ) {
88
+ writeFrame ( frame : Buffer , timestamp : number ) {
88
89
assert ( this . _process ) ;
89
- if ( ! this . _isRunning ( ) )
90
+ if ( this . _isStopped )
90
91
return ;
91
92
this . _progress . log ( `writing frame ` + timestamp ) ;
92
- if ( this . _lastFrameBuffer )
93
- this . _lastWritePromise = this . _flushLastFrame ( timestamp - this . _lastFrameTimestamp ) . catch ( e => this . _progress . log ( 'Error while writing frame: ' + e ) ) ;
93
+
94
+ if ( this . _lastFrameBuffer ) {
95
+ const durationSec = timestamp - this . _lastFrameTimestamp ;
96
+ const repeatCount = Math . max ( 1 , Math . round ( fps * durationSec ) ) ;
97
+ for ( let i = 0 ; i < repeatCount ; ++ i )
98
+ this . _frameQueue . push ( this . _lastFrameBuffer ) ;
99
+ this . _lastWritePromise = this . _lastWritePromise . then ( ( ) => this . _sendFrames ( ) ) ;
100
+ }
101
+
94
102
this . _lastFrameBuffer = frame ;
95
103
this . _lastFrameTimestamp = timestamp ;
96
- this . _lastWriteTimestamp = Date . now ( ) ;
104
+ this . _lastWriteTimestamp = monotonicTime ( ) ;
97
105
}
98
106
99
- private async _flushLastFrame ( durationSec : number ) : Promise < void > {
100
- assert ( this . _process ) ;
101
- const frame = this . _lastFrameBuffer ;
102
- if ( ! frame )
103
- return ;
104
- const previousWrites = this . _lastWritePromise ;
105
- let finishedWriting : ( ) => void ;
106
- const writePromise = new Promise < void > ( fulfill => finishedWriting = fulfill ) ;
107
- const repeatCount = Math . max ( 1 , Math . round ( fps * durationSec ) ) ;
108
- this . _progress . log ( `flushing ${ repeatCount } frame(s)` ) ;
109
- await previousWrites ;
110
- for ( let i = 0 ; i < repeatCount ; i ++ ) {
111
- const callFinish = i === ( repeatCount - 1 ) ;
112
- this . _process . stdin . write ( frame , ( error : Error | null | undefined ) => {
113
- if ( error )
114
- this . _progress . log ( `ffmpeg failed to write: ${ error } ` ) ;
115
- if ( callFinish )
116
- finishedWriting ( ) ;
117
- } ) ;
118
- }
119
- return writePromise ;
107
+ private async _sendFrames ( ) {
108
+ while ( this . _frameQueue . length )
109
+ await this . _sendFrame ( this . _frameQueue . shift ( ) ! ) ;
110
+ }
111
+
112
+ private async _sendFrame ( frame : Buffer ) {
113
+ return new Promise ( f => this . _process ! . stdin . write ( frame , f ) ) . then ( error => {
114
+ if ( error )
115
+ this . _progress . log ( `ffmpeg failed to write: ${ error } ` ) ;
116
+ } ) ;
120
117
}
121
118
122
119
async stop ( ) {
123
- if ( ! this . _gracefullyClose )
120
+ if ( this . _isStopped )
124
121
return ;
125
-
126
- if ( this . _lastWriteTimestamp ) {
127
- const durationSec = ( Date . now ( ) - this . _lastWriteTimestamp ) / 1000 ;
128
- if ( ! this . _lastWritePromise || durationSec > 1 / fps )
129
- this . _flushLastFrame ( durationSec ) . catch ( e => this . _progress . log ( 'Error while writing frame: ' + e ) ) ;
130
- }
131
-
132
- const close = this . _gracefullyClose ;
133
- this . _gracefullyClose = null ;
122
+ this . writeFrame ( Buffer . from ( [ ] ) , this . _lastFrameTimestamp + ( monotonicTime ( ) - this . _lastWriteTimestamp ) / 1000 ) ;
123
+ this . _isStopped = true ;
134
124
await this . _lastWritePromise ;
135
- await close ( ) ;
136
- }
137
-
138
- private _isRunning ( ) : boolean {
139
- return ! ! this . _gracefullyClose ;
125
+ await this . _gracefullyClose ! ( ) ;
140
126
}
141
127
}
0 commit comments