项目背景:本文基于个人项目中的视频监控需求,详细阐述 uni-app 框架下海康威视设备的接入方案,重点介绍双协议支持、自动降级、资源管理等核心技术问题的解决思路。
一、技术选型与架构设计
1.1 协议对比分析
| 协议类型 | 适用场景 | 延迟表现 | 兼容性 | 实现复杂度 |
|---|---|---|---|---|
| WebSocket (私有协议) | H5 平台,支持 JSPlugin | 低 (1-3s) | 依赖浏览器环境 | 高 |
| HLS | 跨平台兼容 | 中 (3-10s) | 优秀 | 中 |
| RTSP/RTMP | 原生 App | 低 | 受限 | 高 |
选型策略:以 WebSocket 为主协议,HLS 作为降级方案,确保在各种异常情况下仍能提供视频服务。
1.2 核心技术栈
// 前端框架
- Vue 3 (Composition API)
- TypeScript
- uni-app (跨平台框架)
// 视频播放
- JSPlugin.js (海康官方 H5 SDK)
- hls.js (Web 端 HLS 播放器)
// 状态管理
- Pinia (带持久化插件)
- uni-app 原生生命周期
1.3 工具准备
1.3.1 海康官方插件包

这里提供了demo 可以测试相关接口以及取流地址是否可用,开发包中主要需要这些文件,放到项目的static(自定义即可)目录下即可

主要所用接口:

这里测试接口的时候按照海康官方文档一步一步执行,文档写的很清晰,最后获取到地址即可。
1.3.2 hls协议实现所需工具
npm下载 video.js,hls.js,实现播放协议的工具有很多,我只使用了这两个,因为hls协议较为简单,所以重点以websocket为主,相关代码也会列出来
海康平台默认hls取得协议是http协议的,

