mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1365 字
4 分钟
WebSocket实时视频流传输技术实践
2026-04-02

WebSocket实时视频流传输技术实践:从连接建立到画质控制#

概述#

本文将详细介绍一个物联网设备管理平台中WebSocket实时视频流传输功能的开发历程。通过四个关键Git提交的演进,我们将深入探讨WebSocket连接建立、图像质量控制、移动端适配以及连接模式优化等技术细节。

系统架构背景#

该项目是一个基于Vue3 + TypeScript的物联网设备管理平台,需要实现以下核心功能:

  • 实时视频流监控
  • 温湿度传感器数据展示
  • 设备管理与控制
  • 支持桌面端和移动端双端访问

视频流传输采用WebSocket协议,实现低延迟的实时图像传输。

开发阶段详解#

阶段一:WebSocket连接基础实现#

主要变更

  • 实现WebSocket连接的基础架构
  • 添加设备状态管理
  • 配置Vite开发服务器代理
  • 修改6个文件,新增404行代码

技术实现

  1. WebSocket连接管理
// 实时监控图片帧
const currentFrameImage = ref<string>('');
const lastFrameTime = ref<string>('');
const connectionError = ref<string>('');
const isStreamDisconnected = ref<boolean>(false);
const currentFps = ref<number>(0);
let lastFrameTimestamp = 0;
let previousFrameTimestamp = 0;
let streamCheckInterval: number | null = null;
let ws: WebSocket | null = null;
let currentImageUrl: string = '';
  1. 连接建立流程
const startRealtimeMonitoring = async () => {
// 关闭旧的连接
stopRealtimeMonitoring();
if (!selectedDeviceId.value) {
console.log('没有选择设备,无法启动实时监控');
return;
}
try {
// 先刷新token
let token = await refreshToken();
if (!token) {
connectionError.value = '未登录,请先登录';
return;
}
// 建立WebSocket连接
const wsUrl = `/api/stream/viewer/ws/${selectedDeviceId.value}?token=${token}`;
ws = new WebSocket(wsUrl);
// 监听连接打开事件
ws.onopen = () => {
console.log('WebSocket连接已建立');
connectionError.value = '';
lastFrameTimestamp = Date.now();
isStreamDisconnected.value = false;
// 启动定时器检查图片流是否更新
streamCheckInterval = window.setInterval(() => {
const now = Date.now();
if (now - lastFrameTimestamp > 2000 && currentFrameImage.value) {
isStreamDisconnected.value = true;
}
}, 2500);
};
// ... 其他事件监听
}
};
  1. 图片数据处理
const handleFrameData = (data: any) => {
try {
let blob: Blob;
if (typeof data === 'string') {
// base64编码的图片数据
blob = base64ToBlob(data);
} else if (data instanceof Blob) {
blob = data;
} else if (data instanceof ArrayBuffer) {
blob = new Blob([data], { type: 'image/jpeg' });
} else if (data instanceof Uint8Array) {
blob = new Blob([data as any], { type: 'image/jpeg' });
} else {
throw new Error('不支持的数据类型');
}
// 释放旧的URL对象
if (currentImageUrl) {
URL.revokeObjectURL(currentImageUrl);
}
// 创建新的URL
currentImageUrl = URL.createObjectURL(blob);
currentFrameImage.value = currentImageUrl;
lastFrameTime.value = new Date().toLocaleTimeString('zh-CN');
// 计算帧率
const now = Date.now();
if (previousFrameTimestamp > 0) {
const frameInterval = now - previousFrameTimestamp;
currentFps.value = Math.round(1000 / frameInterval);
}
previousFrameTimestamp = now;
lastFrameTimestamp = now;
isStreamDisconnected.value = false;
} catch (error) {
console.error('处理图片数据失败:', error);
}
};
  1. Vite代理配置
vite.config.ts
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
ws: true, // 启用WebSocket代理
},
},
}

关键挑战与解决方案

挑战解决方案
WebSocket连接不稳定实现自动重连机制和连接状态检测
内存泄漏及时释放旧的Blob URL对象
多种数据格式支持使用类型判断处理base64/Blob/ArrayBuffer/Uint8Array
帧率计算通过时间戳差值计算实时FPS

阶段二:图像质量与分辨率控制#

主要变更

  • 添加图像质量滑块控制(1-63)
  • 实现视频分辨率切换功能
  • 新增110行代码

技术实现

  1. 画质控制实现
