完整使用示例
vue
<template>
<div class="app-container">
<div ref="containerRef" class="device-container"></div>
<div class="controls">
<div class="input-group">
<label class="label">主机IP</label>
<input v-model="hostIp" placeholder="例如: 192.168.10.50" class="input" />
<label class="label">云机ID (Device Name)</label>
<input v-model="deviceName" placeholder="例如: EDGE00M7EHBKZGVN" class="input" />
</div>
<div class="button-group">
<button @click="connect" class="btn btn-primary">连接</button>
<button @click="disconnect" class="btn btn-secondary">断开</button>
<button @click="handleHome" class="btn btn-action">Home</button>
<button @click="handleBack" class="btn btn-action">Back</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef, markRaw, onMounted, onUnmounted } from "vue";
import {
VmosEdgeClient,
VmosEdgeClientEvents,
type VmosEdgeClientConfig,
type VmosEdgeErrorEvent,
} from "@vmosedge/web-sdk";
const containerRef = ref<HTMLElement | null>(null);
const client = shallowRef<VmosEdgeClient | null>(null);
const hostIp = ref("192.168.10.50"); // 主机IP
const deviceName = ref("EDGE00M7EHBKZGVN"); // 云机ID
// 接口返回数据类型定义
interface DeviceInfo {
ip: string;
host_ip: string;
db_id: string;
network_mode: string;
is_macvlan: boolean;
tcp_port: number;
tcp_control_port: number;
}
interface ApiResponse {
code: number;
msg: string;
data: {
count: number;
host_ip: string;
list: DeviceInfo[];
};
}
// 获取设备信息并构建配置
const getDeviceConfig = async (
hostIp: string,
deviceName: string
): Promise<VmosEdgeClientConfig> => {
const response = await fetch(`http://${hostIp}:18182/container_api/v1/get_db`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: deviceName }),
});
const result: ApiResponse = await response.json();
if (result.code !== 200 || !result.data.list || result.data.list.length === 0) {
throw new Error(result.msg || "获取设备信息失败");
}
const device = result.data.list[0];
const isMacvlan = device.network_mode === "macvlan" || device.is_macvlan;
// 根据网络模式构建配置
if (isMacvlan) {
// 局域网模式(macvlan)
return {
ip: device.ip, // 使用设备的 ip 字段
deviceId: device.db_id, // 使用设备的 db_id 字段
ports: {
video: 9999, // 局域网模式固定端口
touch: 9997, // 局域网模式固定端口
},
};
} else {
// 非局域网模式(bridge)
return {
ip: result.data.host_ip, // 使用 data 层级的 host_ip 字段
deviceId: device.db_id, // 使用设备的 db_id 字段
ports: {
video: device.tcp_port, // 使用设备的 tcp_port 字段
touch: device.tcp_control_port, // 使用设备的 tcp_control_port 字段
},
};
}
};
const connect = async () => {
if (!containerRef.value) return;
// 环境检测
if (!VmosEdgeClient.isWebCodecsSupported()) {
console.error("当前环境不支持 SDK 使用,请使用 file:// 协议打开 HTML 文件,或通过 localhost 访问");
return;
}
try {
// 获取设备配置
const config = await getDeviceConfig(hostIp.value, deviceName.value);
const instance = new VmosEdgeClient({
container: containerRef.value,
config,
retryCount: 3,
});
// 使用 markRaw 避免 Vue 响应式代理
client.value = markRaw(instance);
// 监听事件
client.value.on(VmosEdgeClientEvents.STARTED, () => {
console.log("连接成功");
});
client.value.on(VmosEdgeClientEvents.ERROR, (error: VmosEdgeErrorEvent) => {
console.error("错误:", error);
});
await client.value.start();
} catch (error) {
console.error("连接失败:", error);
}
};
const disconnect = () => {
client.value?.stop();
client.value = null;
};
const handleHome = () => {
client.value?.home();
};
const handleBack = () => {
client.value?.back();
};
onUnmounted(() => {
disconnect();
});
</script>
<style scoped>
.app-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 60px;
padding: 40px;
height: 100vh;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #fff;
}
.device-container {
width: 360px;
height: 720px;
background: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
overflow: hidden;
}
.controls {
display: flex;
flex-direction: column;
gap: 20px;
width: 320px;
flex-shrink: 0;
max-height: 100vh;
overflow: visible;
}
.input-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 0;
}
.input {
padding: 12px 16px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s;
outline: none;
width: 100%;
background: #fff;
}
.input:focus {
border-color: #4a90e2;
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
.button-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.btn {
padding: 12px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
outline: none;
min-width: 0;
}
.btn-primary {
background: #4a90e2;
color: white;
grid-column: 1 / -1;
}
.btn-primary:hover {
background: #357abd;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}
.btn-action {
background: #f8f9fa;
color: #333;
border: 1px solid #dee2e6;
}
.btn-action:hover {
background: #e9ecef;
}
</style>tsx
import { useEffect, useRef, useState } from "react";
import {
VmosEdgeClient,
VmosEdgeClientEvents,
type VmosEdgeClientConfig,
type VmosEdgeErrorEvent,
} from "@vmosedge/web-sdk";
// 接口返回数据类型定义
interface DeviceInfo {
ip: string;
host_ip: string;
db_id: string;
network_mode: string;
is_macvlan: boolean;
tcp_port: number;
tcp_control_port: number;
}
interface ApiResponse {
code: number;
msg: string;
data: {
count: number;
host_ip: string;
list: DeviceInfo[];
};
}
function App() {
const containerRef = useRef<HTMLDivElement>(null);
const clientRef = useRef<VmosEdgeClient | null>(null);
const [connected, setConnected] = useState(false);
const [hostIp, setHostIp] = useState("192.168.10.50"); // 主机IP
const [deviceName, setDeviceName] = useState("EDGE00M7EHBKZGVN"); // 云机ID
// 获取设备信息并构建配置
const getDeviceConfig = async (
hostIp: string,
deviceName: string
): Promise<VmosEdgeClientConfig> => {
const response = await fetch(`http://${hostIp}:18182/container_api/v1/get_db`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: deviceName }),
});
const result: ApiResponse = await response.json();
if (result.code !== 200 || !result.data.list || result.data.list.length === 0) {
throw new Error(result.msg || "获取设备信息失败");
}
const device = result.data.list[0];
const isMacvlan = device.network_mode === "macvlan" || device.is_macvlan;
// 根据网络模式构建配置
if (isMacvlan) {
// 局域网模式(macvlan)
return {
ip: device.ip, // 使用设备的 ip 字段
deviceId: device.db_id, // 使用设备的 db_id 字段
ports: {
video: 9999, // 局域网模式固定端口
touch: 9997, // 局域网模式固定端口
},
};
} else {
// 非局域网模式(bridge)
return {
ip: result.data.host_ip, // 使用 data 层级的 host_ip 字段
deviceId: device.db_id, // 使用设备的 db_id 字段
ports: {
video: device.tcp_port, // 使用设备的 tcp_port 字段
touch: device.tcp_control_port, // 使用设备的 tcp_control_port 字段
},
};
}
};
const connect = async () => {
if (!containerRef.current) return;
// 环境检测
if (!VmosEdgeClient.isWebCodecsSupported()) {
console.error("当前环境不支持 SDK 使用,请使用 file:// 协议打开 HTML 文件,或通过 localhost 访问");
return;
}
try {
// 获取设备配置
const config = await getDeviceConfig(hostIp, deviceName);
const client = new VmosEdgeClient({
container: containerRef.current,
config,
retryCount: 3,
});
clientRef.current = client;
client.on(VmosEdgeClientEvents.STARTED, () => {
setConnected(true);
console.log("连接成功");
});
client.on(VmosEdgeClientEvents.ERROR, (error: VmosEdgeErrorEvent) => {
console.error("错误:", error);
});
await client.start();
} catch (error) {
console.error("连接失败:", error);
}
};
const disconnect = () => {
clientRef.current?.stop();
clientRef.current = null;
setConnected(false);
};
useEffect(() => {
return () => {
disconnect();
};
}, []);
return (
<div style={styles.container}>
<div ref={containerRef} style={styles.deviceContainer} />
<div style={styles.controls}>
<div style={styles.inputGroup}>
<label style={styles.label}>主机IP</label>
<input
value={hostIp}
onChange={(e) => setHostIp(e.target.value)}
placeholder="例如: 192.168.10.50"
style={styles.input}
/>
<label style={styles.label}>云机ID (Device Name)</label>
<input
value={deviceName}
onChange={(e) => setDeviceName(e.target.value)}
placeholder="例如: EDGE00M7EHBKZGVN"
style={styles.input}
/>
</div>
<div style={styles.buttonGroup}>
<button onClick={connect} style={{ ...styles.btn, ...styles.btnPrimary }}>
连接
</button>
<button onClick={disconnect} style={{ ...styles.btn, ...styles.btnSecondary }}>
断开
</button>
<button onClick={() => clientRef.current?.home()} style={{ ...styles.btn, ...styles.btnAction }}>
Home
</button>
<button onClick={() => clientRef.current?.back()} style={{ ...styles.btn, ...styles.btnAction }}>
Back
</button>
</div>
</div>
</div>
);
}
const styles = {
container: {
display: "flex",
flexDirection: "row" as const,
alignItems: "center",
justifyContent: "center",
gap: "40px",
padding: "20px",
minHeight: "100vh",
maxHeight: "100vh",
overflow: "hidden",
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
},
deviceContainer: {
width: 360,
height: 720,
background: "#fff",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "12px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
flexShrink: 0,
overflow: "hidden",
},
controls: {
display: "flex",
flexDirection: "column" as const,
gap: "16px",
width: "360px",
flexShrink: 0,
},
inputGroup: {
display: "flex",
flexDirection: "column" as const,
gap: "8px",
},
label: {
fontSize: "14px",
fontWeight: 500,
color: "#333",
marginBottom: "4px",
},
input: {
padding: "12px 16px",
border: "1px solid #e0e0e0",
borderRadius: "6px",
fontSize: "14px",
transition: "all 0.2s",
outline: "none",
},
buttonGroup: {
display: "flex",
gap: "8px",
flexWrap: "wrap" as const,
},
btn: {
padding: "10px 20px",
border: "none",
borderRadius: "6px",
fontSize: "14px",
fontWeight: 500,
cursor: "pointer",
transition: "all 0.2s",
outline: "none",
},
btnPrimary: {
background: "#4a90e2",
color: "white",
flex: 1,
},
btnSecondary: {
background: "#6c757d",
color: "white",
flex: 1,
},
btnAction: {
background: "#f8f9fa",
color: "#333",
border: "1px solid #dee2e6",
},
};
export default App;html
<!DOCTYPE html>
<html>
<head>
<title>Vmos Edge Web SDK 示例</title>
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
body {
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 40px;
min-height: 100vh;
max-height: 100vh;
background: #f5f5f5;
}
#container {
width: 360px;
height: 720px;
background: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
overflow: hidden;
}
.controls {
display: flex;
flex-direction: column;
gap: 16px;
width: 360px;
flex-shrink: 0;
}
.input-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.input-group label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.input-group input {
padding: 12px 16px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s;
outline: none;
}
.input-group input:focus {
border-color: #4a90e2;
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
.button-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.button-group button {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
outline: none;
}
.button-group button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.btn-primary {
background: #4a90e2;
color: white;
flex: 1;
}
.btn-primary:hover {
background: #357abd;
}
.btn-secondary {
background: #6c757d;
color: white;
flex: 1;
}
.btn-secondary:hover {
background: #5a6268;
}
.btn-action {
background: #f8f9fa;
color: #333;
border: 1px solid #dee2e6;
}
.btn-action:hover {
background: #e9ecef;
}
</style>
</head>
<body>
<div id="container"></div>
<div class="controls">
<div class="input-group">
<label>主机IP</label>
<input id="hostIp" type="text" placeholder="例如: 192.168.10.50" value="192.168.10.50" />
<label>云机ID (Device Name)</label>
<input id="deviceName" type="text" placeholder="例如: EDGE00M7EHBKZGVN" value="EDGE00M7EHBKZGVN" />
</div>
<div class="button-group">
<button onclick="connect()" class="btn-primary">连接</button>
<button onclick="disconnect()" class="btn-secondary">断开</button>
<button onclick="handleHome()" class="btn-action">Home</button>
<button onclick="handleBack()" class="btn-action">Back</button>
</div>
</div>
<script type="module">
import {
VmosEdgeClient,
VmosEdgeClientEvents,
} from "@vmosedge/web-sdk";
let client = null;
// 获取设备信息并构建配置
async function getDeviceConfig(hostIp, deviceName) {
const response = await fetch(`http://${hostIp}:18182/container_api/v1/get_db`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: deviceName }),
});
const result = await response.json();
if (result.code !== 200 || !result.data.list || result.data.list.length === 0) {
throw new Error(result.msg || "获取设备信息失败");
}
const device = result.data.list[0];
const isMacvlan = device.network_mode === "macvlan" || device.is_macvlan;
// 根据网络模式构建配置
if (isMacvlan) {
// 局域网模式(macvlan)
return {
ip: device.ip, // 使用设备的 ip 字段
deviceId: device.db_id, // 使用设备的 db_id 字段
ports: {
video: 9999, // 局域网模式固定端口
touch: 9997, // 局域网模式固定端口
},
};
} else {
// 非局域网模式(bridge)
return {
ip: result.data.host_ip, // 使用 data 层级的 host_ip 字段
deviceId: device.db_id, // 使用设备的 db_id 字段
ports: {
video: device.tcp_port, // 使用设备的 tcp_port 字段
touch: device.tcp_control_port, // 使用设备的 tcp_control_port 字段
},
};
}
}
async function connect() {
const container = document.getElementById("container");
const hostIp = document.getElementById("hostIp").value;
const deviceName = document.getElementById("deviceName").value;
// 环境检测
if (!VmosEdgeClient.isWebCodecsSupported()) {
console.error("当前环境不支持 SDK 使用,请使用 file:// 协议打开 HTML 文件,或通过 localhost 访问");
return;
}
try {
// 获取设备配置
const config = await getDeviceConfig(hostIp, deviceName);
client = new VmosEdgeClient({
container,
config,
retryCount: 3,
});
client.on(VmosEdgeClientEvents.STARTED, () => {
console.log("连接成功");
});
client.on(VmosEdgeClientEvents.ERROR, (error) => {
console.error("错误:", error);
});
await client.start();
} catch (error) {
console.error("连接失败:", error);
}
}
function disconnect() {
if (client) {
client.stop();
client = null;
}
}
function handleHome() {
client?.home();
}
function handleBack() {
client?.back();
}
// 导出到全局作用域
window.connect = connect;
window.disconnect = disconnect;
window.handleHome = handleHome;
window.handleBack = handleBack;
</script>
</body>
</html>⚠️ 注意事项
Vue/React 框架使用
重要: 在 Vue 或 React 中使用时,VmosEdgeClient 实例不应被深度代理。
- Vue 3:使用
shallowRef和markRaw - React:使用
useRef
错误示例(Vue):
typescript
// ❌ 错误:使用 ref 会导致 Proxy 代理问题
const client = ref<VmosEdgeClient | null>(null);
client.value = new VmosEdgeClient({ /* ... */ });正确示例(Vue):
typescript
// ✅ 正确:使用 shallowRef 和 markRaw
const client = shallowRef<VmosEdgeClient | null>(null);
const instance = new VmosEdgeClient({ /* ... */ });
client.value = markRaw(instance);容器元素
- 容器元素必须是有效的
HTMLElement - SDK 会在容器内创建
.vmos-canvas-container元素 - 建议为容器设置固定尺寸,避免布局问题
连接配置
- 单控模式:
video和touch端口都是必填的 - 群控模式:从设备仅需要
touch端口,不需要video端口 audio端口暂不支持,请勿配置- 确保设备 IP 和端口配置正确
错误处理
- 务必监听
ERROR事件,及时处理连接错误 - 根据错误类型(
error.type)和错误代码(error.code)进行相应处理
资源清理
- 在组件卸载或页面关闭时,务必调用
client.stop()释放资源 - 避免内存泄漏
