feat: 🚀 增加了常用的 Scrcpy 高级配置

This commit is contained in:
viarotel 2023-09-18 18:22:51 +08:00
parent 0695aa3377
commit ac52df2add
12 changed files with 358 additions and 26 deletions

View File

@ -2,5 +2,5 @@ out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
jsconfig.json
jsconfig.*.json

View File

@ -8,7 +8,7 @@
:name="item.prop"
lazy
>
<component :is="item.prop" />
<component :is="item.prop" ref="component" />
</el-tab-pane>
</el-tabs>
</div>
@ -39,11 +39,7 @@ export default {
}
},
mounted() {},
methods: {
getDevices() {
window.adbkit.createClient()
},
},
methods: {},
}
</script>

View File

@ -0,0 +1,15 @@
export default () => {
// "[server] INFO: List of audio encoders:"
// "--audio-codec=opus --audio-encoder='c2.android.opus.encoder'"
// "--audio-codec=aac --audio-encoder='c2.android.aac.encoder'"
// "--audio-codec=aac --audio-encoder='OMX.google.aac.encoder'"
return [
{
label: '禁用音频',
field: '--no-audio',
type: 'switch',
value: false,
placeholder: '开启后将禁用音频功能',
},
]
}

View File

@ -0,0 +1,18 @@
export default () => {
return [
{
label: '保持清醒',
type: 'switch',
field: '--stay-awake',
value: false,
placeholder: '开启以防止设备进入睡眠状态(仅有线方式连接时有效)',
},
{
label: '关闭屏幕',
type: 'switch',
field: '--turn-screen-off',
value: false,
placeholder: '开启后连接镜像时将自动关闭设备屏幕',
},
]
}

View File

@ -0,0 +1,4 @@
export { default as video } from './video/index.js'
export { default as device } from './device/index.js'
export { default as window } from './window/index.js'
export { default as audio } from './audio/index.js'

View File

@ -0,0 +1,94 @@
export default () => {
return [
{
label: '分辨率',
type: 'input.number',
field: '--max-size',
value: '',
placeholder: '默认值为设备分辨率,如 1920',
},
{
label: '比特率',
type: 'input',
field: '--video-bit-rate',
value: '',
placeholder: '默认值为 4M等同于 4000000',
},
{
label: '刷新率',
type: 'input.number',
field: '--max-fps',
value: '',
placeholder: '默认值为 60',
},
{
label: '屏幕旋转',
type: 'select',
field: '--rotation=0',
value: '',
placeholder: '默认值为设备屏幕旋转角度',
options: [
{ label: '0°', value: '0' },
{ label: '-90°', value: '1' },
{ label: '180°', value: '2' },
{ label: '90°', value: '3' },
],
},
{
label: '解码器',
type: 'select',
field: '--video-codec',
value: '',
placeholder: '解码器默认值为 h264',
options: [
{
label: 'h264',
value: 'h264',
},
{
label: 'h265',
value: 'h265',
},
{
label: 'av1',
value: 'av1',
},
],
},
{
label: '编码器',
type: 'select',
field: '--video-encoder',
value: '',
placeholder: '编码器默认值为 h264',
// "[server] INFO: List of video encoders:"
// "--video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'"
// "--video-codec=h264 --video-encoder='c2.android.avc.encoder'"
// "--video-codec=h264 --video-encoder='OMX.google.h264.encoder'"
// "--video-codec=h265 --video-encoder='OMX.qcom.video.encoder.hevc'"
// "--video-codec=h265 --video-encoder='c2.android.hevc.encoder'"
options: [
{
label: 'OMX.qcom.video.encoder.avc',
value: 'OMX.qcom.video.encoder.avc',
},
{
label: 'c2.android.avc.encoder',
value: 'c2.android.avc.encoder',
},
{
label: 'OMX.google.h264.encoder',
value: 'OMX.google.h264.encoder',
},
{
label: 'OMX.qcom.video.encoder.hevc',
value: 'OMX.qcom.video.encoder.hevc',
},
{
label: 'c2.android.hevc.encoder',
value: 'c2.android.hevc.encoder',
},
],
},
]
}

View File

@ -0,0 +1,18 @@
export default () => {
return [
{
label: '无边框模式',
field: '--window-borderless',
type: 'switch',
value: false,
placeholder: '开启无边框模式',
},
{
label: '全屏模式',
field: '--fullscreen',
type: 'switch',
value: false,
placeholder: '开启全屏模式',
},
]
}

View File

