mirror of
https://github.com/mobile-next/mobile-mcp.git
synced 2026-10-02 01:05:27 +08:00
fix: wait for mobilecli to confirm screen recording started
mobile_start_screen_recording spawned mobilecli with --silent and stdio ignored, then reported success immediately. A device-not-found or any other startup failure was invisible until stop_screen_recording found no file. Now capture mobilecli output and resolve only after it prints "Screen recording has started". Reject on spawn error, early exit (with mobilecli's own message), or a 30s startup timeout. Salvaged from #383. Refs #359 Co-authored-by: Cosm1cAC <cosm1cac@github.com>
This commit is contained in:
@@ -67,6 +67,7 @@ export interface MobilecliDevicesResponse {
|
||||
|
||||
const TIMEOUT = 30000;
|
||||
const MAX_BUFFER_SIZE = 1024 * 1024 * 8;
|
||||
const SCREEN_RECORDING_STARTED = "Screen recording has started";
|
||||
const DEFAULT_ALLOCATE_TIMEOUT_SECONDS = 900; // matches mobilecli's own "remote allocate --wait" default
|
||||
|
||||
export class Mobilecli {
|
||||
@@ -98,6 +99,60 @@ export class Mobilecli {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns `mobilecli screenrecord` and resolves only once mobilecli reports that
|
||||
* recording has started. Rejects on spawn error, early exit, or startup timeout.
|
||||
*/
|
||||
public startScreenRecording(args: string[]): Promise<ChildProcess> {
|
||||
const child = this.spawnCommand(args, true);
|
||||
const stdout = child.stdout!;
|
||||
const stderr = child.stderr!;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let output = "";
|
||||
let settled = false;
|
||||
|
||||
const finish = () => {
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
stdout.off("data", onData);
|
||||
stderr.off("data", onData);
|
||||
stdout.resume();
|
||||
stderr.resume();
|
||||
};
|
||||
|
||||
const fail = (error: Error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
|
||||
finish();
|
||||
reject(error);
|
||||
};
|
||||
|
||||
const onData = (chunk: Buffer) => {
|
||||
output = (output + chunk.toString()).slice(-MAX_BUFFER_SIZE);
|
||||
if (!settled && output.includes(SCREEN_RECORDING_STARTED)) {
|
||||
finish();
|
||||
resolve(child);
|
||||
}
|
||||
};
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
child.kill();
|
||||
fail(new Error("Timed out waiting for mobilecli to start screen recording"));
|
||||
}, TIMEOUT);
|
||||
|
||||
stdout.on("data", onData);
|
||||
stderr.on("data", onData);
|
||||
child.once("error", fail);
|
||||
child.once("exit", (code, signal) => {
|
||||
const status = signal ? `signal ${signal}` : `code ${code ?? "unknown"}`;
|
||||
fail(new Error(output.trim() || `mobilecli exited before screen recording started (${status})`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public executeCommandBuffer(args: string[]): Buffer {
|
||||
const path = this.getPath();
|
||||
return execFileSync(path, args, {
|
||||
|
||||
+2
-2
@@ -999,12 +999,12 @@ export const createMcpServer = (): McpServer => {
|
||||
|
||||
const outputPath = output || path.join(os.tmpdir(), `screen-recording-${Date.now()}.mp4`);
|
||||
|
||||
const args = ["screenrecord", "--device", device, "--output", outputPath, "--silent"];
|
||||
const args = ["screenrecord", "--device", device, "--output", outputPath];
|
||||
if (timeLimit !== undefined) {
|
||||
args.push("--time-limit", String(timeLimit));
|
||||
}
|
||||
|
||||
const child = mobilecli.spawnCommand(args);
|
||||
const child = await mobilecli.startScreenRecording(args);
|
||||
|
||||
const cleanup = () => {
|
||||
activeRecordings.delete(device);
|
||||
|
||||
@@ -116,4 +116,37 @@ test.describe("mobilecli", () => {
|
||||
expect(calls[0].args).toEqual(["devices", "--include-offline", "--platform", "android", "--type", "emulator"]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("startScreenRecording", () => {
|
||||
test.beforeEach(() => {
|
||||
process.env.MOBILECLI_PATH = process.execPath;
|
||||
});
|
||||
|
||||
test.afterEach(() => {
|
||||
delete process.env.MOBILECLI_PATH;
|
||||
});
|
||||
|
||||
test("rejects when mobilecli exits before reporting that recording started", async () => {
|
||||
const mobilecli = new Mobilecli();
|
||||
|
||||
await expect(mobilecli.startScreenRecording([
|
||||
"-e",
|
||||
"process.stderr.write('device not found\\n'); process.exit(1);",
|
||||
])).rejects.toThrow("device not found");
|
||||
});
|
||||
|
||||
test("resolves only after mobilecli reports that recording started", async () => {
|
||||
const mobilecli = new Mobilecli();
|
||||
const child = await mobilecli.startScreenRecording([
|
||||
"-e",
|
||||
"process.stderr.write('Screen recording has started\\n'); setTimeout(() => {}, 5000);",
|
||||
]);
|
||||
|
||||
try {
|
||||
expect(child.exitCode).toBeNull();
|
||||
} finally {
|
||||
child.kill();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user