// 视频质量控制
const imageQuality = ref(32); // 默认画质为32
const frameSize = ref('FRAMESIZE_VGA'); // 默认视频尺寸
// 发送画质设置到WebSocket连接
const sendStreamQuality = async (quality: number) => {
if (!ws || ws.readyState !== WebSocket.OPEN) {
console.warn('WebSocket连接未建立,无法发送画质设置');
return;
}
const message = JSON.stringify({
code: 1,
item: 'camera',
key: 'jpeg_quality',
values: quality.toString()
});
console.log('向ws连接发送画质设置:', message);
try {
ws.send(message);
} catch (error) {
console.error('发送画质设置失败:', error);
}
};
// 监听滑块变化
watch(imageQuality, (newQuality) => {
// 双端统一:1-63 -> 63-1(反转值,滑块值越大质量越高)
const invertedQuality = 63 - newQuality;
sendStreamQuality(invertedQuality);
});
  1. 分辨率控制实现
// 发送视频尺寸设置到WebSocket连接
const sendFrameSize = async (size: string) => {
if (!ws || ws.readyState !== WebSocket.OPEN) {
console.warn('WebSocket连接未建立,无法发送视频尺寸设置');
return;
}
const message = JSON.stringify({
code: 1,
item: 'camera',
key: 'frame_size',
values: size
});
console.log('向ws连接发送视频尺寸设置:', message);
try {
ws.send(message);
} catch (error) {
console.error('发送视频尺寸设置失败:', error);
}
};
// 监听视频尺寸变化
watch(frameSize, (newSize) => {
sendFrameSize(newSize);
});
  1. UI界面实现
<template>
<div class="video-controls">
<div class="quality-control">
<span class="quality-label">视频质量</span>
<el-slider
v-model="imageQuality"
:min="1"
:max="63"
:step="1"
:show-tooltip="false"
style="width: 120px"
/>
</div>
<div class="quality-control">
<span class="quality-label">视频尺寸</span>
<el-select v-model="frameSize" style="width: 120px" size="small">
<el-option label="128x128" value="FRAMESIZE_128X128" />
<el-option label="240x240" value="FRAMESIZE_240X240" />
<el-option label="320x320" value="FRAMESIZE_320X320" />
<el-option label="VGA" value="FRAMESIZE_VGA" />
<el-option label="SVGA" value="FRAMESIZE_SVGA" />
<el-option label="HD" value="FRAMESIZE_HD" />
<el-option label="FHD" value="FRAMESIZE_FHD" />
</el-select>
</div>
</div>
</template>

技术亮点

  • 使用Vue的watch监听实现实时响应
  • 值反转逻辑(1-63映射为63-1)符合用户直觉(滑块右滑质量越高)
  • 支持7种分辨率选项,从128x128到FHD

阶段三:移动端适配#

主要变更

  • 为MobileData.vue添加视频控制功能
  • 优化移动端UI布局
  • 修改187行代码

移动端适配策略

  1. 响应式控制面板
<template>
<!-- 移动端视频控制 -->
<div class="video-settings">
<div class="setting-item">
<span class="setting-label">画质</span>
<el-slider
v-model="imageQuality"
:min="1"
:max="63"
:step="1"
:show-tooltip="false"
size="small"
style="flex: 1; margin-left: 8px;"
/>
</div>
<div class="setting-item">
<span class="setting-label">视频尺寸</span>
<el-select v-model="frameSize" style="flex: 1; margin-left: 8px;" size="small">
<el-option label="128x128" value="FRAMESIZE_128X128" />
<!-- ... 其他选项 -->
</el-select>
</div>
</div>
</template>
  1. 移动端布局优化
  • 垂直堆叠布局替代水平排列
  • 使用flex布局自适应屏幕宽度
  • 按钮尺寸适配触摸操作

移动端特有考虑

  • 网络环境可能不稳定,提供手动重连按钮
  • 屏幕尺寸限制,简化控制面板
  • 触摸友好的交互设计

阶段四:连接模式优化#

主要变更

  • 优化WebSocket连接模式
  • 修复设备切换时的连接问题
  • 代码量减少10行(优化简化)

新连接模式特点

  1. 单设备单连接
// 新的连接模式:一个WebSocket对应一个设备
const wsUrl = `/api/stream/viewer/ws/${selectedDeviceId.value}?token=${token}`;
ws = new WebSocket(wsUrl);
// 新的连接模式不需要单独发送请求开启设备推流
// 服务器自动根据URL中的设备ID开始推流
  1. 改进的消息处理
