mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-25 04:16:36 +08:00
feat: 支持硬件编码能力测试,otg 自检修改为需要手动执行
This commit is contained in:
@@ -177,6 +177,31 @@ export interface StreamConstraintsResponse {
|
||||
current_mode: string
|
||||
}
|
||||
|
||||
export interface VideoEncoderSelfCheckCodec {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface VideoEncoderSelfCheckCell {
|
||||
codec_id: string
|
||||
ok: boolean
|
||||
elapsed_ms?: number | null
|
||||
}
|
||||
|
||||
export interface VideoEncoderSelfCheckRow {
|
||||
resolution_id: string
|
||||
resolution_label: string
|
||||
width: number
|
||||
height: number
|
||||
cells: VideoEncoderSelfCheckCell[]
|
||||
}
|
||||
|
||||
export interface VideoEncoderSelfCheckResponse {
|
||||
current_hardware_encoder: string
|
||||
codecs: VideoEncoderSelfCheckCodec[]
|
||||
rows: VideoEncoderSelfCheckRow[]
|
||||
}
|
||||
|
||||
export const streamApi = {
|
||||
status: () =>
|
||||
request<{
|
||||
@@ -217,6 +242,9 @@ export const streamApi = {
|
||||
getConstraints: () =>
|
||||
request<StreamConstraintsResponse>('/stream/constraints'),
|
||||
|
||||
encoderSelfCheck: () =>
|
||||
request<VideoEncoderSelfCheckResponse>('/video/encoder/self-check'),
|
||||
|
||||
setBitratePreset: (bitrate_preset: import('@/types/generated').BitratePreset) =>
|
||||
request<{ success: boolean; message?: string }>('/stream/bitrate', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -757,6 +757,15 @@ export default {
|
||||
udc_speed: 'Device may not be fully enumerated; try reconnecting USB',
|
||||
},
|
||||
},
|
||||
encoderSelfCheck: {
|
||||
title: 'Hardware Encoding Capability Test',
|
||||
desc: 'Test hardware encoding capability across 720p, 1080p, 2K, and 4K',
|
||||
run: 'Start Test',
|
||||
failed: 'Failed to run hardware encoding capability test',
|
||||
resolution: 'Resolution',
|
||||
currentHardwareEncoder: 'Current Hardware Encoder',
|
||||
none: 'None',
|
||||
},
|
||||
// WebRTC / ICE
|
||||
webrtcSettings: 'WebRTC Settings',
|
||||
webrtcSettingsDesc: 'Configure STUN/TURN servers for NAT traversal',
|
||||
|
||||
@@ -757,6 +757,15 @@ export default {
|
||||
udc_speed: '设备可能未完成枚举,可尝试重插 USB',
|
||||
},
|
||||
},
|
||||
encoderSelfCheck: {
|
||||
title: '硬件编码能力测试',
|
||||
desc: '按 720p、1080p、2K、4K 测试硬件编码能力',
|
||||
run: '开始测试',
|
||||
failed: '执行硬件编码能力测试失败',
|
||||
resolution: '分辨率',
|
||||
currentHardwareEncoder: '当前硬件编码器',
|
||||
none: '无',
|
||||
},
|
||||
// WebRTC / ICE
|
||||
webrtcSettings: 'WebRTC 设置',
|
||||
webrtcSettingsDesc: '配置 STUN/TURN 服务器以实现 NAT 穿透',
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
type UpdateOverviewResponse,
|
||||
type UpdateStatusResponse,
|
||||
type UpdateChannel,
|
||||
type VideoEncoderSelfCheckResponse,
|
||||
} from '@/api'
|
||||
import type {
|
||||
ExtensionsStatus,
|
||||
@@ -539,6 +540,64 @@ async function onRunOtgSelfCheckClick() {
|
||||
await runOtgSelfCheck()
|
||||
}
|
||||
|
||||
type VideoEncoderSelfCheckCell = VideoEncoderSelfCheckResponse['rows'][number]['cells'][number]
|
||||
type VideoEncoderSelfCheckRow = VideoEncoderSelfCheckResponse['rows'][number]
|
||||
|
||||
const videoEncoderSelfCheckLoading = ref(false)
|
||||
const videoEncoderSelfCheckResult = ref<VideoEncoderSelfCheckResponse | null>(null)
|
||||
const videoEncoderSelfCheckError = ref('')
|
||||
const videoEncoderRunButtonPressed = ref(false)
|
||||
|
||||
function videoEncoderCell(row: VideoEncoderSelfCheckRow, codecId: string): VideoEncoderSelfCheckCell | undefined {
|
||||
return row.cells.find(cell => cell.codec_id === codecId)
|
||||
}
|
||||
|
||||
const currentHardwareEncoderText = computed(() =>
|
||||
videoEncoderSelfCheckResult.value?.current_hardware_encoder === 'None'
|
||||
? t('settings.encoderSelfCheck.none')
|
||||
: (videoEncoderSelfCheckResult.value?.current_hardware_encoder || t('settings.encoderSelfCheck.none'))
|
||||
)
|
||||
|
||||
function videoEncoderCodecLabel(codecId: string, codecName: string): string {
|
||||
return codecId === 'h265' ? 'H.265' : codecName
|
||||
}
|
||||
|
||||
function videoEncoderCellClass(ok: boolean | undefined): string {
|
||||
return ok ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'
|
||||
}
|
||||
|
||||
function videoEncoderCellSymbol(ok: boolean | undefined): string {
|
||||
return ok ? '✓' : '✗'
|
||||
}
|
||||
|
||||
function videoEncoderCellTime(cell: VideoEncoderSelfCheckCell | undefined): string {
|
||||
if (!cell || typeof cell.elapsed_ms !== 'number') return '-'
|
||||
return `${cell.elapsed_ms}ms`
|
||||
}
|
||||
|
||||
async function runVideoEncoderSelfCheck() {
|
||||
videoEncoderSelfCheckLoading.value = true
|
||||
videoEncoderSelfCheckError.value = ''
|
||||
try {
|
||||
videoEncoderSelfCheckResult.value = await streamApi.encoderSelfCheck()
|
||||
} catch (e) {
|
||||
console.error('Failed to run encoder self-check:', e)
|
||||
videoEncoderSelfCheckError.value = t('settings.encoderSelfCheck.failed')
|
||||
} finally {
|
||||
videoEncoderSelfCheckLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onRunVideoEncoderSelfCheckClick() {
|
||||
if (!videoEncoderSelfCheckLoading.value) {
|
||||
videoEncoderRunButtonPressed.value = true
|
||||
window.setTimeout(() => {
|
||||
videoEncoderRunButtonPressed.value = false
|
||||
}, 160)
|
||||
}
|
||||
await runVideoEncoderSelfCheck()
|
||||
}
|
||||
|
||||
function alignHidProfileForLowEndpoint() {
|
||||
if (hidProfileAligned.value) return
|
||||
if (!configLoaded.value || !devicesLoaded.value) return
|
||||
@@ -1781,16 +1840,15 @@ onMounted(async () => {
|
||||
if (updateRunning.value) {
|
||||
startUpdatePolling()
|
||||
}
|
||||
|
||||
await runOtgSelfCheck()
|
||||
})
|
||||
|
||||
watch(updateChannel, async () => {
|
||||
await loadUpdateOverview()
|
||||
})
|
||||
|
||||
watch(() => config.value.hid_backend, async () => {
|
||||
await runOtgSelfCheck()
|
||||
watch(() => config.value.hid_backend, () => {
|
||||
otgSelfCheckResult.value = null
|
||||
otgSelfCheckError.value = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2364,6 +2422,86 @@ watch(() => config.value.hid_backend, async () => {
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="flex flex-row items-start justify-between space-y-0">
|
||||
<div class="space-y-1.5">
|
||||
<CardTitle>{{ t('settings.encoderSelfCheck.title') }}</CardTitle>
|
||||
<CardDescription>{{ t('settings.encoderSelfCheck.desc') }}</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="videoEncoderSelfCheckLoading"
|
||||
:class="[
|
||||
'transition-all duration-150 active:scale-95 active:brightness-95',
|
||||
videoEncoderRunButtonPressed ? 'scale-95 brightness-95' : ''
|
||||
]"
|
||||
@click="onRunVideoEncoderSelfCheckClick"
|
||||
>
|
||||
<RefreshCw class="h-4 w-4 mr-2" :class="{ 'animate-spin': videoEncoderSelfCheckLoading }" />
|
||||
{{ t('settings.encoderSelfCheck.run') }}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-3">
|
||||
<p v-if="videoEncoderSelfCheckError" class="text-xs text-red-600 dark:text-red-400">
|
||||
{{ videoEncoderSelfCheckError }}
|
||||
</p>
|
||||
|
||||
<template v-if="videoEncoderSelfCheckResult">
|
||||
<div class="text-sm">
|
||||
{{ t('settings.encoderSelfCheck.currentHardwareEncoder') }}:{{ currentHardwareEncoderText }}
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border bg-card">
|
||||
<table class="w-full table-fixed text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-2 py-3 text-left font-medium w-[18%]">{{ t('settings.encoderSelfCheck.resolution') }}</th>
|
||||
<th
|
||||
v-for="codec in videoEncoderSelfCheckResult.codecs"
|
||||
:key="codec.id"
|
||||
class="px-2 py-3 text-center font-medium w-[20.5%]"
|
||||
>
|
||||
{{ videoEncoderCodecLabel(codec.id, codec.name) }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in videoEncoderSelfCheckResult.rows"
|
||||
:key="row.resolution_id"
|
||||
>
|
||||
<td class="px-2 py-3 align-middle">
|
||||
<div class="font-medium">{{ row.resolution_label }}</div>
|
||||
</td>
|
||||
<td
|
||||
v-for="codec in videoEncoderSelfCheckResult.codecs"
|
||||
:key="`${row.resolution_id}-${codec.id}`"
|
||||
class="px-2 py-3 align-middle"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center gap-1"
|
||||
:class="videoEncoderCellClass(videoEncoderCell(row, codec.id)?.ok)"
|
||||
>
|
||||
<div class="text-lg leading-none font-semibold">
|
||||
{{ videoEncoderCellSymbol(videoEncoderCell(row, codec.id)?.ok) }}
|
||||
</div>
|
||||
<div class="text-[11px] leading-4 text-foreground/70">
|
||||
{{ videoEncoderCellTime(videoEncoderCell(row, codec.id)) }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else-if="videoEncoderSelfCheckLoading" class="text-xs text-muted-foreground">
|
||||
{{ t('common.loading') }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Access Section -->
|
||||
|
||||
Reference in New Issue
Block a user