1- import { ChildProcessWithoutNullStreams , spawn } from 'node:child_process' ;
2- import { readFile , unlink } from 'node:fs/promises' ;
3- import { tmpdir } from 'node:os' ;
4- import { join , normalize } from 'node:path' ;
51import { W3C_ELEMENT_KEY , errors } from '@appium/base-driver' ;
62import { Element , Rect } from '@appium/types' ;
7- import { NovaWindowsDriver } from '../driver ' ;
8- import { $ , getBundledFfmpegPath , sleep } from '../util ' ;
3+ import { tmpdir } from 'node:os ' ;
4+ import { join , normalize } from 'node:path ' ;
95import { POWER_SHELL_FEATURE } from '../constants' ;
10- import { keyDown ,
11- keyUp ,
12- mouseDown ,
13- mouseMoveAbsolute ,
14- mouseScroll ,
15- mouseUp ,
16- sendKeyboardEvents
17- } from '../winapi/user32' ;
18- import { KeyEventFlags , VirtualKey } from '../winapi/types' ;
6+ import { NovaWindowsDriver } from '../driver' ;
7+ import { ClickType , Enum , Key } from '../enums' ;
198import {
209 AutomationElement ,
2110 AutomationElementMode ,
@@ -29,7 +18,18 @@ import {
2918 convertStringToCondition ,
3019 pwsh
3120} from '../powershell' ;
32- import { ClickType , Enum , Key } from '../enums' ;
21+ import { $ , sleep } from '../util' ;
22+ import { DEFAULT_EXT , ScreenRecorder , UploadOptions , uploadRecordedMedia } from './screen-recorder' ;
23+ import { KeyEventFlags , VirtualKey } from '../winapi/types' ;
24+ import {
25+ keyDown ,
26+ keyUp ,
27+ mouseDown ,
28+ mouseMoveAbsolute ,
29+ mouseScroll ,
30+ mouseUp ,
31+ sendKeyboardEvents
32+ } from '../winapi/user32' ;
3333
3434const PLATFORM_COMMAND_PREFIX = 'windows:' ;
3535
@@ -715,110 +715,73 @@ export async function executeScroll(this: NovaWindowsDriver, scrollArgs: {
715715export async function startRecordingScreen ( this : NovaWindowsDriver , args ?: {
716716 outputPath ?: string ,
717717 timeLimit ?: number ,
718- videoSize ?: string ,
719718 videoFps ?: number ,
719+ videoFilter ?: string ,
720+ preset ?: string ,
721+ captureCursor ?: boolean ,
722+ captureClicks ?: boolean ,
723+ audioInput ?: string ,
720724 forceRestart ?: boolean ,
721725} ) : Promise < void > {
722726 const {
723- outputPath = join ( tmpdir ( ) , `novawindows-recording-${ Date . now ( ) } .mp4` ) ,
724- timeLimit = 180 ,
725- videoSize,
726- videoFps = 15 ,
727- forceRestart = false ,
728- } = args ?? { } ;
729-
730- if ( this . recordingProcess && ! forceRestart ) {
731- throw new errors . InvalidArgumentError ( 'Screen recording is already in progress. Use forceRestart to start a new recording.' ) ;
732- }
733-
734- if ( this . recordingProcess && forceRestart ) {
735- const oldProc = this . recordingProcess ;
736- this . recordingProcess = undefined ;
737- this . recordingOutputPath = undefined ;
738- oldProc . stdin ?. write ( 'q' ) ;
739- try {
740- await new Promise < void > ( ( resolve ) => {
741- oldProc . on ( 'exit' , ( ) => resolve ( ) ) ;
742- setTimeout ( ( ) => {
743- oldProc . kill ( 'SIGKILL' ) ;
744- resolve ( ) ;
745- } , 3000 ) ;
746- } ) ;
747- } catch {
748- oldProc . kill ( 'SIGKILL' ) ;
749- }
750- }
751-
752- const ffmpegPath = getBundledFfmpegPath ( ) ;
753- if ( ! ffmpegPath ) {
754- throw new errors . UnknownError (
755- 'Screen recording is not available: the bundled ffmpeg is missing. Reinstall the driver.'
756- ) ;
757- }
758-
759- const ffmpegArgs = [
760- '-f' , 'gdigrab' ,
761- '-framerate' , String ( videoFps ) ,
762- '-i' , 'desktop' ,
763- '-t' , String ( timeLimit ) ,
764- '-c:v' , 'libx264' ,
765- '-preset' , 'ultrafast' ,
766- '-y' ,
767727 outputPath,
768- ] ;
769- if ( videoSize ) {
770- const sizeIdx = ffmpegArgs . indexOf ( '-i' ) ;
771- ffmpegArgs . splice ( sizeIdx , 0 , '-video_size' , videoSize ) ;
772- }
728+ timeLimit,
729+ videoFps : fps ,
730+ videoFilter,
731+ preset,
732+ captureCursor,
733+ captureClicks,
734+ audioInput,
735+ forceRestart = true ,
736+ } = args ?? { } ;
773737
774- const proc = spawn ( ffmpegPath , ffmpegArgs , { stdio : [ 'pipe' , 'pipe' , 'pipe' ] } ) ;
775- proc . on ( 'error' , ( err ) => {
776- this . log . error (
777- `Screen recording failed: ${ err . message } . The bundled ffmpeg may be missing or invalid; try reinstalling the driver.`
778- ) ;
738+ if ( this . _screenRecorder ?. isRunning ( ) ) {
739+ this . log . debug ( 'The screen recording is already running' ) ;
740+ if ( ! forceRestart ) {
741+ this . log . debug ( 'Doing nothing' ) ;
742+ return ;
743+ }
744+ this . log . debug ( 'Forcing the active screen recording to stop' ) ;
745+ await this . _screenRecorder . stop ( true ) ;
746+ } else if ( this . _screenRecorder ) {
747+ this . log . debug ( 'Clearing the recent screen recording' ) ;
748+ await this . _screenRecorder . stop ( true ) ;
749+ }
750+ this . _screenRecorder = null ;
751+
752+ const videoPath = outputPath ?? join ( tmpdir ( ) , `novawindows-recording-${ Date . now ( ) } .${ DEFAULT_EXT } ` ) ;
753+ this . _screenRecorder = new ScreenRecorder ( videoPath , this . log , {
754+ fps : fps !== undefined ? parseInt ( String ( fps ) , 10 ) : undefined ,
755+ timeLimit : timeLimit !== undefined ? parseInt ( String ( timeLimit ) , 10 ) : undefined ,
756+ preset,
757+ captureCursor,
758+ captureClicks,
759+ videoFilter,
760+ audioInput,
779761 } ) ;
780- proc . stderr ?. on ( 'data' , ( ) => { /* suppress ffmpeg progress output */ } ) ;
781-
782- this . recordingProcess = proc as ChildProcessWithoutNullStreams ;
783- this . recordingOutputPath = outputPath ;
762+ try {
763+ await this . _screenRecorder . start ( ) ;
764+ } catch ( e ) {
765+ this . _screenRecorder = null ;
766+ throw e ;
767+ }
784768}
785769
786- export async function stopRecordingScreen ( this : NovaWindowsDriver , args ?: { remotePath ?: string } ) : Promise < string > {
787- const { remotePath } = args ?? { } ;
788-
789- if ( ! this . recordingProcess || ! this . recordingOutputPath ) {
790- throw new errors . InvalidArgumentError ( 'No screen recording in progress.' ) ;
770+ export async function stopRecordingScreen ( this : NovaWindowsDriver , args ?: UploadOptions ) : Promise < string > {
771+ if ( ! this . _screenRecorder ) {
772+ this . log . debug ( 'No screen recording has been started. Doing nothing' ) ;
773+ return '' ;
791774 }
792775
793- const proc = this . recordingProcess ;
794- const outputPath = this . recordingOutputPath ;
795- this . recordingProcess = undefined ;
796- this . recordingOutputPath = undefined ;
797-
798- proc . stdin ?. write ( 'q' ) ;
799-
800- await new Promise < void > ( ( resolve ) => {
801- proc . on ( 'exit' , ( ) => resolve ( ) ) ;
802- setTimeout ( ( ) => resolve ( ) , 5000 ) ;
803- } ) ;
804-
805- if ( remotePath ) {
806- // TODO: upload to remotePath; for now return empty per Appium convention
807- try {
808- await unlink ( outputPath ) ;
809- } catch {
810- /* ignore */
811- }
776+ this . log . debug ( 'Retrieving the resulting video data' ) ;
777+ const videoPath = await this . _screenRecorder . stop ( ) ;
778+ if ( ! videoPath ) {
779+ this . log . debug ( 'No video data is found. Returning an empty string' ) ;
812780 return '' ;
813781 }
814782
815- try {
816- const buffer = await readFile ( outputPath ) ;
817- await unlink ( outputPath ) ;
818- return buffer . toString ( 'base64' ) ;
819- } catch ( err ) {
820- throw new errors . UnknownError ( `Failed to read recording: ${ ( err as Error ) . message } ` ) ;
821- }
783+ const { remotePath, ...uploadOpts } = args ?? { } ;
784+ return await uploadRecordedMedia ( videoPath , remotePath , uploadOpts ) ;
822785}
823786
824787export async function deleteFile ( this : NovaWindowsDriver , args : { path : string } ) : Promise < void > {
0 commit comments