Files
One-KVM/web/src/composables/useVideoSession.ts
mofeng 1786b7689d feat: 完善架构优化性能
- 调整音视频架构,提升 RKMPP 编码 MJPEG-->H264 性能,同时解决丢帧马赛克问题;
- 删除多用户逻辑,只保留单用户,支持设置 web 单会话;
- 修复删除体验不好的的回退逻辑,前端页面菜单位置微调;
- 增加 OTG USB 设备动态调整功能;
- 修复 mdns 问题,webrtc 视频切换更顺畅。
2026-01-25 16:04:29 +08:00

222 lines
5.6 KiB
TypeScript

import { ref } from 'vue'
export interface StreamModeSwitchingEvent {
transition_id: string
to_mode: string
from_mode: string
}
export interface StreamModeReadyEvent {
transition_id: string
mode: string
}
export interface WebRTCReadyEvent {
codec: string
hardware: boolean
transition_id?: string
}
let singleton: ReturnType<typeof createVideoSession> | null = null
function createVideoSession() {
const localSwitching = ref(false)
const backendSwitching = ref(false)
const activeTransitionId = ref<string | null>(null)
const expectedTransitionId = ref<string | null>(null)
let lastUserSwitchAt = 0
let webrtcReadyWaiter: {
transitionId: string
resolve: (ready: boolean) => void
timer: ReturnType<typeof setTimeout>
} | null = null
let webrtcReadyAnyWaiter: {
resolve: (ready: boolean) => void
timer: ReturnType<typeof setTimeout>
} | null = null
let modeReadyWaiter: {
transitionId: string
resolve: (mode: string | null) => void
timer: ReturnType<typeof setTimeout>
} | null = null
function startLocalSwitch() {
localSwitching.value = true
}
function tryStartLocalSwitch(minIntervalMs = 800): boolean {
const now = Date.now()
if (localSwitching.value) return false
if (now - lastUserSwitchAt < minIntervalMs) return false
lastUserSwitchAt = now
localSwitching.value = true
return true
}
function endLocalSwitch() {
localSwitching.value = false
}
function clearWaiters() {
if (webrtcReadyWaiter) {
clearTimeout(webrtcReadyWaiter.timer)
webrtcReadyWaiter.resolve(false)
webrtcReadyWaiter = null
}
if (webrtcReadyAnyWaiter) {
clearTimeout(webrtcReadyAnyWaiter.timer)
webrtcReadyAnyWaiter.resolve(false)
webrtcReadyAnyWaiter = null
}
if (modeReadyWaiter) {
clearTimeout(modeReadyWaiter.timer)
modeReadyWaiter.resolve(null)
modeReadyWaiter = null
}
expectedTransitionId.value = null
}
function registerTransition(transitionId: string) {
expectedTransitionId.value = transitionId
activeTransitionId.value = transitionId
backendSwitching.value = true
}
function isStaleTransition(transitionId?: string): boolean {
if (!transitionId) return false
return expectedTransitionId.value !== null && transitionId !== expectedTransitionId.value
}
function waitForWebRTCReady(transitionId: string, timeoutMs = 3000): Promise<boolean> {
if (webrtcReadyWaiter) {
clearTimeout(webrtcReadyWaiter.timer)
webrtcReadyWaiter.resolve(false)
webrtcReadyWaiter = null
}
return new Promise((resolve) => {
const timer = setTimeout(() => {
if (webrtcReadyWaiter?.transitionId === transitionId) {
webrtcReadyWaiter = null
}
resolve(false)
}, timeoutMs)
webrtcReadyWaiter = {
transitionId,
resolve,
timer,
}
})
}
function waitForWebRTCReadyAny(timeoutMs = 3000): Promise<boolean> {
if (webrtcReadyAnyWaiter) {
clearTimeout(webrtcReadyAnyWaiter.timer)
webrtcReadyAnyWaiter.resolve(false)
webrtcReadyAnyWaiter = null
}
return new Promise((resolve) => {
const timer = setTimeout(() => {
if (webrtcReadyAnyWaiter) {
webrtcReadyAnyWaiter = null
}
resolve(false)
}, timeoutMs)
webrtcReadyAnyWaiter = {
resolve,
timer,
}
})
}
function waitForModeReady(transitionId: string, timeoutMs = 5000): Promise<string | null> {
if (modeReadyWaiter) {
clearTimeout(modeReadyWaiter.timer)
modeReadyWaiter.resolve(null)
modeReadyWaiter = null
}
return new Promise((resolve) => {
const timer = setTimeout(() => {
if (modeReadyWaiter?.transitionId === transitionId) {
modeReadyWaiter = null
}
resolve(null)
}, timeoutMs)
modeReadyWaiter = {
transitionId,
resolve,
timer,
}
})
}
function onModeSwitching(data: StreamModeSwitchingEvent) {
if (localSwitching.value && expectedTransitionId.value && data.transition_id !== expectedTransitionId.value) {
return
}
backendSwitching.value = true
activeTransitionId.value = data.transition_id
expectedTransitionId.value = data.transition_id
}
function onModeReady(data: StreamModeReadyEvent) {
if (isStaleTransition(data.transition_id)) return
backendSwitching.value = false
activeTransitionId.value = null
expectedTransitionId.value = null
if (modeReadyWaiter?.transitionId === data.transition_id) {
clearTimeout(modeReadyWaiter.timer)
modeReadyWaiter.resolve(data.mode)
modeReadyWaiter = null
}
}
function onWebRTCReady(data: WebRTCReadyEvent) {
if (isStaleTransition(data.transition_id)) return
if (data.transition_id && webrtcReadyWaiter?.transitionId === data.transition_id) {
clearTimeout(webrtcReadyWaiter.timer)
webrtcReadyWaiter.resolve(true)
webrtcReadyWaiter = null
} else if (!data.transition_id && webrtcReadyAnyWaiter) {
clearTimeout(webrtcReadyAnyWaiter.timer)
webrtcReadyAnyWaiter.resolve(true)
webrtcReadyAnyWaiter = null
}
}
return {
localSwitching,
backendSwitching,
activeTransitionId,
expectedTransitionId,
startLocalSwitch,
tryStartLocalSwitch,
endLocalSwitch,
clearWaiters,
registerTransition,
waitForWebRTCReady,
waitForWebRTCReadyAny,
waitForModeReady,
onModeSwitching,
onModeReady,
onWebRTCReady,
}
}
export function useVideoSession(): ReturnType<typeof createVideoSession> {
if (!singleton) {
singleton = createVideoSession()
}
return singleton
}