UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

66 lines (57 loc) • 3.2 kB
// Standalone scrcpy smoke test — proves the exact @yume-chan calls the // Android backend's startScrcpy needs: connect → push server → H.264 video // stream → inject a tap. Run against a booted emulator on this box. import { AdbServerClient, Adb } from '@yume-chan/adb'; import { AdbServerNodeTcpConnector } from '@yume-chan/adb-server-node-tcp'; import { AdbScrcpyClient, AdbScrcpyOptions2_1 } from '@yume-chan/adb-scrcpy'; import { DefaultServerPath, AndroidMotionEventAction } from '@yume-chan/scrcpy'; import { ReadableStream } from '@yume-chan/stream-extra'; import { readFileSync } from 'node:fs'; const log = (...a) => console.log('[smoke]', ...a); const connector = new AdbServerNodeTcpConnector({ host: '127.0.0.1', port: 5037 }); const server = new AdbServerClient(connector); const devices = await server.getDevices(); log('devices:', devices.map((d) => d.serial)); const device = devices.find((d) => d.serial === 'emulator-5554') ?? devices[0]; if (!device) throw new Error('no adb device'); const transport = await server.createTransport(device); const adb = new Adb(transport); log('connected to', device.serial); const jar = readFileSync(new URL('./scrcpy-server', import.meta.url)); await AdbScrcpyClient.pushServer( adb, new ReadableStream({ start(c) { c.enqueue(new Uint8Array(jar)); c.close(); } }), ); log('pushed scrcpy-server', jar.length, 'bytes'); const options = new AdbScrcpyOptions2_1({ video: true, audio: false, control: true, maxSize: 1024, maxFps: 15 }); const client = await AdbScrcpyClient.start(adb, DefaultServerPath, options); log('scrcpy started'); const video = await client.videoStream; log('video metadata:', JSON.stringify(video.metadata), 'size', video.width + 'x' + video.height); let dataPackets = 0, configPackets = 0, keyframes = 0, totalBytes = 0; const reader = video.stream.getReader(); const deadline = Date.now() + 8000; while (Date.now() < deadline && dataPackets < 40) { const { done, value } = await reader.read(); if (done) break; if (value.type === 'configuration') { configPackets++; continue; } dataPackets++; totalBytes += value.data.byteLength; if (value.keyframe) keyframes++; if (dataPackets <= 3) log(` frame#${dataPackets} keyframe=${!!value.keyframe} bytes=${value.data.byteLength}`); } reader.releaseLock(); log(`VIDEO: ${dataPackets} data packets, ${configPackets} config, ${keyframes} keyframes, ${totalBytes} bytes total`); // Inject a tap at screen center (down + up). const w = video.width, h = video.height, cx = Math.floor(w / 2), cy = Math.floor(h / 2); const controller = client.controller; if (!controller) throw new Error('no controller (control disabled?)'); const touch = (action, pressure) => controller.injectTouch({ pointerId: 0n, action, pointerX: cx, pointerY: cy, screenWidth: w, screenHeight: h, pressure, actionButton: 0, buttons: 0, }); await touch(AndroidMotionEventAction.Down, 1); await touch(AndroidMotionEventAction.Up, 0); log(`TAP injected at ${cx},${cy} on ${w}x${h}`); log(dataPackets > 0 && keyframes > 0 ? 'SMOKE PASS ✅' : 'SMOKE FAIL ❌ (no frames/keyframe)'); await client.close?.(); process.exit(dataPackets > 0 && keyframes > 0 ? 0 : 1);