萤石云NVR开发
萤石开放平台接入方案总结
萤石开放平台是海康威视旗下的IoT物联网开放平台,为开发者提供设备接入、视频监控、云存储等一站式解决方案。
平台概述
萤石开放平台支持市面上大部分摄像头、录像机、直播一体机等设备接入云端,提供从设备管理到视频应用的完整API能力。
核心功能模块
1. 设备管理
| 功能 | 说明 |
|---|---|
| 设备接入 | 支持摄像头、录像机、直播一体机等设备接入云端 |
| 基础管理 | 设备命名、分组、增删改查等操作 |
| 设备控制 | 云台控制、物理变焦、抽帧等功能 |
| 消息上报 | 设备报警、视频报警、设备故障报警等事件通知 |
2. 视频监控
实时预览
支持多种视频流协议:
| 协议 | 延迟 | 适用场景 |
|---|---|---|
| HLS | 2~10秒 | Web端、移动端直播 |
| RTSP | 低延迟 | 内网监控、PC客户端 |
| HTTP-FLV | 低延迟 | Web端直播 |
| WebRTC | <500ms | 远程控制、视频会议 |
| EZOPEN | 私有协议 | 萤石专用协议 |
云通话
- 实时对讲
- 云端广播
视频回放
支持从以下来源获取视频片段进行回放:
- SD卡
- 硬盘录像机
- 云存储
本项目接入方案
架构设计
前端 (Vue) → 后端 (Java) → 萤石开放平台
↓
只调用一个接口
/api/lapp/token/get ([官方文档](https://open.ys7.com/help/672))
本项目不直接调用萤石API,而是后端封装后提供统一的播放地址接口。
萤石API
| 接口 | 功能 | 调用位置 | 官方文档 |
|---|---|---|---|
/api/lapp/token/get |
获取AccessToken | 后端获取播放地址前 | 查看 |
/api/lapp/device/list |
获取设备列表 | 可用于设备同步 | 查看 |
/api/v3/device/searchDeviceInfo |
搜索设备信息 | v3版本接口 | 查看 |
/api/lapp/device/camera/list |
获取设备通道列表 | 查询NVR通道 | 查看 |
/api/lapp/v2/live/address/get |
获取直播地址 | 核心接口 | 查看 |
ezopen 协议播放地址格式
萤石的 ezopen 协议是私有协议,用于拼接播放地址。
实时预览地址
ezopen://open.ys7.com/{设备序列号}/{通道号}.live
示例:
ezopen://open.ys7.com/ABC123456/1.live
视频回放地址
ezopen://open.ys7.com/{设备序列号}/{通道号}.rec?startTime={开始时间}&stopTime={结束时间}
参数说明:
startTime:开始时间,格式为yyyyMMddHHmmssstopTime:结束时间,格式为yyyyMMddHHmmss
示例:
ezopen://open.ys7.com/ABC123456/1.rec?startTime=20240520100000&stopTime=20240520120000
视频编码格式兼容性
萤石平台对视频编码格式的支持情况:
| 协议类型 | H.264 支持 | H.265 支持 |
|---|---|---|
| EZOPEN 私有协议 | ✅ 支持 | ✅ 支持 |
| HLS | ✅ 支持 | ❌ 不支持 |
| RTMP | ✅ 支持 | ❌ 不支持 |
| HTTP-FLV | ✅ 支持 | ❌ 不支持(需 SDK 支持) |
注意: 标准流协议(HLS/RTMP/FLV)仅支持 H.264 编码的设备,如设备为 H.265 编码会报错"视频编码非H264"。
本项目方案的优势:
本项目使用萤石官方 JSSDK 播放页面 + EZOPEN 协议,具有以下优势:
- 编码兼容性最强:EZOPEN 协议既支持 H.264,也支持 H.265,无需担心设备编码格式
- 无需额外配置:萤石 JSSDK 已内置 H.265 解码能力
- 跨设备兼容:无论设备出厂默认使用哪种编码,都可以正常播放
H.265 的优势:
H.265(HEVC)相比 H.264 在相同图像质量下码流减少约 39%-44%,有利于降低带宽需求和存储空间。
萤石官方播放器播放ezopen协议播放地址
本项目使用萤石官方的 JSSDK 播放页面,无需自行解析视频流:
| 场景 | 播放器页面 |
|---|---|
| 实时预览 | https://open.ys7.com/jssdk/theme.html |
| 视频回放 | https://open.ys7.com/console/jssdk/pc.html |
完整播放地址拼接规则
后端返回给前端的完整播放地址格式:
{萤石播放器地址}?accessToken={token}&url={ezopen地址}
示例:
# 实时预览
https://open.ys7.com/jssdk/theme.html?accessToken=at.xxx&url=ezopen://open.ys7.com/ABC123456/1.live
# 视频回放
https://open.ys7.com/console/jssdk/pc.html?accessToken=at.xxx&url=ezopen://open.ys7.com/ABC123456/1.rec?startTime=20240520100000&stopTime=20240520120000
前端通过 window.open(url) 直接打开萤石官方播放器页面即可播放。
API 接口详情
获取Token
获取授权令牌,这是调用其他所有API的前提。
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| appKey | String | 是 | 应用Key |
| appSecret | String | 是 | 应用Secret |
返回示例:
{
"code": "200",
"msg": "操作成功",
"data": {
"accessToken": "at.xxx",
"expireTime": 1609459200
}
}
获取设备列表
获取当前账号下的所有设备列表。
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| accessToken | String | 是 | 授权令牌 |
返回示例:
{
"code": "200",
"msg": "操作成功",
"data": [
{
"deviceSerial": "设备序列号",
"deviceName": "设备名称",
"deviceType": 1,
"status": 1,
"isEncrypt": 0,
"category": "camera"
}
]
}
搜索设备信息(v3接口)
POST /api/v3/device/searchDeviceInfo
根据条件搜索设备信息,v3版本接口。
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| accessToken | String | 是 | 授权令牌 |
| deviceSerial | String | 否 | 设备序列号 |
| status | Integer | 否 | 设备状态筛选 |
返回示例:
{
"meta": {
"code": "200",
"message": "操作成功"
},
"data": [
{
"deviceSerial": "设备序列号",
"deviceName": "设备名称",
"status": 1
}
]
}
获取设备通道列表
POST /api/lapp/device/camera/list
获取指定设备下的通道列表,适用于NVR等支持多通道的设备。
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| accessToken | String | 是 | 授权令牌 |
| deviceSerial | String | 是 | 设备序列号 |
返回示例:
{
"code": "200",
"msg": "操作成功",
"data": [
{
"channelNo": 1,
"channelName": "通道1",
"channelType": 0,
"status": 1,
"videoLevel": 0
},
{
"channelNo": 2,
"channelName": "通道2",
"channelType": 0,
"status": 0,
"videoLevel": 0
}
]
}
通道字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| channelNo | Integer | 通道号 |
| channelName | String | 通道名称 |
| channelType | Integer | 通道类型(0=模拟通道,1=数字通道) |
| status | Integer | 通道状态(0=离线,1=在线) |
| videoLevel | Integer | 视频清晰度(0=流畅,1=均衡,2=高清) |
获取直播地址(v2版本)
POST /api/lapp/v2/live/address/get
获取设备的直播播放地址,v2版本接口,支持多种流协议。
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| accessToken | String | 是 | 授权令牌 |
| deviceSerial | String | 是 | 设备序列号 |
| channelNo | Integer | 否 | 通道号,默认1 |
| protocol | Integer | 否 | 流播放协议,默认1 |
| quality | Integer | 否 | 视频清晰度:1=高清,2=流畅 |
| type | Integer | 否 | 地址类型:1=预览,2=本地录像回放,3=云存储录像回放 |
| startTime | String | 否 | 回放开始时间,格式:yyyy-MM-dd HH:mm:ss |
| stopTime | String | 否 | 回放结束时间,格式:yyyy-MM-dd HH:mm:ss |
| code | String | 否 | 视频加密密码(设备开启视频加密时必填) |
| expireTime | Integer | 否 | 地址有效期(秒),范围30秒-7天 |
protocol 参数取值:
| 值 | 协议 | H.264 支持 | H.265 支持 |
|---|---|---|---|
| 1 | ezopen | ✅ 支持 | ✅ 支持 |
| 2 | hls | ✅ 支持 | ❌ 不支持 |
| 3 | rtmp | ✅ 支持 | ❌ 不支持 |
| 4 | flv | ✅ 支持 | ❌ 不支持 |
quality 参数取值:
| 值 | 清晰度 | 说明 |
|---|---|---|
| 1 | 高清 | 主码流,画质好,开启速度较慢 |
| 2 | 流畅 | 子码流,画质一般,开启速度快 |
type 参数取值:
| 值 | 类型 | 说明 |
|---|---|---|
| 1 | 预览 | 实时预览(默认) |
| 2 | 本地录像回放 | 回放设备本地存储的录像 |
| 3 | 云存储录像回放 | 回放云存储的录像 |
返回示例:
{
"code": "200",
"msg": "操作成功",
"data": {
"id": "直播任务ID",
"url": "ezopen://open.ys7.com/设备序列号/1.live",
"expireTime": 1609459200000
}
}
返回 url 格式说明:
| type | 返回格式 | 示例 |
|---|---|---|
| 1(预览) | 实时预览地址 | ezopen://open.ys7.com/设备序列号/1.live |
| 2(本地回放) | 本地录像回放地址 | ezopen://open.ys7.com/设备序列号/1.rec?startTime=yyyyMMddHHmmss&stopTime=yyyyMMddHHmmss |
| 3(云存储回放) | 云存储录像回放地址 | ezopen://open.ys7.com/设备序列号/1.cloud?startTime=yyyyMMddHHmmss&stopTime=yyyyMMddHHmmss |
返回字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | 直播任务标识符 |
| url | String | 直播地址 |
| expireTime | Long | 地址过期时间戳(毫秒) |
常见报错:
- "视频编码非H264":设备为 H.265 编码,使用标准流协议时会出现。解决方案:改用 EZOPEN 协议(protocol=1)
后端实现
Token 获取与缓存
// 核心代码参考 WebcamController.java
private final Map<Integer, YsTokenCache> ysTokenCache = new ConcurrentHashMap<>();
private String getYsAccessToken(WebcamConf conf) {
// 1. 检查缓存,Token有效期约7天,提前1分钟刷新
YsTokenCache cache = ysTokenCache.get(conf.getId());
long now = System.currentTimeMillis();
if (cache != null && cache.expireTime > now + 60_000) {
return cache.accessToken;
}
// 2. 调用萤石API获取Token
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("appKey", conf.getCamPlatformAppkey());
form.add("appSecret", conf.getCamPlatformAppsecret());
String tokenUrl = conf.getCamPlatformUrl();
if (tokenUrl == null || tokenUrl.trim().isEmpty()) {
tokenUrl = "https://open.ys7.com/api/lapp/token/get";
}
String response = restTemplate.postForObject(tokenUrl, new HttpEntity<>(form, headers), String.class);
// 3. 解析Token并缓存
JsonNode root = objectMapper.readTree(response);
String code = root.path("code").asText();
if (!"200".equals(code)) {
throw new IllegalStateException(root.path("msg").asText("获取萤石云Token失败"));
}
String accessToken = root.path("data").path("accessToken").asText();
long expireTime = root.path("data").path("expireTime").asLong(now + 60 * 60 * 1000);
ysTokenCache.put(conf.getId(), new YsTokenCache(accessToken, expireTime));
return accessToken;
}
播放地址获取接口
@GetMapping("/play-url/{id}")
public CommonResult<Map<String, String>> playUrl(
@PathVariable Long id,
@RequestParam(defaultValue = "1") int channelNo,
@RequestParam(defaultValue = "false") boolean isPlayback,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String stopTime) {
// ... 权限校验和参数校验 ...
String token = getYsAccessToken(conf);
String deviceSerial = webcam.getYsDeviceSerial().trim();
String ezopenUrl;
String baseUrl;
if (isPlayback) {
// 视频回放:使用 .rec 后缀
ezopenUrl = String.format(
"ezopen://open.ys7.com/%s/%d.rec?startTime=%s&stopTime=%s",
deviceSerial, channelNo, startTime, stopTime);
baseUrl = "https://open.ys7.com/console/jssdk/pc.html?accessToken=" + token;
} else {
// 实时预览:使用 .live 后缀
ezopenUrl = String.format("ezopen://open.ys7.com/%s/%d.live", deviceSerial, channelNo);
baseUrl = "https://open.ys7.com/jssdk/theme.html?accessToken=" + token;
}
String fullUrl = baseUrl + "&url=" + ezopenUrl;
Map<String, String> data = new HashMap<>();
data.put("url", fullUrl);
return CommonResult.success(data);
}
前端调用
API 定义
// src/api/video/webcam.ts
export interface PlayUrlParams {
channelNo: number
isPlayback: boolean
startTime?: string
stopTime?: string
}
export const getWebcamPlayUrl = (id: number, params: PlayUrlParams) => {
return request.get({ url: '/webcam/play-url/' + id, params })
}
调用示例
// 实时预览
const data = await getWebcamPlayUrl(webcamId, {
channelNo: 1,
isPlayback: false
})
window.open(data.url, '_blank')
// 视频回放(默认回放最近2小时)
const endTime = new Date()
const startTime = new Date(endTime.getTime() - 2 * 60 * 60 * 1000)
const formatTime = (d: Date) => {
const pad = (n: number) => String(n).padStart(2, '0')
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`
}
const data = await getWebcamPlayUrl(webcamId, {
channelNo: 1,
isPlayback: true,
startTime: formatTime(startTime),
stopTime: formatTime(endTime)
})
window.open(data.url, '_blank')
授权方式
方式一:直接授权
使用 appKey 和 Secret 直接调用API,只能操作同一账号下的设备。
方式二:设备托管
允许其他萤石用户授权设备供开发者管理,实现跨账号设备控制。
注意事项
- Token管理:AccessToken有效期约7天,后端做了缓存和自动刷新
- 时间格式:回放接口的 startTime 和 stopTime 必须是
yyyyMMddHHmmss格式 - 安全合规:遵守平台使用协议,确保数据安全
- 协议选择:本项目使用萤石官方 JSSDK,无需自行解析视频流
- 视频加密:如果设备开启了视频加密,需要传入正确的
code(加密密码)才能获取到有效地址
相关资源


浙公网安备 33010602011771号