ws.onmessage = (event) => {
try {
// 检查是否是设备断开的文本消息
if (typeof event.data === 'string' && event.data.includes('设备已断开')) {
console.log('收到设备断开消息:', event.data);
connectionError.value = '设备已断开';
return;
}
// 尝试解析JSON数据(控制命令响应)
try {
const data = JSON.parse(event.data);
if (data.code === 1 && data.key === 'jpeg_quality') {
console.log('画质设置成功:', data);
} else if (data.code === 1 && data.key === 'frame_size') {
console.log('视频尺寸设置成功:', data);
} else if (data.code === 400) {
console.error('订阅失败:', data.msg);
connectionError.value = '订阅失败';
}
} catch (jsonError) {
// 如果不是JSON数据,处理为二进制图片数据
handleFrameData(event.data);
}
} catch (error) {
console.error('解析WebSocket消息失败:', error);
}
};
  1. Bug修复
  • 修复设备切换时WebSocket连接未正确关闭的问题
  • 优化连接状态管理
  • 改进错误处理逻辑

技术架构总结#

WebSocket连接生命周期#

1. 用户选择设备
2. 获取访问token
3. 建立WebSocket连接 (/api/stream/viewer/ws/{deviceId}?token={token})
4. 连接成功,开始接收视频帧
5. 可发送控制命令(画质、分辨率)
6. 用户切换设备或离开页面时关闭连接

消息协议设计#

消息类型方向格式说明
视频帧数据Server → ClientBinary (Blob/ArrayBuffer)JPEG图像数据
画质设置Client → ServerJSON{"code":1,"item":"camera","key":"jpeg_quality","values":"32"}
分辨率设置Client → ServerJSON{"code":1,"item":"camera","key":"frame_size","values":"FRAMESIZE_VGA"}
控制响应Server → ClientJSON{"code":1,"msg":"success"}{"code":400,"msg":"error"}
设备断开通知Server → ClientText”设备已断开”

性能优化策略#

  1. 内存管理
// 释放旧的URL对象防止内存泄漏
if (currentImageUrl) {
URL.revokeObjectURL(currentImageUrl);
}
currentImageUrl = URL.createObjectURL(blob);
  1. 连接状态检测
// 每2.5秒检查一次是否收到新帧
streamCheckInterval = window.setInterval(() => {
const now = Date.now();
if (now - lastFrameTimestamp > 2000 && currentFrameImage.value) {
isStreamDisconnected.value = true;
}
}, 2500);
  1. 动态画质调整
  • 根据网络状况调整图像质量
  • 移动端默认较低画质以节省流量
  • 支持实时调整无需重新连接

遇到的挑战及解决方案#

挑战1:多种数据格式兼容#

问题:WebSocket可能接收base64字符串、Blob、ArrayBuffer等多种格式的图片数据

解决方案

const handleFrameData = (data: any) => {
let blob: Blob;
if (typeof data === 'string') {
blob = base64ToBlob(data);
} else if (data instanceof Blob) {
blob = data;
} else if (data instanceof ArrayBuffer) {
blob = new Blob([data], { type: 'image/jpeg' });
} else if (data instanceof Uint8Array) {
blob = new Blob([data as any], { type: 'image/jpeg' });
}
// ...
};

挑战2:移动端性能优化#

问题:移动端的网络环境和性能限制

解决方案

  • 默认使用较低分辨率(VGA)
  • 提供画质调节滑块,让用户根据网络状况自主选择
  • 优化UI布局,减少重绘

挑战3:连接稳定性#

问题:WebSocket连接可能因网络波动断开

解决方案

  • 实现连接状态检测(2秒无新帧标记为断开)
  • 提供手动重连按钮
  • 设备切换时正确关闭旧连接

挑战4:用户直觉与数据映射#

问题:画质滑块值越大应该质量越好,但设备接收的值是反的(值越小质量越高)

解决方案

// 值反转:UI显示1-63(低-高),实际发送63-1
watch(imageQuality, (newQuality) => {
const invertedQuality = 63 - newQuality;
sendStreamQuality(invertedQuality);
});

总结#

通过这四个阶段的迭代开发,我们实现了一个稳定、高效的WebSocket实时视频流传输系统。关键经验包括:

  1. 渐进式开发:先实现基础连接,再添加控制功能,最后优化体验
  2. 多端统一:桌面端和移动端共享核心逻辑,仅UI层差异化
  3. 状态管理:完善的连接状态检测和错误处理机制
  4. 性能优先:内存管理、连接优化、动态画质调整

这套系统目前支持:

  • 实时视频流传输
  • 动态画质控制(1-63级)
  • 7种分辨率选项
  • 双端自适应UI
  • 稳定的连接管理
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

WebSocket实时视频流传输技术实践
http://www.cyanbutterfly.top/posts/websocket_video_stream_development/
作者
青蝶半染
发布于
2026-04-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时