一、周六早上,邻居电话比鸡还早
周六七点,城里手机响了。乡下邻居说:院门开着,鸡在路上啄菜。你打开那只「能看院子」的 App——设备离线。再拨村里的侄子,他回:「Wi-Fi 是隔壁借的,路由器上周就没电。」
院子里其实已经挂着户外机。缺的不是镜头,是一条不靠邻家宽带、还能嵌进自家小程序的远程看路径:4G 插卡上电,云端签发可播地址,家人点开就能看。
二、为什么「院子能看」值得单独做成一条工程路径
2.1 庭院 / 小农场不是「再装一路监控」
城里看乡下院子,真正卡住的通常是这四件事同时成立:
- 没网:菜地、鸡圈、院门很少有稳定光纤或 Wi-Fi。
- 人远:业主在市区,侄子在镇上,要看的是同一路画面。
- 流量贵:4G 卡按量计费,主码流 24 小时裸奔,月账单比鸡还贵。
- 要进自己的页:家人要嵌进小程序 / 公众号 H5,而不是每人再下一套 App。
开放平台侧,和这条路径直接相关的现行能力可以收成一张表:
| 业务问题 | 现行能力 / 接口 | 庭院场景怎么用 |
|---|---|---|
| 院子没宽带 | 4G 物联网卡:插卡联网,适用智慧养殖 / 户外点位 | 户外 4G 机上电即上云,先不要挖沟 |
| 设备在不在、在不在线 | listDeviceDetailsByPage | 台账过滤 deviceStatus + channelStatus 都为 online |
| 家人点开看现在 | bindDeviceLive(streamId=1)+ getLiveStreamInfo | 默认辅码流 HTTPS HLS,嵌 H5 |
| 别 24 小时烧流量 | batchModifyLivePlan / modifyLivePlan + modifyLivePlanStatus | 只在早饲、午巡、晚归开流 |
| Web 值班墙要更低延迟 | createDeviceFlvLive 或 getKitToken + ImouPlayer | 业主后台第二路径,不要和家人 H5 揉进同一个 <video> |
选型不要一上来啃私有协议。开发总览里的对接分层,用在庭院远程看也成立:
| 路径 | 关键接口 / 组件 | 延迟体感 | 对接成本 | 庭院建议 |
|---|---|---|---|---|
| 云直播 HLS | bindDeviceLive | 较高(秒级偏上) | 极低 | 家人「看一眼」主路径 |
| 云直播 FLV | createDeviceFlvLive | 通常优于 HLS | 低 | 业主 Web 值班墙第二选择 |
| 轻应用 | getKitToken + imouPlayer | PC 约 2~3s 量级 | 低~中 | 要回放 / 云台时再上 |
| 移动 OpenSDK | 客户端 SDK | 更低 | 高 | 要做原生农事 App 再上 |
本文主线:4G 接入 + 列表验收 online + 辅码流 HLS + 看园档期。对讲、云台深度控制、告警流水线另文展开,避免第一周范围膨胀。
2.2 解决思路:把「卡、设备、流、人」拆成四层
┌──────────────────┐ ┌────────────────┐ ┌──────────────────────┐
│ 户外 4G 机 + 物联网卡 │ → │ 开发者资产池 │ → │ 现行 OpenAPI 出流 │
│ 上电 / 插卡 │ │ bind / 列表 │ │ HLS / FLV / kitToken │
└──────────────────┘ └────────────────┘ └──────────┬───────────┘
│
▼
┌──────────────────────┐
│ 农事业务后端(BFF) │
│ 院落↔设备映射 + 鉴权 │
│ 家人 H5 / 小程序 web-view │
└──────────────────────┘
业务同学不该关心这路是 4G 还是将来换成光纤——他们只该知道:
- 这路在不在线(
deviceStatus/channelStatus); - 怎么拿到可播地址(优先辅码流 HTTPS);
- 谁有权看这一路(你们自己的登录态,不是把
m3u8写死在前端)。
一句话:4G 解决「连得上」;云直播解决「进你的系统、按你的档期播」;业务后端解决「谁能看哪个院子」。三者缺一,邻居电话来了你还是只能干瞪眼。
三、从插卡上电到 H5 出画
3.1 端到端主流程
flowchart TB
subgraph A[现场接入]
S1[户外 4G 机插物联网卡上电]
S2[确认设备联网 / App 可看]
S3[开发者账号绑定设备]
end
subgraph B[云端资产]
T[accessToken]
L[listDeviceDetailsByPage]
M[(本地表: yardId / deviceId / channelId / liveToken)]
end
subgraph C[出流消费]
H[bindDeviceLive streamId=1]
I[getLiveStreamInfo 取 HTTPS]
P[batchModifyLivePlan 看园档期]
W[家人 H5 / 小程序 web-view]
end
S1 --> S2 --> S3 --> L
T --> L --> M
M --> H --> I --> W
H --> P
环境约定:Node.js ≥ 18(自带 fetch / crypto)。先在 open.imou.com 创建应用,于「控制台 → 我的应用 → 应用信息」取 appId / appSecret。
mkdir farm-yard-live && cd farm-yard-live
npm init -y
# .env 永远不要写成 VITE_ / REACT_APP_ 前缀,否则会被打进前端包
IMOU_APP_ID=lcdxxxxxxxxx
IMOU_APP_SECRET=your_secret
DEVICE_ID=TESTQWERXXXX
DEVICE_CODE= # 未改密:标签/二维码 8 位安全码;已改密:新密码
3.2 现行签名壳:HMAC-SHA256,不要抄网上的 MD5
请求统一:
POST https://openapi.lechange.cn/openapi/{method}
开发规范 现行算法分三步:
原始串 = time:{秒},nonce:{随机串},appSecret:{密钥}
password = lowercase(hex(SHA-256(appSecret)))
sign = Base64(HMAC-SHA256(原始串, password))
time 是 UTC 秒级时间戳,与服务器误差超过 5 分钟会返回 SN1002;nonce 五分钟内不能复用,否则 SN1005。官方标准案例可自测:time=1706511734、nonce=f5a1ae2d-c09c-4d39-a744-83a5c2c653c2、appSecret=test123456789test123456789,算出来的 sign 必须是:
xjhCQBoJ9hRDsCjyDcHjtDNzRZ3ZJezcawsfWeiaoxU=
对不上,先别调任何业务接口。网上大量「MD5 32 位小写」示例对应的是已不维护的旧协议,新接入按现行规范走 HMAC。
// server/imou-client.js
import crypto from 'node:crypto';
import { randomUUID } from 'node:crypto';
const OPENAPI = 'https://openapi.lechange.cn/openapi';
function calcSign(time, nonce, appSecret) {
const raw = `time:${time},nonce:${nonce},appSecret:${appSecret}`;
const password = crypto.createHash('sha256').update(appSecret, 'utf8').digest('hex');
return crypto.createHmac('sha256', password).update(raw, 'utf8').digest('base64');
}
export async function callOpenApi(method, appId, appSecret, params = {}) {
const time = Math.floor(Date.now() / 1000);
const nonce = randomUUID();
const body = {
system: { ver: '1.0', appId, time, nonce, sign: calcSign(time, nonce, appSecret) },
id: randomUUID(),
params,
};
const res = await fetch(`${OPENAPI}/${method}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const json = await res.json();
if (String(json?.result?.code) !== '0') {
const err = new Error(`${method} failed: ${json?.result?.code} ${json?.result?.msg}`);
err.payload = json;
throw err;
}
return json.result.data;
}
解释:appSecret 只出现在这一层。浏览器、小程序、埋点日志里都不该出现它。乡下机房或云主机都要开 NTP,避免整点前后批量 SN1002。
自测脚本(先跑通再写业务):
// sign-selfcheck.js
import crypto from 'node:crypto';
const time = '1706511734';
const nonce = 'f5a1ae2d-c09c-4d39-a744-83a5c2c653c2';
const appSecret = 'test123456789test123456789';
const raw = `time:${time},nonce:${nonce},appSecret:${appSecret}`;
const password = crypto.createHash('sha256').update(appSecret, 'utf8').digest('hex');
const sign = crypto.createHmac('sha256', password).update(raw, 'utf8').digest('base64');
console.log(sign);
// 期望: xjhCQBoJ9hRDsCjyDcHjtDNzRZ3ZJezcawsfWeiaoxU=
踩坑 1:从旧博客抄来 md5(time:...,nonce:...,appSecret:...),标准案例对不上,后面所有接口都会 SN1001。不是设备坏了,是签名壳过期了。
3.3 Step 1:拿管理员 accessToken(约 3 天有效)
accessToken:params 可空;返回 accessToken 与 expireTime(剩余秒数)。有效期约 3 天;过期或 TK1002 再取。超过 2 天再请求会拿到新 token,新旧各自独立可用,但不要为了「刷新」而打满调用次数。
// server/access-token.js
import { callOpenApi } from './imou-client.js';
let cache = { value: '', expireAt: 0 };
export async function getAccessToken(appId, appSecret) {
const now = Date.now();
if (cache.value && now < cache.expireAt) return cache.value;
const data = await callOpenApi('accessToken', appId, appSecret, {});
cache = {
value: data.accessToken,
// expireTime 是剩余秒;提前 2 小时刷新,避开临界窗
expireAt: now + (Number(data.expireTime) - 7200) * 1000,
};
return cache.value;
}
踩坑 2:每次家人点开页面都打一次 accessToken。文档写得很清楚:每个管理员 token 具备独立的 3 天生命周期,频繁调用会占配额。缓存即可。
3.4 Step 2:现场 4G 上电 + 绑定进开发者资产
现场 SOP(可直接贴进实施手册):
1. 物联网卡入网开通(控制台申请 / 运营商侧激活,以现场卡类型为准)
2. 卡插入户外 4G 机,上电;等待指示灯进入可联网状态
3. 用开发者主账号在乐橙 App / 控制台确认设备可预览(排除「卡没流量」伪故障)
4. 将设备绑定到开放平台应用对应的开发者账号
- 控制台绑定,或
- HTTP:bindDevice(部分新设备可能需结合客户端 SDK 完成绑定)
5. listDeviceDetailsByPage 验收 deviceStatus === online
bindDevice 参数要点:
| 参数 | 说明 |
|---|---|
token | 管理员 accessToken |
deviceId | 设备序列号 |
code | 未改密:标签 / 二维码 8 位安全码;已改密:新密码;无 8 位安全码且未改密:可传空 |
encryptCode | 可选,与 code 二选一,安全要求高时用文档给出的 AES 规则加密 |
// bind-device.js
import { callOpenApi } from './server/imou-client.js';
import { getAccessToken } from './server/access-token.js';
const APP_ID = process.env.IMOU_APP_ID;
const APP_SECRET = process.env.IMOU_APP_SECRET;
async function main() {
const token = await getAccessToken(APP_ID, APP_SECRET);
await callOpenApi('bindDevice', APP_ID, APP_SECRET, {
token,
deviceId: process.env.DEVICE_ID,
code: process.env.DEVICE_CODE || '',
});
console.log('bindDevice ok');
}
main().catch(console.error);
踩坑 3(真实发生过):侄子用私人乐橙号在 App 里加设备,开发者侧 listDeviceDetailsByPage 永远 0 台,家人页写「暂无画面」。
处理:绑定必须落在公司 / 业主开发者主账号(或明确托管 / 分享链路),再同步资产。App 能看、列表没有,多半还没绑进开发者账号。
3.5 Step 3:分页台账——「设备是否真在线」
分页查询设备详细信息 是现行列表接口。page 从 1 起,pageSize 为 1–50;source 默认 bindAndShare。返回数组字段叫 deviceList,通道在 channelList——不要去调已停维护栏目里的旧列表方法名。
// server/list-yard-devices.js
import { callOpenApi } from './imou-client.js';
import { getAccessToken } from './access-token.js';
export async function listAllDevices(appId, appSecret) {
const token = await getAccessToken(appId, appSecret);
const pageSize = 50;
let page = 1;
const all = [];
while (true) {
const data = await callOpenApi('listDeviceDetailsByPage', appId, appSecret, {
token,
pageSize,
page,
source: 'bindAndShare', // bind | share | bindAndShare
});
const list = data.deviceList || [];
all.push(...list);
if (list.length < pageSize) break;
page += 1;
}
return all;
}
export function toYardRows(devices) {
return devices.map((d) => ({
deviceId: d.deviceId,
name: d.deviceName,
model: d.deviceModel,
catalog: d.catalog, // IPC / NVR …
status: d.deviceStatus, // online | offline | sleep | upgrading
channels: (d.channelList || []).map((c) => ({
channelId: c.channelId,
channelStatus: c.channelStatus,
cameraStatus: c.cameraStatus, // on=遮罩打开(画面被盖)
abilities: c.channelAbility,
})),
}));
}
业务库建议最小字段:
-- 示意:院落点位与云端设备映射
CREATE TABLE yard_camera (
id BIGINT PRIMARY KEY,
yard_id VARCHAR(64) NOT NULL, -- 院落 / 地块编号
point_name VARCHAR(128), -- 院门 / 鸡圈 / 菜地
device_id VARCHAR(64) NOT NULL,
channel_id VARCHAR(16) NOT NULL DEFAULT '0',
live_token VARCHAR(64),
prefer_stream INT NOT NULL DEFAULT 1, -- 4G 默认辅码流
UNIQUE (yard_id, device_id, channel_id)
);
踩坑 4:只看 deviceStatus=online,忽略通道 channelStatus。少数场景设备在线但通道休眠 / 异常,出流仍失败。家人页过滤条件建议两者都过。cameraStatus === "on" 时遮罩是盖上的,播放器出画也会是一块隐私盖,别当成组件坏了。sleep 在户外 4G 机上很常见——省电策略把通道睡过去,列表不是「没这台」,是「这台在睡觉」。
3.6 Step 4:最小出画——bindDeviceLive 辅码流
4G 流量金贵:家人预览一律 streamId: 1(标清辅码流);业主要看鸡脚环、看菜叶病斑,再临时切 0。
bindDeviceLive 要点:
- URL:
https://openapi.lechange.cn/openapi/bindDeviceLive - 必填:
token、deviceId、channelId、streamId(0 主 / 1 辅) liveMode可不填或固定"proxy"- 后台会默认创建 主/辅码流 × HTTP/HTTPS 共四类地址;本接口只返回当前所选码流的 HTTP HLS
- 全量地址用
getLiveStreamInfo,或到开发者控制台直播服务页查看 - 设备解绑会自动删除直播地址
- 直播地址对外公开后,他人可直接看画面——当机密句柄,不要印在院门口的铁皮牌上
// server/create-yard-live.js
import { callOpenApi } from './imou-client.js';
import { getAccessToken } from './access-token.js';
export async function createYardLive(appId, appSecret, {
deviceId,
channelId = '0',
streamId = 1,
}) {
const token = await getAccessToken(appId, appSecret);
const data = await callOpenApi('bindDeviceLive', appId, appSecret, {
token,
deviceId,
channelId: String(channelId),
streamId,
liveMode: 'proxy',
});
// liveToken:后续改计划 / 启停的唯一句柄,务必入库
return {
liveToken: data.liveToken,
liveStatus: data.liveStatus, // 1 开启;2 暂停
httpHls: data.streams?.[0]?.hls, // 通常只回所选码流的 HTTP
coverUrl: data.streams?.[0]?.coverUrl,
deviceId: data.deviceId,
channelId: data.channelId,
job: data.job, // 新建后常见 period: always, status: true
};
}
踩坑 5:创建完立刻把返回的 http://…m3u8 塞进微信。内置浏览器对明文 HTTP 媒体极不友好,常见「转圈无画面」。下一步必须拿 HTTPS。
3.7 Step 5:getLiveStreamInfo 取齐 HTTPS 与状态
getLiveStreamInfo:需先 bindDeviceLive,否则查不到。一次能拿到高清主码流、标清辅码流、HTTP、HTTPS 四类地址。
streams[].status 对照:
| status | 含义 |
|---|---|
0 | 正在直播中 |
1 | 正在直播中,但视频封面异常 |
2 | 视频源异常 |
3 | 码流转换异常 |
4 | 云存储访问异常 |
10 | 直播暂停中 |
// server/get-yard-streams.js
import { callOpenApi } from './imou-client.js';
import { getAccessToken } from './access-token.js';
export async function getYardStreams(appId, appSecret, { deviceId, channelId = '0' }) {
const token = await getAccessToken(appId, appSecret);
const data = await callOpenApi('getLiveStreamInfo', appId, appSecret, {
token,
deviceId,
channelId: String(channelId),
});
const httpsSd = data.streams?.find(
(s) => Number(s.streamId) === 1 && String(s.hls).startsWith('https://'),
);
const httpsHd = data.streams?.find(
(s) => Number(s.streamId) === 0 && String(s.hls).startsWith('https://'),
);
return {
job: data.job, // period / beginTime / endTime / status
httpsSdHls: httpsSd?.hls,
httpsHdHls: httpsHd?.hls,
sdStatus: httpsSd?.status,
coverUrl: httpsSd?.coverUrl,
liveToken: httpsSd?.liveToken || data.streams?.[0]?.liveToken,
};
}
家人默认下发 HTTPS + 辅码流;仅业主后台预览可切主码流。coverUrl 适合做加载占位,别当权限凭证。HTTPS 地址常见带 ?proto=https,端口与 HTTP 不同——按返回原样使用,不要手改。
踩坑 6:以为「没有 HTTPS」。其实要用 getLiveStreamInfo(或控制台)才能一次看到四类地址。bindDeviceLive 只回 HTTP,是文档写明的行为,不是接口坏了。
3.8 Step 6:把 always 改成「看园档期」——4G 省钱第一刀
新建直播后,job 常见是 period: "always"。庭院远程看不是安防值班墙,不必 24 小时出流。早饲、午巡、晚归三档就够大多数家庭:
// server/set-yard-plan.js
import { callOpenApi } from './imou-client.js';
import { getAccessToken } from './access-token.js';
/** 每天三档看园;按真实作息改 rules */
export async function setYardWatchPlan(appId, appSecret, liveToken) {
const token = await getAccessToken(appId, appSecret);
await callOpenApi('batchModifyLivePlan', appId, appSecret, {
token,
liveToken,
rules: [
{
period: 'monday,tuesday,wednesday,thursday,friday,saturday,sunday',
beginTime: '06:00',
endTime: '08:00',
},
{
period: 'monday,tuesday,wednesday,thursday,friday,saturday,sunday',
beginTime: '11:30',
endTime: '13:00',
},
{
period: 'monday,tuesday,wednesday,thursday,friday,saturday,sunday',
beginTime: '17:00',
endTime: '20:00',
},
],
});
}
/** 回城过年 / 整院腾空:一键关 */
export async function pauseYardLive(appId, appSecret, liveToken) {
const token = await getAccessToken(appId, appSecret);
await callOpenApi('modifyLivePlanStatus', appId, appSecret, {
token,
liveToken,
status: 'off', // on | off
});
}
export async function resumeYardLive(appId, appSecret, liveToken) {
const token = await getAccessToken(appId, appSecret);
await callOpenApi('modifyLivePlanStatus', appId, appSecret, {
token,
liveToken,
status: 'on',
});
}
batchModifyLivePlan:对单个 liveToken 写多条规则;period 用英文星期,逗号分隔;beginTime / endTime 格式是 HH:mm;重叠时段平台会合并。
若只需「每天同一时段」,用单计划接口即可:
await callOpenApi('modifyLivePlan', appId, appSecret, {
token: await getAccessToken(appId, appSecret),
liveToken,
period: 'everyday', // always | once | everyday
beginTime: '06:00:00', // everyday 用 HH:mm:ss;平台秒位常归一为 00
endTime: '20:00:00',
});
注意:batchModifyLivePlan 的时间格式是 HH:mm,modifyLivePlan 的 everyday 是 HH:mm:ss——别混用。once 则是 yyyy-MM-dd HH:mm:ss,且 endTime 必须大于当前时间。
踩坑 7:只在业务层把「看院子」按钮夜间灰掉,平台侧仍是 always。历史 m3u8 若曾泄露,夜间仍可能被拉流——平台计划 + 业务鉴权要双开。4G 卡账单异常高,第一怀疑对象永远是「主码流 + always」。
3.9 Step 7:业务签发 + H5 播放(代码优先)
// server/routes/yard-live.js
import { getYardStreams } from '../get-yard-streams.js';
const TICKET_TTL_SEC = 180; // 业务层:3 分钟内开播
export async function issueYardLive(req, res) {
const { yardId } = req.body;
const bind = await db.yard.find(yardId);
if (!bind) return res.status(404).json({ error: 'yard_not_bound' });
// 示例:仅登录家人可看;按你们家庭组 / 租户规则改
if (!req.session?.userId) {
return res.status(401).json({ error: 'login_required' });
}
if (!canViewYard(req.session.userId, yardId)) {
return res.status(403).json({ error: 'not_this_yard' });
}
const streams = await getYardStreams(process.env.IMOU_APP_ID, process.env.IMOU_APP_SECRET, {
deviceId: bind.device_id,
channelId: bind.channel_id,
});
if (String(streams.sdStatus) === '10') {
return res.status(503).json({ error: 'live_paused' });
}
if (!streams.httpsSdHls) {
return res.status(503).json({ error: 'stream_unavailable' });
}
return res.json({
hls: streams.httpsSdHls,
coverUrl: streams.coverUrl,
expireIn: TICKET_TTL_SEC,
});
}
<!-- public/yard-live.html -->
<video id="v" controls playsinline webkit-playsinline
poster="" style="width:100%;background:#111"></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7/dist/hls.min.js"></script>
<script>
async function playYard(yardId) {
const r = await fetch('/api/yard/live', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ yardId }),
});
if (!r.ok) {
alert('当前不在看园时段,或设备暂不可看');
return;
}
const { hls, coverUrl } = await r.json();
const video = document.getElementById('v');
if (coverUrl) video.poster = coverUrl;
if (video.canPlayType('application/vnd.apple.mpegurl')) {
// iOS Safari / 部分微信环境:原生 HLS
video.src = hls;
} else if (window.Hls && Hls.isSupported()) {
const player = new Hls({ enableWorker: true, lowLatencyMode: false });
player.loadSource(hls);
player.attachMedia(video);
} else {
alert('当前环境不支持 HLS 播放');
}
}
playYard(new URLSearchParams(location.search).get('yardId'));
</script>
公众号入口建议:菜单 / 模板消息 → 业务域名 H5,不要把 m3u8 写进图文正文。小程序侧可用 web-view 打开同一 H5。平台侧 HLS URL 本身可能具备较长可访问周期,业务必须用登录态 + 档期 + 审计补齐「人」的维度。
sequenceDiagram
participant Family as 家人H5
participant Biz as 农事后端
participant Imou as OpenAPI
participant Cam as 户外4G机
Family->>Biz: POST /api/yard/live
Biz->>Biz: 登录 + 院落 ACL + 档期
Biz->>Imou: getLiveStreamInfo
Imou->>Cam: 代理出流(若计划开启)
Imou-->>Biz: HTTPS 辅码流 m3u8
Biz-->>Family: 短效播放信息
Family->>Family: hls.js / 原生 HLS
3.10 一次联调检查清单(可当发版门禁)
[ ] 签名标准用例算出 xjhCQBoJ9hRDsCjyDcHjtDNzRZ3ZJezcawsfWeiaoxU=
[ ] accessToken 成功,且服务端缓存,未下发前端
[ ] 物联网卡已开通,设备指示灯可联网
[ ] 设备在开发者资产池,且 deviceStatus / channelStatus 均为 online
[ ] bindDeviceLive(streamId=1) 返回 liveToken 已入库
[ ] getLiveStreamInfo 能拿到 https + streamId=1
[ ] batchModifyLivePlan 后,非档期拉流失败或 status=10
[ ] modifyLivePlanStatus=off 后签发接口返回 live_paused
[ ] 微信内 HTTPS 可播;HTTP 地址已从产品路径剔除
[ ] 家人账号只能看到自己的 yardId,串院测试被 403
四、边界、4G 流量与生产注意
4.1 能力边界:别用云直播扛「对讲赶鸡」
| 需求 | 云直播 HLS 路径 | 建议改道 |
|---|---|---|
| 家人看一眼院门 / 鸡圈 | ✅ | — |
| 远程喊话、对讲赶牲口 | ❌ | 轻应用 / 移动 OpenSDK |
| 云台转到死角 | 播放器侧要 getKitToken 且 type 覆盖 6 或 0 | 不要指望 HLS <video> 出云台 |
| 翻昨天傍晚「谁开的门」 | 可用 createDeviceRecordHls / createDeviceFlvLive type=playback | 与实时看园分开入口 |
| 超低延迟业主墙 | 评估 createDeviceFlvLive type=realTime | 返回 flv(标清)/ flvHD(高清) |
createDeviceFlvLive 实时预览只需:
const data = await callOpenApi('createDeviceFlvLive', appId, appSecret, {
token: await getAccessToken(appId, appSecret),
deviceId,
channelId: '0',
type: 'realTime', // 默认值;playback 才需要 beginTime / endTime / recordType
});
// data.flv 标清
// data.flvHD 高清
回放跨度最大 24 小时,可跨天;recordType 为 localRecord 或 cloudRecord。查询已创建的实时 FLV 用 queryDeviceFlvLive,删除用 deleteDeviceFlvLive(只支持删实时直播)。
业主 Web 墙若走轻应用:getKitToken 的 token 必须是管理员 At_,type 用字符串(1 仅预览,0 全权限,6 云台);kitToken 约 2 小时有效,文档建议服务端缓存约 1 小时。播放器初始化的 type: 1 是直播、type: 2 是回放——和 getKitToken 的 type 不是同一套枚举。
4.2 4G 流量与性能:先砍码流,再砍时段,再砍并发
- 默认辅码流:家人侧永不主动发主码流;业主后台单独开 HD。
- 计划外不签发:业务层档期校验与平台
job双校验,避免无效getLiveStreamInfo风暴。 - token 缓存:
accessToken约 3 天;按剩余秒数刷新即可。 - 播放器:HLS 本身有分片延迟,「看得见院子」可接受;不要为了抠 1~2 秒延迟上整套原生 SDK。
- 封面刷新:
coverUpdate单位为秒;status=1(封面异常)时先查镜头遮挡和设备在线,而不是重绑直播。 - 休眠:户外 4G 机
deviceStatus=sleep时,先别创建直播。省电策略和「卡没流量」在现场长得很像,要用列表字段分开。
4.3 安全与合规(院子画面也敏感)
- 院门可能拍到邻居、门牌、孩子进出——详情页禁止裸奔 m3u8。
- 整院腾空、转租、撤机:先
modifyLivePlanStatus=off,再视需要走unbindLive(按liveToken删除直播地址),并清空业务绑定。设备解绑也会自动删直播地址。 - 多院落:一院一
liveToken映射;不要用一个全局 HLS 给所有亲戚复用。 - 应急:家族群反馈「链接外传」→ 立刻
off→ 轮换业务签发策略 → 通知家人重新进入 H5。
4.4 真实踩坑速记(可当值班手册)
| 现象 | 根因 | 处理 |
|---|---|---|
| 标准案例 sign 对不上 | 抄了旧 MD5 | 改现行 HMAC-SHA256 |
接口 SN1002 | time 用了毫秒,或时钟漂移 > 5 分钟 | 秒级时间戳 + NTP |
接口 SN1005 | 5 分钟内 nonce 复用 | 每次请求 randomUUID() |
TK1002 | accessToken 失效 | 刷新并更新缓存 |
| 列表 0 台,App 却能看 | 绑在私人号下 | 绑到开发者主账号 |
| 微信里黑屏 | 用了 HTTP m3u8 | getLiveStreamInfo 挑 https |
| 创建成功但 getLive 空 | 未先 bind / 已解绑 | 重建直播并更新 liveToken |
| 夜间仍被拉流 | 平台计划仍为 always | batchModifyLivePlan + 业务拒签 |
| 月流量炸了 | 默认主码流 + always | 全量切 streamId=1,锁三档看园 |
| 设备 online 仍无画 | 通道 sleep / 遮罩 on | 同时看 channelStatus、cameraStatus |
五、小结与延伸
庭院 / 小农场「人在城里、院在乡下」的远程看,可以收成一条短链路:
- 现场:户外 4G 机插卡上电,绑进开发者资产,而不是侄子的私人 App。
- 鉴权壳:现行 HMAC-SHA256 +
accessToken(只活在服务端)。 - 出流:
bindDeviceLive(streamId=1)→getLiveStreamInfo取 HTTPS 辅码流。 - 省钱闸门:
batchModifyLivePlan锁看园档期,modifyLivePlanStatus做腾空熔断。 - 触达:业务 H5 + hls.js;公众号走菜单进页,不把
m3u8写进图文。 - 边界:云直播解决「可看」;对讲、云台、深度 App 能力另选轻应用或 OpenSDK。
延伸阅读(平台现行文档栏目,查阅时跳过「旧版本协议」):
- 开发规范:域名、
system.sign、错误码 - accessToken:管理员 token 生命周期
- listDeviceDetailsByPage:现行设备台账
- 设备直播说明:
bindDeviceLive/ 计划 / FLV / RTMP 全家桶 - 4G 物联网卡:户外 / 养殖 / 工地等无宽带点位
- 轻应用组件 / getKitToken:业主墙要回放或云台时
若你正在把乡下院子的户外机做成可嵌入的家人页,可在 开放平台 注册开发者应用,用控制台领取的接入与媒体资源做联调。平台以视频技术和安全为核心,开放偏低代码的开发组件,适合第三方厂商和个人开发者把「设备画面」较快接到自己的 H5 / 小程序业务里——庭院远程看这条线,从 4G 上电到 bindDeviceLive 跑通辅码流 HTTPS,通常就是最短闭环。