perf: ️ Optimize scrcpy parameter conversion performance

This commit is contained in:
viarotel 2024-10-23 16:14:25 +08:00
parent ca79e1b57d
commit 18dcd24e65
3 changed files with 151 additions and 54 deletions

View File

@ -3,12 +3,13 @@ import util from 'node:util'
import { adbPath, scrcpyPath } from '$electron/configs/index.js'
import appStore from '$electron/helpers/store.js'
import { replaceIP, sleep } from '$renderer/utils/index.js'
import commandHelper from '$renderer/utils/command/index.js'
let adbkit
const exec = util.promisify(_exec)
const shell = async (command, { stdout, stderr } = {}) => {
async function shell(command, { stdout, stderr } = {}) {
const spawnPath = appStore.get('common.scrcpyPath') || scrcpyPath
const ADB = appStore.get('common.adbPath') || adbPath
const args = command.split(' ')
@ -58,7 +59,7 @@ const shell = async (command, { stdout, stderr } = {}) => {
})
}
const execShell = async (command) => {
async function execShell(command) {
const spawnPath = appStore.get('common.scrcpyPath') || scrcpyPath
const ADB = appStore.get('common.adbPath') || adbPath
@ -71,7 +72,7 @@ const execShell = async (command) => {
return res
}
const getEncoders = async (serial) => {
async function getEncoders(serial) {
const res = await execShell(`--serial="${serial}" --list-encoders`)
const stdout = res.stdout
@ -97,29 +98,26 @@ const getEncoders = async (serial) => {
return value
}
const mirror = async (
async function mirror(
serial,
{ title, args = '', exec = false, ...options } = {},
) => {
const mirrorShell = exec ? execShell : shell
) {
const currentShell = exec ? execShell : shell
return mirrorShell(
return currentShell(
`--serial="${serial}" --window-title="${title}" ${args}`,
options,
)
}
const record = async (
serial,
{ title, args = '', savePath, ...options } = {},
) => {
async function record(serial, { title, args = '', savePath, ...options } = {}) {
return shell(
`--serial="${serial}" --window-title="${title}" --record="${savePath}" ${args}`,
options,
)
}
const mirrorGroup = async (serial, { openNum = 1, ...options } = {}) => {
async function mirrorGroup(serial, { openNum = 1, ...options } = {}) {
const overlayDisplay
= appStore.get(`scrcpy.${replaceIP(serial)}.--display-overlay`)
|| appStore.get('scrcpy.global.--display-overlay')
@ -170,6 +168,17 @@ const mirrorGroup = async (serial, { openNum = 1, ...options } = {}) => {
return Promise.allSettled(results)
}
async function control(serial, { command, exec = true, ...options } = {}) {
const currentShell = exec ? execShell : shell
const stringCommand = commandHelper.stringify(command)
return currentShell(
`--serial="${serial}" --no-video --no-audio ${stringCommand}`,
options,
)
}
export default (options = {}) => {
adbkit = options.adbkit
@ -180,5 +189,6 @@ export default (options = {}) => {
mirror,
record,
mirrorGroup,
control,
}
}

View File

@ -13,6 +13,8 @@ import {
} from './helpers/index.js'
import model from './model/index.js'
import command from '$/utils/command/index.js'
const { adbPath, scrcpyPath, gnirehtetPath } = window.electron?.configs || {}
export const usePreferenceStore = defineStore({
@ -130,7 +132,7 @@ export const usePreferenceStore = defineStore({
return value
},
getScrcpyArgs(
scrcpyParameter(
scope = this.deviceScope,
{ isRecord = false, isCamera = false, isOtg = false, excludes = [] } = {},
) {
@ -140,55 +142,31 @@ export const usePreferenceStore = defineStore({
return ''
}
const valueList = Object.entries(data).reduce((arr, [key, value]) => {
if (!value && typeof value !== 'number') {
return arr
const params = Object.entries(data).reduce((obj, [key, value]) => {
const shouldExclude
= (!value && typeof value !== 'number')
|| this.excludeKeys.includes(key)
|| (!isRecord && this.recordKeys.includes(key))
|| (!isCamera && this.cameraKeys.includes(key))
|| (!isOtg && this.otgKeys.includes(key))
|| excludes.includes(key)
|| excludes.includes(`${key}=${value}`)
if (shouldExclude) {
return obj
}
if (this.excludeKeys.includes(key)) {
return arr
}
obj[key] = value
if (!isRecord) {
if (this.recordKeys.includes(key)) {
return arr
}
}
return obj
}, {})
if (!isCamera) {
if (this.cameraKeys.includes(key)) {
return arr
}
}
if (!isOtg) {
if (this.otgKeys.includes(key)) {
return arr
}
}
if (excludes.includes(key) || excludes.includes(`${key}=${value}`)) {
return arr
}
if (typeof value === 'boolean') {
arr.push(key)
}
else {
arr.push(`${key}="${value}"`)
}
return arr
}, [])
let value = command.stringify(params)
if (data.scrcpyAppend) {
valueList.push(...data.scrcpyAppend.split(' '))
value += ` ${data.scrcpyAppend}`
}
const value = valueList.join(' ')
// console.log('value', value)
return value
},
getModel(path) {

109
src/utils/command/index.js Normal file
View File

@ -0,0 +1,109 @@
/**
* Convert object parameters to command line arguments string
* @param {Object} options - The options object containing parameters
* @returns {string} The formatted command line arguments string
*/
function stringify(options) {
if (!options || typeof options !== 'object' || Array.isArray(options)) {
console.warn('Options must be a plain object')
return ''
}
const args = []
// Helper function to format parameter names
const formatParamName = (name) => {
// 验证参数名称的合法性
if (typeof name !== 'string' || !name.length) {
throw new TypeError('Parameter name must be a non-empty string')
}
if (name.startsWith('-')) {
return name
}
return name.length === 1
? `-${name}`
: `--${name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}`
}
// Helper function to format values based on their types
const formatValue = (value) => {
if (value === null || value === undefined) {
throw new TypeError('Value cannot be null or undefined')
}
if (typeof value === 'string') {
// 处理空字符串
if (!value.length) {
return '""'
}
// 转义引号并在需要时添加引号
const needsQuotes = /[\s"']/.test(value)
return needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value
}
if (typeof value === 'number') {
if (!Number.isFinite(value)) {
throw new TypeError('Number values must be finite')
}
return value.toString()
}
if (typeof value === 'boolean') {
return '' // 布尔值不需要返回值
}
if (Array.isArray(value)) {
return formatValue(value.join(','))
}
throw new TypeError(`Unsupported value type: ${typeof value}`)
}
// Process each option
for (const [key, value] of Object.entries(options)) {
// Skip null or undefined values
if ([null, undefined, false].includes(value)) {
continue
}
const paramName = formatParamName(key)
// Handle boolean flags
if (typeof value === 'boolean') {
if (value) {
args.push(paramName)
}
continue
}
// Handle array values
if (Array.isArray(value)) {
if (value.length === 0) {
continue // 跳过空数组
}
value.forEach((item) => {
if (item !== null && item !== undefined) {
const formattedValue = formatValue(item)
if (formattedValue) {
args.push(`${paramName} ${formattedValue}`)
}
}
})
continue
}
// Handle regular key-value pairs
const formattedValue = formatValue(value)
if (formattedValue) {
args.push(`${paramName}=${formattedValue}`)
}
}
return args.join(' ')
}
export { stringify }
export default { stringify }