feat(cdp): Phase 2 - real-time UI mirroring via screencast + remote input

This commit is contained in:
2026-03-07 20:41:53 +09:00
parent 9234be33db
commit 4b855c9e57
6 changed files with 526 additions and 1 deletions

View File

@@ -25,6 +25,8 @@ class CDPClient {
this.pollInterval = null;
this.onChatUpdate = null; // callback(html)
this.onDisconnect = null; // callback()
this.onScreencastFrame = null; // callback({data, metadata})
this.screencastRunning = false;
}
/**
@@ -268,7 +270,7 @@ class CDPClient {
}
/**
* 스크린샷 (Phase 2 미리보기용)
* 스크린샷 (미리보기용)
*/
async captureScreenshot() {
if (!this.connected || !this.client) return null;
@@ -280,6 +282,118 @@ class CDPClient {
return null;
}
}
// ─── Phase 2: Screencast & Remote Input ────────────────
/**
* 실시간 화면 스트리밍 시작
* Page.startScreencast로 프레임을 수신하고 onScreencastFrame 콜백으로 전달
*/
async startScreencast(options = {}) {
if (!this.connected || !this.client) return false;
if (this.screencastRunning) return true;
const {
format = 'jpeg',
quality = 60,
maxWidth = 1280,
maxHeight = 900,
everyNthFrame = 2,
} = options;
try {
// 프레임 이벤트 리스너 등록
this.client.Page.screencastFrame(async (params) => {
// ACK — 이걸 보내야 다음 프레임이 옴
try {
await this.client.Page.screencastFrameAck({ sessionId: params.sessionId });
} catch { /* ignore */ }
if (this.onScreencastFrame) {
this.onScreencastFrame({
data: params.data, // base64 JPEG
metadata: params.metadata, // {offsetTop, pageScaleFactor, deviceWidth, deviceHeight, ...}
sessionId: params.sessionId,
});
}
});
await this.client.Page.startScreencast({
format,
quality,
maxWidth,
maxHeight,
everyNthFrame,
});
this.screencastRunning = true;
console.log(`[CDP] Screencast 시작 (${maxWidth}x${maxHeight}, q${quality}, every ${everyNthFrame}th frame)`);
return true;
} catch (err) {
console.error('[CDP] Screencast 시작 오류:', err.message);
return false;
}
}
/**
* 실시간 화면 스트리밍 중지
*/
async stopScreencast() {
if (!this.connected || !this.client || !this.screencastRunning) return;
try {
await this.client.Page.stopScreencast();
} catch { /* ignore */ }
this.screencastRunning = false;
this.onScreencastFrame = null;
console.log('[CDP] Screencast 중지');
}
/**
* 원격 입력 이벤트 디스패치
* @param {Object} evt — { type: 'mouse'|'key'|'scroll', ... }
*/
async dispatchInput(evt) {
if (!this.connected || !this.client) return;
try {
switch (evt.type) {
case 'mouse':
await this.client.Input.dispatchMouseEvent({
type: evt.action, // mousePressed, mouseReleased, mouseMoved
x: evt.x,
y: evt.y,
button: evt.button || 'left',
clickCount: evt.clickCount || 1,
modifiers: evt.modifiers || 0,
});
break;
case 'scroll':
await this.client.Input.dispatchMouseEvent({
type: 'mouseWheel',
x: evt.x,
y: evt.y,
deltaX: evt.deltaX || 0,
deltaY: evt.deltaY || 0,
});
break;
case 'key':
await this.client.Input.dispatchKeyEvent({
type: evt.action, // keyDown, keyUp, char
key: evt.key,
code: evt.code || '',
text: evt.text || '',
nativeVirtualKeyCode: evt.keyCode || 0,
windowsVirtualKeyCode: evt.keyCode || 0,
modifiers: evt.modifiers || 0,
});
break;
}
} catch (err) {
// 입력 이벤트 오류는 빈번할 수 있으므로 조용히 처리
}
}
}
module.exports = CDPClient;