feat(web): 添加远程视频录制功能
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import { reactive, markRaw } from 'vue';
|
||||
import { SignalingClient } from '../services/SignalingClient';
|
||||
import { WebRtcController } from '../services/WebRtcController';
|
||||
import { VideoRecorder } from '../services/VideoRecorder';
|
||||
import { loadProto } from '../proto/controlMessage';
|
||||
|
||||
// 远程视频录制器(基于浏览器原生 MediaRecorder)。
|
||||
const videoRecorder = new VideoRecorder();
|
||||
|
||||
// 与 Android/Flutter 端一致的 ICE 配置(请按需替换为自己的 TURN 凭据)。
|
||||
export const DEFAULT_ICE_SERVERS = [
|
||||
// 公共 TURN(relay 兜底):UDP + TCP 两种传输,TCP 用于 UDP 被防火墙拦截的网络。
|
||||
@@ -38,6 +42,10 @@ export const store = reactive({
|
||||
remoteStream: null,
|
||||
// 被控端上报的当前实际采集分辨率(宽/高/帧率),用于与分辨率下拉框保持一致。
|
||||
currentResolution: null,
|
||||
|
||||
// 远程视频录制状态
|
||||
recording: false,
|
||||
recordStatus: '',
|
||||
});
|
||||
|
||||
let signaling = null;
|
||||
@@ -134,8 +142,16 @@ export async function connectToDevice(targetId, authType = null, authValue = nul
|
||||
store.targetDeviceId = targetId;
|
||||
store.error = '';
|
||||
store.statusText = '正在发起连接...';
|
||||
// 清空上一次连接的录制提示。
|
||||
store.recording = false;
|
||||
store.recordStatus = '';
|
||||
|
||||
if (webrtc) { await webrtc.close(); webrtc = null; }
|
||||
if (webrtc) {
|
||||
// 切换目标设备会结束当前控制,若正在录制则先自动保存。
|
||||
if (videoRecorder.recording) await stopRecording();
|
||||
await webrtc.close();
|
||||
webrtc = null;
|
||||
}
|
||||
|
||||
try {
|
||||
webrtc = new WebRtcController({
|
||||
@@ -166,15 +182,72 @@ export async function connectToDevice(targetId, authType = null, authValue = nul
|
||||
}
|
||||
|
||||
export async function disconnectDevice() {
|
||||
// 结束控制时若正在录制,先停止并自动保存(触发浏览器下载),再关闭连接。
|
||||
if (videoRecorder.recording) {
|
||||
await stopRecording();
|
||||
}
|
||||
if (webrtc) { await webrtc.close(); webrtc = null; }
|
||||
store.rtcConnected = false;
|
||||
store.dataChannelOpen = false;
|
||||
store.remoteStream = null;
|
||||
store.stats = null;
|
||||
store.currentResolution = null;
|
||||
// 注意:保留 recordStatus 的"已保存:文件名"提示,便于用户确认自动保存结果;
|
||||
// 仅复位 recording 标志,下次连接开始时再清空提示。
|
||||
store.recording = false;
|
||||
store.statusText = store.signalingConnected ? '已断开设备连接' : '未连接';
|
||||
}
|
||||
|
||||
// 切换远程视频录制:开始 / 停止。
|
||||
export function toggleRecording() {
|
||||
if (store.recording) return stopRecording();
|
||||
return startRecording();
|
||||
}
|
||||
|
||||
// 开始录制当前远端视频流。
|
||||
export function startRecording() {
|
||||
if (store.recording) return;
|
||||
if (!store.remoteStream) {
|
||||
store.error = '尚未接收到视频画面,无法录制';
|
||||
return;
|
||||
}
|
||||
if (!videoRecorder.isSupported) {
|
||||
store.error = '当前浏览器不支持 MediaRecorder 录制';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const ok = videoRecorder.start(store.remoteStream);
|
||||
if (ok) {
|
||||
store.recording = true;
|
||||
store.recordStatus = '录制中…';
|
||||
}
|
||||
} catch (e) {
|
||||
store.recordStatus = '';
|
||||
store.error = '开始录制失败: ' + (e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
// 停止录制并触发浏览器下载。
|
||||
export async function stopRecording() {
|
||||
const result = await videoRecorder.stop();
|
||||
store.recording = false;
|
||||
if (result) {
|
||||
// 通过 Blob URL 触发浏览器下载(纯前端唯一"保存"途径)。
|
||||
const a = document.createElement('a');
|
||||
a.href = result.url;
|
||||
a.download = result.filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
store.recordStatus = '已保存:' + result.filename;
|
||||
// 下载完成后延迟释放 ObjectURL,避免下载被浏览器中断。
|
||||
setTimeout(() => URL.revokeObjectURL(result.url), 30000);
|
||||
} else {
|
||||
store.recordStatus = '录制已停止';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function disconnectSignaling() {
|
||||
disconnectDevice();
|
||||
if (signaling) { signaling.disconnect(); signaling = null; }
|
||||
|
||||
Reference in New Issue
Block a user