只要将potocal的参数改为hlss(wss同理),即可获取https协议地址,这个地址主要是为项目需要上政府企业或者安全要求高的企业使用,一般情况下http即可。
二、核心实现流程
2.1 完整业务流程
2.2 API 接口设计
2.2.1 获取视频流地址
这里获取视频流地址,主要是i后端对海康平台做了一层接口转发。实际上参数跟海康开发平台文档一样,主要是获取
export const getStreamUrl = (data: GetPreviewURLsServiceReq) =>
post('/manage', {
data: {
api: 'getPreviewURLsService',
v: '1.0',
data,
}
});
// 请求参数类型
interface GetPreviewURLsServiceReq {
/** 监控索引编码 */
cameraIndexCode: string;
/** 协议类型 */
protocol: 'hik' | 'rtsp' | 'rtmp' | 'hls' | 'ws';
/** 码流类型: 0-主码流, 1-子码流, 2-第三码流 */
streamType?: number;
/** 传输协议: 0-UDP, 1-TCP */
transmode?: number;
}
// 响应数据结构
{
code: 200,
msg: 'success',
data: {
url: 'ws://stream.example.com/live/xxx',
expireTime: 3600,
protocol: 'ws'
}
}
三、WebSocket 协议实现(主方案)
3.1 动态脚本加载
核心问题:JSPlugin 体积较大(16M),且仅在 H5 环境需要,按需加载。
注意:如果有上政务环境的要求,要先去官网问一下有无文档体积限制,(例如浙江这边,浙政钉要求源码上传,最多不能20M)
解决方案:
// src/pages/monitor/detail/index.vue:155-176
const loadJSPluginScript = (): Promise<void> => {
return new Promise((resolve, reject) => {
// 1. 检查缓存,避免重复加载
if (window.JSPlugin) {
resolve();
return;
}
// 2. 动态创建 script 标签
const script = document.createElement('script');
script.type = 'text/javascript';
// 远程加载(要现在浏览器测试是否输入后能否直接访问)
script.src = 'http://xxxxx.com/h5Player/h5player.min.js';
// 本地加载 (这里注意本地加载的话是挂在全局的html文件下)
script.src = '/static/h5Player/js/JsPlugin.js';
// 3. 加载成功
script.onload = () => {
console.log('JSPlugin 脚本加载成功');
resolve();
};
// 4. 加载失败处理
script.onerror = (error) => {
console.error('JSPlugin 脚本加载失败:', error);
reject(new Error('JSPlugin 加载超时或网络错误'));
};
// 5. 添加到文档
document.head.appendChild(script);
});
};
关键点:
- ✅ Promise 封装,支持异步等待
- ✅ 缓存检查,避免重复加载
- ✅ 明确的错误处理,便于降级
- ✅ 异步加载,不阻塞页面渲染
3.2 播放器初始化
// src/pages/monitor/detail/index.vue:198-261
const initPlayer = async () => {
try {
// 1. 动态加载 JSPlugin
await loadJSPluginScript();
} catch (error) {
console.error('加载JSPlugin失败:', error);
// 触发降级:记录日志,提示用户
uni.showToast({
title: 'WS协议播放器加载失败',
icon: 'none'
});
// 抛出错误,由上层处理降级逻辑
throw error;
}
try {
// 2. 检查 JSPlugin 是否可用
if (typeof window.JSPlugin === 'undefined') {
console.error('JSPlugin未定义');
uni.showToast({
title: 'WS协议播放器不可用',
icon: 'none'
});
throw new Error('JSPlugin未定义');
}
// 3. 初始化播放器实例
myPlugin = new window.JSPlugin({
szId: 'play_window', // 播放容器 ID
szBasePath: 'http://xxxxx.com/h5Player/', // 静态资源路径 (这里如果本地加载一定要与h5player.min.js的引用目录一致,/static/h5Player/)
mseWorkerEnable: true, // 启用 Worker 提升性能
bSupporDoubleClickFull: true, // 支持双击全屏
});
// 4. 设置事件回调
myPlugin.JS_SetWindowControlCallback({
windowEventSelect: (index: number) => {
curIndex = index;
console.log('窗口选中:', index);
},
pluginErrorHandler: (index: number, iErrorCode: number, oError: any) => {
console.error('播放器错误:', iErrorCode, oError);
uni.showToast({ title: '播放失败', icon: 'none' });
},
firstFrameDisplay: (index: number, iWidth: number, iHeight: number) => {
console.log('首帧显示:', index, iWidth, iHeight);
},
InterruptStream: (iWndIndex: number, interruptTime: number) => {
console.log('断流事件:', iWndIndex, interruptTime);
},
});
} catch (error) {
console.error('初始化播放器失败:', error);
uni.showToast({
title: 'WS协议播放器初始化失败',
icon: 'none'
});
throw error;
}
};
配置参数说明:
| 参数 | 说明 | 值 |
|---|---|---|
szId | 容器 ID | ‘play_window’ |
szBasePath | 资源基路径 | CDN 地址 |
mseWorkerEnable | Worker 模式 | true (性能优化) |
bSupporDoubleClickFull | 双击全屏 | true (用户体验) |
3.3 视频播放控制
// src/pages/monitor/detail/index.vue:263-304
/**
* 播放视频
* @param url 视频流地址
*/
const playVideo = async (url: string) => {
if (!myPlugin) {
console.error('播放器未初始化');
return;
}
try {
await myPlugin.JS_Play(
url,
{
playURL: url, // 播放地址
mode: 0, // 播放模式: 0-实时播放
PlayBackMode: 1, // 回放模式: 1-实时
keepDecoder: 0, // 解码器配置
},
curIndex, // 窗口索引
);
console.log('开始播放');
hideLoading(); // 隐藏 loading
} catch (err) {
console.error('播放失败:', err);
hideLoading();
uni.showToast({
title: '播放失败',
icon: 'none'
});
// 可在此触发降级逻辑
throw err;
}
};
/**
* 停止视频播放
*/
const stopVideo = async () => {
if (!myPlugin) return;
try {
await myPlugin.JS_Stop(curIndex);
console.log('停止播放');
} catch (err) {
console.error('停止失败:', err);
}
};
此处 主要是按照海康官方文档一步一步实现。相关api在文档中都有写明
四、HLS 协议实现(降级方案)
4.1 HLS 播放器初始化
// src/pages/monitor/detail/index.vue:306-413
const initHlsPlayer = (url: string) => {
console.log('初始化HLS播放器, URL:', url);
// #ifdef H5
// 1. 参数校验
if (!url || url.trim() === '') {
console.error('HLS URL 为空');
uni.showToast({ title: 'HLS地址无效', icon: 'none' });
return;
}
// 2. 获取视频元素
const videoElement = videoRef.value?.$el || videoRef.value;
console.log('视频元素类型:', videoElement);
if (!videoElement) {
console.error('视频元素未找到');
uni.showToast({ title: '视频元素加载失败', icon: 'none' });
return;
}
// 兼容不同环境的 video 获取方式
const video = videoElement.tagName === 'VIDEO'
? videoElement
: videoElement.querySelector('video');
if (!video) {
console.error('未找到video标签');
uni.showToast({ title: '视频标签加载失败', icon: 'none' });
return;
}
// 3. 浏览器能力检测与播放
if (Hls.isSupported()) {
// 3.1 使用 hls.js 库
console.log('使用 hls.js 播放');
hls = new Hls({
debug: true, // 调试模式
enableWorker: true, // 启用 Worker
lowLatencyMode: true, // 低延迟模式
});
hls.loadSource(url);
hls.attachMedia(video);
// 3.2 成功解析 manifest
hls.on(Hls.Events.MANIFEST_PARSED, () => {
console.log('HLS manifest 解析成功');
video.play().catch((err) => {
console.error('自动播放失败:', err);
uni.showToast({
title: '请手动点击播放',
icon: 'none'
});
});
});
// 3.3 错误处理与恢复机制
hls.on(Hls.Events.ERROR, (event, data) => {
console.error('HLS 错误:', data);
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.error('网络错误,尝试恢复');
hls?.startLoad(); // 重新加载
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.error('媒体错误,尝试恢复');
hls?.recoverMediaError(); // 恢复媒体错误
break;
default:
console.error('无法恢复的错误');
hls?.destroy();
hls = null;
uni.showToast({
title: 'HLS播放失败',
icon: 'none'
});
break;
}
}
});
}
// 3.4 原生 HLS 支持(Safari、iOS)
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
console.log('使用原生 HLS 播放');
video.src = url;
video.addEventListener('loadedmetadata', () => {
video.play().catch((err) => {
console.error('播放失败:', err);
uni.showToast({
title: '请手动点击播放',
icon: 'none'
});
});
});
video.addEventListener('error', (e) => {
console.error('视频加载错误:', e);
uni.showToast({
title: '视频加载失败',
icon: 'none'
});
});
}
// 3.5 完全不支持 HLS
else {
console.error('浏览器不支持 HLS');
uni.showToast({
title: '浏览器不支持HLS',
icon: 'none'
});
}
// #endif
};
HLS 错误类型处理:
| 错误类型 | 原因 | 处理方式 |
|---|---|---|
NETWORK_ERROR | 网络中断/跨域 | startLoad() 重试 |
MEDIA_ERROR | 解码错误 | recoverMediaError() 恢复 |
OTHER_ERROR | 未知错误 | 销毁实例,提示用户 |
五、双协议切换与降级策略
5.1 协议切换实现
// src/pages/monitor/detail/index.vue:415-464
const switchTab = async (tab: 'ws' | 'hls') => {
if (activeTab.value === tab) return;
// 1. 显示加载状态
showLoading('监控连接中...');
// 2. 清理当前协议资源
if (activeTab.value === 'ws') {
await stopVideo();
} else if (activeTab.value === 'hls' && hls) {
hls.destroy();
hls = null;
}
// 3. 更新当前标签
activeTab.value = tab;
// 4. 初始化新协议
if (tab === 'ws' && previewUrl.value) {
try {
await initPlayer();
if (myPlugin && activeTab.value === 'ws') {
playVideo(previewUrl.value);
}
} catch (error) {
console.error('切换到WS协议失败:', error);
hideLoading();
uni.showToast({ title: '切换失败', icon: 'none' });
}
} else if (tab === 'hls') {
if (!hlsUrl.value) {
hideLoading();
uni.showToast({ title: '未获取到HLS流地址', icon: 'none' });
return;
}
setTimeout(() => {
initHlsPlayer(hlsUrl.value);
hideLoading();
}, 100);
}
};
5.2 页面初始化流程
// src/pages/monitor/detail/index.vue:466-520
const getDetail = async () => {
try {
// 1. 获取设备详情
const res = await getMonitorDetail({
indexCode: deviceCode.value,
});
deviceInfo.value = res.data || {};
console.log('获取监控详情', res.data);
// 2. 获取 WS 流地址(主协议)
const wsStreamRes = await getMonitorStreamUrl({
cameraIndexCode: deviceCode.value,
protocol: 'ws',
streamType: 0,
transmode: 1,
});
previewUrl.value = wsStreamRes.data.url || '';
console.log('获取WS流地址:', previewUrl.value);
// 3. 预获取 HLS 地址(备用)
// const hlsStreamRes = await getMonitorStreamUrl({
// cameraIndexCode: deviceCode.value,
// protocol: 'hls',
// streamType: 0,
// transmode: 1,
// });
// hlsUrl.value = hlsStreamRes.data.url || '';
// 4. 流地址有效性检查
if (!previewUrl.value) {
uni.showToast({ title: '获取视频流失败', icon: 'none' });
return;
}
// 5. 初始化播放器
await initPlayer();
if (myPlugin) {
playVideo(previewUrl.value);
}
} catch (error) {
console.error('获取监控信息失败:', error);
uni.showToast({ title: '加载失败,请重试', icon: 'none' });
hideLoading();
}
};
// 页面生命周期
onLoad(() => {
// #ifndef MP-WEIXIN
setNavTitle('查看监控');
// #endif
deviceCode.value = route.query.code as string || '';
// 时间显示
updateTime();
timer = setInterval(updateTime, 1000);
// 显示 loading 并开始加载
showLoading('监控连接中...');
getDetail();
});
5.3 降级策略实现
5.3.1 主动降级(推荐)
// 方案:初始化阶段检测并降级
const initPlayer = async () => {
try {
await loadJSPluginScript();
// ... 初始化逻辑
} catch (error) {
// 自动降级到 HLS
console.error('WS协议不可用,自动降级到HLS');
// 如果没有 HLS 地址,需要重新获取
if (!hlsUrl.value) {
await fetchHLSUrl();
}
if (hlsUrl.value) {
activeTab.value = 'hls';
initHlsPlayer(hlsUrl.value);
} else {
uni.showToast({
title: '暂无可播放的视频流',
icon: 'none'
});
}
}
};
// 获取 HLS 地址
const fetchHLSUrl = async () => {
try {
const hlsStreamRes = await getMonitorStreamUrl({
cameraIndexCode: deviceCode.value,
protocol: 'hls',
streamType: 0,
transmode: 1,
});
hlsUrl.value = hlsStreamRes.data.url || '';
} catch (error) {
console.error('获取HLS地址失败:', error);
}
};
5.3.2 播放失败降级
// 播放失败时自动切换
const playVideo = async (url: string) => {
if (!myPlugin) return;
try {
await myPlugin.JS_Play(url, { ... }, curIndex);
} catch (err) {
console.error('WS播放失败,尝试HLS降级');
// 确保有 HLS 地址
if (!hlsUrl.value) {
await fetchHLSUrl();
}
if (hlsUrl.value) {
activeTab.value = 'hls';
initHlsPlayer(hlsUrl.value);
}
}
};
5.3.3 预加载备用方案
// 在获取 WS 地址时,同时获取 HLS 地址
const getDetail = async () => {
try {
// 并行请求两个协议的流地址
const [wsRes, hlsRes] = await Promise.allSettled([
getMonitorStreamUrl({
cameraIndexCode: deviceCode.value,
protocol: 'ws',
streamType: 0,
transmode: 1,
}),
getMonitorStreamUrl({
cameraIndexCode: deviceCode.value,
protocol: 'hls',
streamType: 0,
transmode: 1,
})
]);
// 处理 WS 结果
if (wsRes.status === 'fulfilled') {
previewUrl.value = wsRes.value.data.url || '';
}
// 处理 HLS 结果
if (hlsRes.status === 'fulfilled') {
hlsUrl.value = hlsRes.value.data.url || '';
}
// 优先使用 WS
if (previewUrl.value) {
await initPlayer();
if (myPlugin) {
playVideo(previewUrl.value);
}
} else if (hlsUrl.value) {
// WS 失败,使用 HLS
activeTab.value = 'hls';
initHlsPlayer(hlsUrl.value);
} else {
uni.showToast({ title: '获取视频流失败', icon: 'none' });
}
} catch (error) {
console.error('获取监控信息失败:', error);
uni.showToast({ title: '加载失败,请重试', icon: 'none' });
}
};
六、资源清理与内存管理
6.1 资源泄漏风险点
| 风险点 | 影响 | 解决方案 |
|---|---|---|
| 定时器未清理 | 内存泄漏 | onHide/onUnload 清除 |
| 播放器实例未销毁 | Socket 连接持续 | 显式调用销毁方法 |
| HLS 事件未解绑 | 事件监听器累积 | destroy() 自动解绑 |
| DOM 引用残留 | 无法 GC | 置为 null |
| WebSocket 连接 | 后台持续消耗 | JS_Stop/JS_Destroy |
6.2 页面隐藏时的清理(onHide)
// src/pages/monitor/detail/index.vue:543-576
onHide(async () => {
console.log('页面隐藏,清理资源');
// 1. 清理定时器
if (timer) {
clearInterval(timer);
timer = null;
}
// 2. 隐藏 loading
hideLoading();
// 3. 停止 WS 播放
await stopVideo();
// 4. 销毁 WS 播放器实例
if (myPlugin) {
console.log('销毁WS播放器实例');
try {
// 确保调用 JS_Stop 停止播放
if (typeof myPlugin.JS_Stop === 'function') {
await myPlugin.JS_Stop(curIndex);
console.log('调用JS_Stop停止播放成功');
}
} catch (error) {
console.error('销毁播放器实例失败:', error);
} finally {
// 确保清空实例引用
myPlugin = null;
}
}
// 5. 销毁 HLS 实例
if (hls) {
console.log('销毁 HLS 实例');
hls.destroy();
hls = null;
}
});
6.3 页面卸载时的清理(onUnload)
// src/pages/monitor/detail/index.vue:578-630
onUnload(async () => {
console.log('页面卸载,清理资源');
// 1. 清理定时器
if (timer) {
clearInterval(timer);
timer = null;
}
// 2. 隐藏 loading
hideLoading();
// 3. 停止播放
await stopVideo();
// 4. 销毁 WS 播放器
if (myPlugin) {
console.log('销毁WS播放器实例');
try {
// 尝试多种销毁方法
if (typeof myPlugin.JS_Destroy === 'function') {
myPlugin.JS_Destroy();
} else if (typeof myPlugin.JS_Uninit === 'function') {
myPlugin.JS_Uninit();
}
} catch (error) {
console.error('销毁播放器实例失败:', error);
}
myPlugin = null;
}
// 5. 销毁 HLS
if (hls) {
console.log('销毁 HLS 实例');
hls.destroy();
hls = null;
}
});
清理顺序:
- 停止播放 → 2. 销毁实例 → 3. 清除引用 → 4. 清理定时器
七、降级处理策略详解
7.1 降级触发场景矩阵
| 降级场景 | 触发条件 | 降级方案 | 用户提示 |
|---|---|---|---|
| 脚本加载失败 | CDN 超时/跨域/网络错误 | 切换到 HLS | “WS协议播放器加载失败” |
| 播放器初始化失败 | JSPlugin 未定义/初始化异常 | 切换到 HLS | “WS协议播放器不可用” |
| 视频流获取失败 | API 返回空地址/网络错误 | 提示错误 | “获取视频流失败” |
| 播放失败 | JS_Play 调用失败 | 切换到 HLS | “播放失败” |
| HLS 播放失败 | manifest 解析错误 | 网络错误尝试恢复 | “HLS播放失败” |
| 浏览器不支持 | 无 MSE/原生 HLS | 提示不支持 | “浏览器不支持HLS” |
7.2 降级流程图
7.3 降级代码实现
// 完整的降级处理逻辑
const initializeVideoPlayer = async () => {
// 1. 尝试 WebSocket 协议
try {
console.log('尝试初始化 WebSocket 协议...');
await initPlayer();
// 检查播放器是否可用
if (myPlugin && previewUrl.value) {
await playVideo(previewUrl.value);
console.log('WebSocket 协议初始化成功');
return; // 成功,直接返回
}
throw new Error('播放器不可用');
} catch (wsError) {
console.warn('WebSocket 协议失败:', wsError);
// 2. 尝试 HLS 协议
try {
console.log('尝试初始化 HLS 协议...');
// 获取 HLS 地址(如果未预获取)
if (!hlsUrl.value) {
const hlsRes = await getMonitorStreamUrl({
cameraIndexCode: deviceCode.value,
protocol: 'hls',
streamType: 0,
transmode: 1,
});
hlsUrl.value = hlsRes.data.url || '';
}
if (!hlsUrl.value) {
throw new Error('未获取到 HLS 流地址');
}
// 切换到 HLS 标签
activeTab.value = 'hls';
// 初始化 HLS 播放器
await new Promise((resolve, reject) => {
try {
initHlsPlayer(hlsUrl.value);
// HLS 是异步的,稍后检查
setTimeout(() => {
const video = document.querySelector('video');
if (video && video.readyState >= 2) {
resolve(true);
} else {
reject(new Error('HLS 初始化超时'));
}
}, 2000);
} catch (error) {
reject(error);
}
});
console.log('HLS 协议初始化成功');
} catch (hlsError) {
console.error('HLS 协议也失败:', hlsError);
// 3. 完全失败,提示用户
uni.showToast({
title: '视频播放失败,请稍后重试',
icon: 'none',
duration: 3000
});
// 可选:记录错误日志
// reportError(wsError, hlsError);
} finally {
hideLoading();
}
}
};
7.4 降级用户体验优化
// 1. Loading 状态管理
const showLoading = (text = '加载中...') => {
uni.showLoading({ title: text, mask: true });
};
const hideLoading = () => {
uni.hideLoading();
};
// 2. 分阶段提示
const阶段性提示 = async () => {
showLoading('正在连接...');
try {
// 阶段1: 获取设备信息
showLoading('获取设备信息...');
await getDetail();
// 阶段2: 初始化播放器
showLoading('初始化播放器...');
await initializeVideoPlayer();
} catch (error) {
// 阶段3: 错误处理
console.error('播放流程失败:', error);
uni.showToast({
title: '视频加载失败',
icon: 'none'
});
} finally {
hideLoading();
}
};
// 3. 错误日志记录
const logPlaybackError = (stage: string, error: any, context: any) => {
console.error(`[${stage}] 错误详情:`, {
error: error.message || error,
context,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
});
// 可选:上报到监控平台
// reportToMonitoring({
// stage,
// error: error.message,
// deviceCode: deviceCode.value,
// timestamp: Date.now()
// });
};
八、平台兼容性处理
8.1 条件编译指令
<!-- 8.1.1 H5 平台专用结构 -->
<!-- #ifdef H5 -->
<div v-show="activeTab === 'hls'" class="hls-container">
<video
ref="videoRef"
class="hls-video"
controls
autoplay
muted
playsinline
/>
</div>
<!-- #endif -->
<!-- 8.1.2 非 H5 平台(小程序、App) -->
<!-- #ifndef H5 -->
<video
v-show="activeTab === 'hls'"
class="hls-video"
:src="hlsUrl"
controls
autoplay
muted
/>
<!-- #endif -->
<!-- 8.1.3 非微信小程序平台 -->
<!-- #ifndef MP-WEIXIN -->
<view class="tab-container">
<!-- Tab 切换控件 -->
</view>
<!-- #endif -->
8.2 VConsole 处理(H5 开发环境)
// 仅在 H5 开发环境启用 VConsole
// #ifdef H5
if (import.meta.env.DEV && import.meta.env.VITE_APP_ENV === 'development') {
import('vconsole').then((VConsole) => {
new VConsole.default();
console.log('VConsole 已启用');
});
}
// #endif
九、关键代码片段汇总
9.1 核心状态管理
// 播放器实例
let myPlugin: any = null; // WebSocket 播放器
let hls: Hls | null = null; // HLS 播放器
// 视频流地址
const previewUrl = ref<string>(''); // WS 地址
const hlsUrl = ref<string>(''); // HLS 地址
// 当前状态
const activeTab = ref<'ws' | 'hls'>('ws'); // 当前协议
const deviceInfo = ref<deviceType>(); // 设备信息
const currentTime = ref(''); // 当前时间
let timer: any = null; // 时间定时器
// DOM 引用
const videoRef = ref(); // HLS 视频元素
9.2 完整生命周期管理
// 页面加载
onLoad(() => {
deviceCode.value = route.query.code as string || '';
// 时间显示
updateTime();
timer = setInterval(updateTime, 1000);
// 开始加载
showLoading('监控连接中...');
getDetail();
});
// 页面显示
onShow(() => {
// 可用于恢复播放
console.log('页面显示');
});
// 页面隐藏
onHide(async () => {
console.log('页面隐藏,清理资源');
if (timer) clearInterval(timer);
hideLoading();
await stopVideo();
if (myPlugin) myPlugin = null;
if (hls) {
hls.destroy();
hls = null;
}
});
// 页面卸载
onUnload(async () => {
console.log('页面卸载,清理资源');
if (timer) clearInterval(timer);
hideLoading();
await stopVideo();
if (myPlugin) {
try {
if (typeof myPlugin.JS_Destroy === 'function') {
myPlugin.JS_Destroy();
}
} catch (error) {
console.error('销毁失败:', error);
}
myPlugin = null;
}
if (hls) {
hls.destroy();
hls = null;
}
});
十、方案优缺点分析
10.1 优势(Pros)
✅ 技术优势
-
双协议架构,可靠性高
- WebSocket 作为主协议,提供低延迟体验
- HLS 作为降级方案,确保在任何情况下都能播放
- 失败自动切换,用户无感知
-
动态加载,性能优化
- JSPlugin 按需加载,减少首屏资源体积
- 避免不必要的脚本加载,提升页面加载速度
- 仅在 H5 环境加载,其他平台无负担
-
完善的资源管理
- 页面隐藏/卸载时彻底清理资源
- 防止内存泄漏和后台连接消耗
- 多重保障确保 Socket 连接关闭
-
错误处理完善
- 多层错误捕获(加载、初始化、播放)
- 详细的错误日志记录
- 用户友好的提示信息
-
平台兼容性好
- 使用 uni-app 条件编译
- H5、小程序、App 多端支持
- 根据平台自动选择最佳协议
✅ 业务优势
-
用户体验流畅
- Loading 状态明确提示
- 降级过程对用户透明
- 支持手动切换协议
-
可维护性强
- 代码结构清晰,职责分离
- 配置集中管理
- 易于扩展新协议
10.2 缺点(Cons)
❌ 技术局限性
-
WebSocket 协议依赖性强
- 问题:严重依赖
JSPlugin.js,该脚本必须通过 CDN 加载 - 影响:
- CDN 不可用时完全无法使用 WebSocket
- 跨域问题可能导致加载失败
- 网络环境差时加载超时
- 缓解:HLS 降级方案
- 问题:严重依赖
-
HLS 协议延迟较高
- 问题:HLS 延迟通常在 3-10 秒,不适合实时性要求高的场景
- 影响:
- 用户操作反馈慢
- 不适合需要快速响应的监控场景
- 缓解:使用 WebSocket 作为首选
-
平台限制明显
- 问题:JSPlugin 仅支持 H5 平台
- 影响:
- 微信小程序无法使用 WebSocket
- App 端需要原生开发或额外 SDK
- 跨平台一致性差
- 缓解:小程序强制使用 HLS,App 端可考虑原生方案
-
浏览器兼容性问题
- 问题:HLS.js 依赖 MSE (Media Source Extensions)
- 影响:
- IE11 及以下完全不支持
- 部分国产浏览器内核不完整
- iOS Safari 需要原生支持
- 缓解:多重降级策略
十一、 对接过程遇到的问题
11.1 hls个别视频无法播放
解决方案:确实海康平台视频编码是否为h264,hls协议对编码格式要求高,最好是视频编码h264,若有音频 最好ACC格式,而且推流的视频不保证100%播放,有问题可以询问海康官方对接群。
11.2 ws协议视频无法播放
解决方案:确认控制台是否报错,因为海康用官方插件的报错几乎在控制台都有错误码,去海康开放平台发给在线ai客服 几乎都能找到解决方案。我这遇到的主要就是媒体网关问题,要更高的版本,要找运维与海康那边对接升级。
11.3 https或者wss无法播放
解决方案:1.确认海康那边是否支持,2. 去海康申请媒体的ssl证书,在海康的运营中心上传证书。无法播放九成就是证书的问题
11.4 vpn权限
当时同事对接的时候一直无法播放,最后发现vpn权限不足的话也会影响到视频的正常取流播放。因为甲方监控部署在内网中。
十二、 nginx部署
因为海康插件包太大,要上政府平台会因为体积包太大无法上传,此时就要提前先上传到服务器,然后通过src远程加载
你可以先放置在与项目平级的目录中,我的是单独创建了plugins中,


nginx映射的时候要注意,海康插件在h264都是正常的,但是检测到h265视频编码时,会自动去兼容decoderWorker文件播放,这时候要后端映射好nginx文件,否则会控制台报跨域问题

十三、总结
13.1 方案核心价值
一句话总结:通过 WebSocket + HLS 双协议架构,配合完善的降级策略和资源管理,实现海康监控在 uni-app 中的稳定播放。
三大核心:
- 可靠性:双协议互为备份,确保 99% 的可用性
- 性能:动态加载 + 资源清理,保证流畅体验
- 兼容性:多平台适配,覆盖主流场景
13.2 适用场景
| 场景 | 推荐度 | 说明 |
|---|---|---|
| H5 监控页面 | ⭐⭐⭐⭐⭐ | 完美支持,体验最佳 |
| 微信小程序 | ⭐⭐⭐⭐ | 使用 HLS,延迟可接受 |
| App 内嵌 H5 | ⭐⭐⭐⭐ | 依赖 WebView 能力 |
| 跨平台应用 | ⭐⭐⭐ | 需要额外适配工作 |
| 实时性要求极高 | ⭐⭐ | 建议原生开发 |

被折叠的 条评论
为什么被折叠?



