feat: 增加信令自动重连保活与控制端视频录制

- 信令客户端增加心跳保活与指数退避自动重连机制
- 服务端优化传输异常日志级别,客户端断开不再报错
- 被控端信令断开时保持服务运行并自动重连
- Flutter 控制端增加远程视频录制功能,兼容标准 WebRTC 模式切换
- 各端连接按钮增加“连接中”状态防重复点击
This commit is contained in:
TongTongStudio
2026-07-30 00:40:22 +08:00
parent 7f545359d9
commit e831ef2795
14 changed files with 444 additions and 27 deletions

View File

@@ -31,8 +31,10 @@ function onConnectDevice() {
<input class="input" v-model="store.deviceId" placeholder="例如 web-controller-001" />
</div>
<button class="btn" v-if="!store.signalingConnected" @click="connectSignaling">连接信令服务器</button>
<button class="btn danger" v-else @click="disconnectSignaling">断开信令</button>
<button class="btn danger" v-if="store.signalingConnected" @click="disconnectSignaling">断开信令服务器</button>
<button class="btn" v-else :disabled="store.signalingConnecting" @click="connectSignaling">
{{ store.signalingConnecting ? '正在连接...' : '连接信令服务器' }}
</button>
<div class="section-title">被控端设备 (CONTROLLED)</div>
<button class="btn secondary" style="margin-bottom:12px" @click="refreshDevices" :disabled="!store.signalingConnected">

View File

@@ -19,3 +19,33 @@ import { store } from '../store/controllerStore';
<span>目标: <b>{{ store.targetDeviceId || '-' }}</b></span>
</div>
</template>
<style scoped>
.stats-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
}
.stats-bar span {
display: inline-flex;
align-items: center;
white-space: nowrap;
}
/* 每个统计项固定宽度,避免文字多少导致整体跳动 */
.stats-bar span:nth-child(1) { width: 140px; } /* 分辨率 */
.stats-bar span:nth-child(2) { width: 90px; } /* 帧率 */
.stats-bar span:nth-child(3) { width: 110px; } /* 编码 */
.stats-bar span:nth-child(4) { width: 100px; } /* 延迟 */
.stats-bar span:nth-child(5) { width: 100px; } /* 抖动 */
.stats-bar span:nth-child(6) { width: 100px; } /* 丢帧 */
.stats-bar span:nth-child(7) { width: 120px; } /* 解码耗时 */
.stats-bar span:nth-child(8) { width: 120px; } /* 上传 */
.stats-bar span:nth-child(9) { width: 120px; } /* 下载 */
.stats-bar span:nth-child(10) { width: 100px; } /* 丢包 */
.stats-bar span:nth-child(11) { width: 120px; } /* 时长 */
.stats-bar span:nth-child(12) { width: 140px; } /* 控制通道 */
.stats-bar span:nth-child(13) { width: 160px; } /* 目标 */
</style>

View File

@@ -26,6 +26,7 @@ export const store = reactive({
protoReady: false,
signalingConnected: false,
signalingConnecting: false,
registered: false,
rtcConnected: false,
dataChannelOpen: false,
@@ -60,20 +61,26 @@ export function connectSignaling() {
const deviceId = store.deviceId.trim();
if (!deviceId) { store.error = '请填写本机设备 ID'; return; }
store.signalingConnecting = true;
signaling = new SignalingClient({
serverUrl: store.serverUrl.trim(),
deviceId,
onConnected: () => {
store.signalingConnected = true;
store.signalingConnecting = false;
store.statusText = '已连接信令服务器';
refreshDevices();
},
onDisconnected: () => {
store.signalingConnected = false;
store.signalingConnecting = false;
store.registered = false;
store.statusText = '已断开信令连接';
},
onError: (e) => { store.error = '信令错误: ' + e; },
onError: (e) => {
store.signalingConnecting = false;
store.error = '信令错误: ' + e;
},
onMessage: handleSignalMessage,
});
signaling.connect();
@@ -172,6 +179,7 @@ export function disconnectSignaling() {
disconnectDevice();
if (signaling) { signaling.disconnect(); signaling = null; }
store.signalingConnected = false;
store.signalingConnecting = false;
store.registered = false;
store.controlledDevices = [];
}