@ -1,11 +1,148 @@
<template>
<div class="grid gap-6 pr-2">
<el-card v-for="(item, index) of scrcpyModel" :key="index" shadow="hover" class="">
<template #header>
<div class="flex items-center">
<div class="flex-1 w-0 truncate pl-2 text-base">
{{ item.label }}
</div>
<div class="flex-none pl-4">
<el-button type="primary" text @click="handleReset(item.type)">
恢复默认值
</el-button>
</div>
</div>
</template>
<div class="">
[WIP]
<el-form ref="elForm" :model="scrcpyForm" label-width="120px" class="pr-8 pt-4">
<el-row :gutter="20">
<el-col
v-for="(item_1, index_1) of getScrcpyConfig(item.type)"
:key="index_1"
:span="12"
:offset="0"
>
<el-form-item :label="item_1.label" :prop="item_1.field">
<el-input
v-if="item_1.type === 'input'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
class="!w-full"
:placeholder="item_1.placeholder"
></el-input>
<el-input
v-if="item_1.type === 'input.number'"
v-bind="item_1.props || {}"
v-model.number="scrcpyForm[item_1.field]"
class="!w-full"
:placeholder="item_1.placeholder"
></el-input>
<el-switch
v-if="item_1.type === 'switch'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
class="!w-full"
:placeholder="item_1.placeholder"
></el-switch>
<el-select
v-if="item_1.type === 'select'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
:placeholder="item_1.placeholder"
class="!w-full"
>
<el-option
v-for="(item_2, index_2) in item_1.options"
:key="index_2"
:label="item_2.label"
:value="item_2.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
</div>
</template>
<script>
export default {}
import storage from '@renderer/utils/storages'
import * as scrcpyConfigs from './configs/index.js'
export default {
emits: ['change'],
data() {
const scrcpyCache = storage.get('scrcpyCache') || {}
// console.log('scrcpyCache', scrcpyCache)
return {
scrcpyModel: [
{
label: '显示配置',
type: 'video',
},
{
label: '设备控制',
type: 'device',
},
{
label: '音频控制',
type: 'audio',
},
{
label: '窗口控制',
type: 'window',
},
],
scrcpyForm: { ...this.getDefaultValues(), ...scrcpyCache },
}
},
watch: {
scrcpyForm: {
handler() {
storage.set('scrcpyCache', this.scrcpyForm)
this.$message.success('保存配置成功,将在下一次控制设备时生效')
},
deep: true,
},
},
methods: {
getScrcpyConfig(type) {
const value = scrcpyConfigs[type]()
return value
},
getDefaultValues(type) {
const model = []
if (type) {
model.push(...this.getScrcpyConfig(type))
}
else {
// console.log('scrcpyConfigs', scrcpyConfigs)
const values = Object.values(scrcpyConfigs)
model.push(...values.flatMap(handler => handler()))
}
const value = model.reduce((obj, item) => {
const { field, value } = item
obj[field] = value
return obj
}, {})
return value
},
handleReset(type) {
this.scrcpyForm = { ...this.scrcpyForm, ...this.getDefaultValues(type) }
storage.set('scrcpyCache', this.scrcpyForm)
},
},
}
</script>
<style></style>
<style scoped lang="postcss">
:deep(.el-card) {
--el-card-padding: 8px;
}
</style>

View File

@ -1,19 +1,26 @@
<template>
<div class="h-full flex flex-col">
<div class="flex items-center flex-none space-x-2">
<el-input v-model="formData.host" placeholder="192.168.0.1" class="w-72">
<el-input v-model="formData.host" placeholder="192.168.0.1" class="w-86" clearable>
<template #prepend>
无线连接设备
无线连接
</template>
</el-input>
<div class="text-gray-500 text-sm">
:
</div>
<el-input v-model.number="formData.port" type="number" placeholder="5555" class="w-24">
<el-input
v-model.number="formData.port"
type="number"
placeholder="5555"
:min="0"
clearable
class="w-32"
>
</el-input>
<el-button type="primary" :loading="connectLoading" @click="handleConnect">
开始连接
连接设备
</el-button>
<el-button type="primary" :loading="loading" @click="getDeviceData">
刷新设备
@ -34,8 +41,8 @@
<template #empty>
<el-empty description="设备列表为空" />
</template>
<el-table-column prop="id" label="设备 ID" />
<el-table-column prop="name" label="设备名称">
<el-table-column prop="id" label="设备 ID" show-overflow-tooltip />
<el-table-column prop="name" label="设备名称" show-overflow-tooltip>
<template #default="{ row }">
<div class="flex items-center">
<el-tooltip
@ -51,9 +58,9 @@
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center">
<el-table-column label="操作" width="350" align="center">
<template #default="{ row }">
<el-button type="primary" :loading="row.$loading" @click="handleStart(row)">
<el-button type="primary" :loading="row.$loading" @click="handleMirror(row)">
{{ row.$loading ? '镜像中' : '开始镜像' }}
</el-button>
<el-button type="default" @click="handleScreenUp(row)">
@ -137,16 +144,37 @@ export default {
}
row.$stopLoading = false
},
async handleStart(row) {
async handleMirror(row) {
row.$loading = true
try {
await this.$scrcpy.shell(`--serial=${row.id}`)
await this.$scrcpy.shell(`--serial=${row.id} ${this.addScrcpyConfigs()}`)
}
catch (error) {
this.$message.warning(error.message)
}
row.$loading = false
},
addScrcpyConfigs() {
const configs = storage.get('scrcpyCache') || {}
const value = Object.entries(configs)
.reduce((arr, [key, value]) => {
if (!value) {
return arr
}
if (typeof value === 'boolean') {
arr.push(key)
}
else {
arr.push(`${key}=${value}`)
}
return arr
}, [])
.join(' ')
console.log('addScrcpyConfigs.value', value)
return value
},
async getDeviceData() {
this.loading = true
await sleep()

View File

@ -13,9 +13,9 @@
--el-color-primary-light-9: rgba(var(--color-primary-50), 1);
/* 字体大小 */
--el-font-size-base: 12px;
--el-font-size-small: 14px;
--el-font-size-large: 16px;
/* --el-font-size-base: 14px;
--el-font-size-small: 16px;
--el-font-size-large: 18px; */
}
.el-tabs-flex {
@ -30,6 +30,6 @@
}
.el-tab-pane {
@apply h-full;
@apply h-full overflow-auto;
}
}

View File

@ -0,0 +1,21 @@
html {
font-size: 14px !important;
}
/* 自定义滚动条的外观 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background-color: theme('colors.gray.100');
}
::-webkit-scrollbar-thumb {
background-color: theme('colors.gray.300');
border-radius: 9999px;
}
::-webkit-scrollbar-thumb:hover {
background-color: theme('colors.gray.500');
}

View File

@ -1 +1,2 @@
import '@viarotel-org/design/styles/resets'
import './desktop.css'