Fork me on GitHub

萤石云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:开始时间,格式为 yyyyMMddHHmmss
  • stopTime:结束时间,格式为 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 协议,具有以下优势:

  1. 编码兼容性最强:EZOPEN 协议既支持 H.264,也支持 H.265,无需担心设备编码格式
  2. 无需额外配置:萤石 JSSDK 已内置 H.265 解码能力
  3. 跨设备兼容:无论设备出厂默认使用哪种编码,都可以正常播放

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

POST /api/lapp/token/get

获取授权令牌,这是调用其他所有API的前提。

请求参数:

参数 类型 必填 说明
appKey String 应用Key
appSecret String 应用Secret

返回示例:

{
  "code": "200",
  "msg": "操作成功",
  "data": {
    "accessToken": "at.xxx",
    "expireTime": 1609459200
  }
}

获取设备列表

POST /api/lapp/device/list

获取当前账号下的所有设备列表。

请求参数:

参数 类型 必填 说明
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')

授权方式

方式一:直接授权

使用 appKeySecret 直接调用API,只能操作同一账号下的设备。

方式二:设备托管

允许其他萤石用户授权设备供开发者管理,实现跨账号设备控制。

注意事项

  1. Token管理:AccessToken有效期约7天,后端做了缓存和自动刷新
  2. 时间格式:回放接口的 startTime 和 stopTime 必须是 yyyyMMddHHmmss 格式
  3. 安全合规:遵守平台使用协议,确保数据安全
  4. 协议选择:本项目使用萤石官方 JSSDK,无需自行解析视频流
  5. 视频加密:如果设备开启了视频加密,需要传入正确的 code(加密密码)才能获取到有效地址

相关资源


posted @ 2026-05-22 21:26  秋夜雨巷  阅读(41)  评论(0)    收藏  举报