import { Logger } from "@perf-profiler/logger"; import { executeAsync, executeCommand } from "./shell"; import { ChildProcess } from "child_process"; import { waitFor } from "../utils/waitFor"; const RECORDING_FOLDER = "/data/local/tmp/"; async function isProcessRunning(pid: number): Promise { try { const result = executeCommand(`adb shell ps -p ${pid}`).toString(); return result.includes(pid.toString()); } catch (error) { return false; } } export class ScreenRecorder { private fileName; private process?: ChildProcess = undefined; private recordingStartTime = 0; constructor(file: string) { this.fileName = file; } async startRecording({ bitRate = 8000000, size, }: { bitRate?: number; size?: string; } = {}): Promise { const filePath = `${RECORDING_FOLDER}${this.fileName}`; this.process = executeAsync( `adb shell screenrecord ${filePath} --bit-rate ${bitRate} ${ size ? `--size ${size}` : "" } --verbose` ); await new Promise((resolve) => { this.process?.stdout?.on("data", (data) => { if (data.toString().includes("Content area is")) { resolve(); } }); }); Logger.info("Recording started"); this.recordingStartTime = performance.now(); } async stopRecording(): Promise { if (!this.process) return; // Wait an arbitrary 5 seconds to make sure the recording captures everything we want // Otherwise, sometimes we miss the end of the video await new Promise((resolve) => setTimeout(resolve, 5000)); const pid = this.process.pid; this.process.kill("SIGINT"); this.process = undefined; // Wait for the process to stop running await waitFor(async () => pid && (await isProcessRunning(pid)), { timeout: 10000, checkInterval: 100, errorMessage: "ERROR: PID still running after timeout, it should have been killed before", }); // Wait an arbitrary time to ensure we don't end up with a corrupted video await new Promise((resolve) => setTimeout(resolve, 500)); Logger.info("Recording stopped"); } getRecordingStartTime(): number { return this.recordingStartTime; } async pullRecording(destinationPath: string): Promise { executeCommand(`adb pull ${RECORDING_FOLDER}${this.fileName} ${destinationPath}`); executeCommand(`adb shell rm ${RECORDING_FOLDER}${this.fileName}`); Logger.info(`Recording saved to ${destinationPath}/${this.fileName}`); } }