Android 应用集成乐橙 OpenSDK:视频预览 + 录像回放一站式接入
连锁巡店 App 的 Android 工程师小林把 bindDeviceLive 返回的 HLS 塞进 WebView,总部验收时实时画面延迟 8 秒、滑动列表还 OOM。换乐橙 OpenSDK 私有协议 后出流 1–2 秒——但录像页又全黑:他用预览参数去播 SD 卡片段,列表却是 queryLocalRecords 查出来的 recordId。预览和回放,在 SDK 里是两套窗口、两条参数链。
为什么移动端值得用 OpenSDK 而不是只接 HTTP 拉流
乐橙开放平台(open.imou.com)给移动场景提供了多种对接方式。开发总览对比很直观:
| 对接方式 | 协议 | 出流速度 | 实时延迟 | 录像回放 | 语音对讲 | 对接耗时 |
|---|---|---|---|---|---|---|
| 移动应用 + OpenSDK | 私有协议 | 1–2 秒 | 1–2 秒 | ✅ | ✅ | 1–3 月 |
| 云直播(HLS) | hls | 3–8 秒 | 8–10 秒 | ✅ | ❌ | 1–8 小时 |
| 轻应用 | hls/rtsp | 1–3 秒 | 2–3 秒 | ✅ | ✅ | 1–7 日 |
巡店、工地、社区类 App 的核心诉求是「低延迟预览 + 回放 + 对讲」——HLS 适合 Web 快速验证,不适合作为 Android 主播放链路。OpenSDK 把解码、渲染、手势、对讲封装进 LCOpenSDK_PlayRealWindow / LCOpenSDK_PlayBackWindow,开发者专注业务 UI 与权限。
但 OpenSDK 不会替你做录像列表查询。完整链路是「云云对接」:
┌──────────────────────────────────────────────────────────────┐
│ Android App(OpenSDK) │
│ LCOpenSDK_Api.init → PlayRealWindow / PlayBackWindow │
└────────────────────────────┬─────────────────────────────────┘
│ accessToken、playToken、录像 recordId
┌────────────────────────────▼─────────────────────────────────┐
│ 你的业务后端(HTTP OpenAPI) │
│ accessToken · listDeviceDetailsByPage · queryLocalRecords │
│ · getCloudRecords │
└────────────────────────────┬─────────────────────────────────┘
│
┌────────────────────────────▼─────────────────────────────────┐
│ 乐橙云平台 openapi.lechange.cn │
└──────────────────────────────────────────────────────────────┘
标准化 Demo 说明特别强调:Demo 在客户端直连 OpenAPI 仅为演示;生产环境 appId/appSecret 必须在服务端,否则反编译即密钥泄露。
预览 vs 回放:别混窗口类
| 能力 | SDK 类 | 列表数据来源(OpenAPI) | 关键参数字段 |
|---|---|---|---|
| 实时预览 | LCOpenSDK_PlayRealWindow | listDeviceDetailsByPage → playToken | deviceId、channel、psk、playToken |
| 设备 SD 回放 | LCOpenSDK_PlayBackWindow | queryLocalRecords → recordId | 按文件名 / 按时间两种 Param |
| 云录像回放 | LCOpenSDK_PlayBackWindow | getCloudRecords → recordId | recordRegionID、recordType |
「能预览」不等于「能回放」——设备无 TF 卡时本地列表为空;云存 csStatus 非 using 时云列表为空。列表查 OpenAPI,播放调 SDK,是小林黑屏的根因。
前置条件
- open.imou.com 注册开发者,创建移动应用,获取
appId/appSecret。 - 资源下载 获取 Android SDK(LCOpenSDK.aar) 与 Android Demo(参考
DeviceOnlineMediaPlayActivityNew.java、DeviceRecordPlayActivityNew.java)。 - 设备已绑定开发者账号(乐橙 App 或 SDK Demo 添加)。
- 使用现行 OpenAPI / OpenSDK 文档,勿引用「旧版本协议、不再维护」栏目。
1. 工程接入:AAR + 权限 + ABI
Step 1:解压 SDK,将 libs/LCOpenSDK.aar 拷入工程 app/libs/,在 build.gradle 引用:
android {
defaultConfig {
ndk {
abiFilters "armeabi", "arm64-v8a" // 按目标机型选择
}
}
}
dependencies {
implementation files('libs/LCOpenSDK.aar')
}
Step 2:AndroidManifest.xml 声明权限(摘自 OpenSDK 组件文档):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
未开
RECORD_AUDIO→ 对讲失败;未开存储权限 → 本地录制/下载失败。
Step 3:播放布局——SDK 播放类继承 FrameLayout,需要父容器 + 渲染子容器:
<FrameLayout
android:id="@+id/live_window"
android:layout_width="match_parent"
android:layout_height="200dp">
<FrameLayout
android:id="@+id/live_window_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
2. 全局初始化:OpenSDK 第一个必调接口
// ImouSdkHolder.java —— 建议在 Application.onCreate 调用
public final class ImouSdkHolder {
private static volatile boolean inited;
public static void init(Application app, String accessToken) {
if (inited) return;
String host = "openapi.lechange.cn:443"; // 不带 https://
InitParams params = new InitParams(app, host, accessToken);
int ret = LCOpenSDK_Api.initOpenApi(params);
if (ret != 0) throw new IllegalStateException("initOpenApi failed: " + ret);
LCOpenSDK_Utils.enableLogPrint(); // 联调期开启,上线可关
LCOpenSDK_Util.setLogLevel(4); // debug
inited = true;
}
}
accessToken 由你的后端调 https://openapi.lechange.cn/openapi/accessToken 换取(有效期 3 天,TK1002 时刷新),不要把 appSecret 写进 APK。
踩坑 A:host 写成 https://openapi.lechange.cn 带协议 → 初始化异常。官方格式为 openapi.lechange.cn:443。
3. 后端下发播放凭证:deviceId + playToken + psk
App 登录后请求你自己的接口 /api/devices/{id}/play-auth,后端聚合 OpenAPI:
// 服务端:listDeviceDetailsByPage 取 playToken(加速出流,非必传但强烈建议)
const data = await platformCall('listDeviceDetailsByPage', {
token, pageSize: 10, page: 1, source: 'bind',
});
const dev = data.deviceList.find(d => d.deviceId === deviceId);
return {
accessToken, // 子账号 token 亦可
deviceId: dev.deviceId,
channelId: dev.channelList[0].channelId,
playToken: dev.playToken, // 设备播放码,opensdk 使用
psk: dev.encryptMode === '1' ? userCustomKey : dev.deviceId,
encryptMode: dev.encryptMode,
};
playToken 字段说明见 批量获取设备详细信息:非必传,传入可加速拉流——小林验收时补上后,首帧从 5 秒降到 1 秒级。
4. 实时预览:LCOpenSDK_PlayRealWindow
public class LivePlayFragment extends Fragment {
private LCOpenSDK_PlayRealWindow playWindow;
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
ViewGroup container = view.findViewById(R.id.live_window_content);
playWindow = new LCOpenSDK_PlayRealWindow();
playWindow.initPlayWindow(requireContext(), container, 0, false);
playWindow.setWindowListener(new LCOpenSDK_EventListener() {
@Override
public void onPlayerResult(String code, int type, int index) {
Log.d("IMOU", "preview result code=" + code);
}
@Override
public void onPlayBegan(int index) { /* 出画 */ }
});
playWindow.openTouchListener(); // 双指缩放等手势
}
public void startPreview(PlayAuth auth) {
LCOpenSDK_ParamReal param = new LCOpenSDK_ParamReal();
param.accessToken = auth.accessToken;
param.deviceId = auth.deviceId;
param.channel = Integer.parseInt(auth.channelId);
param.psk = auth.psk;
param.playToken = auth.playToken;
param.isOpt = true; // 拉流优化,建议 true
param.streamType = 0; // 0 高清主码流,1 辅码流
playWindow.playRtspReal(param);
}
public void stopPreview() {
if (playWindow != null) {
playWindow.stopRtspReal(true);
playWindow.uninitPlayWindow();
}
}
@Override
public void onDestroyView() {
stopPreview();
super.onDestroyView();
}
}
预览流程(官方 OpenSDK 组件 · 6.1):
initPlayWindow → setWindowListener → playRtspReal
→ onPlayBegan / onPlayerResult
→ stopRtspReal → uninitPlayWindow
踩坑 B:Activity 旋转或切后台未 stopRtspReal → 占用并发带宽(开放平台按播放端累计并发数)。
踩坑 C:NVR 多通道设备 channel 须传具体通道号,单 IPC 一般为 0。
5. 录像列表:OpenAPI 查,SDK 播
5.1 设备本地 SD:queryLocalRecords
// 后端:单日查询,勿跨天(易超时,见 Demo 注意事项)
const records = await platformCall('queryLocalRecords', {
token,
deviceId: 'TESTQWERXXXX',
channelId: '0',
beginTime: '2026-06-10 08:00:00',
endTime: '2026-06-10 18:00:00',
type: 'All',
count: 50,
});
// records[].recordId, beginTime, endTime, fileLength...
分页策略:下次 beginTime = 上次结果最大 endTime + 1 秒(见 queryLocalRecords 文档)。
5.2 云录像:getCloudRecords
const cloud = await platformCall('getCloudRecords', {
token,
deviceId: 'TESTQWERXXXX',
channelId: '0',
beginTime: '2026-06-10 08:00:00',
endTime: '2026-06-10 18:00:00',
count: 30, // 单次最大 30
});
// cloud.records[].recordId, beginTime, endTime...
通道 csStatus === 'using' 时再查,否则列表常为空。
6. 录像回放:LCOpenSDK_PlayBackWindow
设备录像(按 recordId,推荐):
public void playLocalRecord(PlayAuth auth, String recordId) {
LCOpenSDK_PlayBackWindow playWindow = new LCOpenSDK_PlayBackWindow();
playWindow.initPlayWindow(this, findViewById(R.id.live_window_content), 0, true);
playWindow.setWindowListener(eventListener);
playWindow.openTouchListener();
LCOpenSDK_ParamDeviceRecord param = new LCOpenSDK_ParamDeviceRecord();
param.accessToken = auth.accessToken;
param.deviceId = auth.deviceId;
param.channel = Integer.parseInt(auth.channelId);
param.psk = auth.psk;
param.fileName = recordId; // queryLocalRecords 返回的 recordId
param.offsetTime = 0;
param.isOpt = true;
param.isPlayBack = true;
playWindow.playRtspPlayback(param);
}
云录像:
public void playCloudRecord(PlayAuth auth, CloudRecordItem item) {
LCOpenSDK_PlayBackWindow playWindow = new LCOpenSDK_PlayBackWindow();
playWindow.initPlayWindow(this, findViewById(R.id.live_window_content), 0, true);
playWindow.setWindowListener(eventListener);
LCOpenSDK_ParamCloudRecord param = new LCOpenSDK_ParamCloudRecord();
param.accessToken = auth.accessToken;
param.deviceId = auth.deviceId;
param.channel = Integer.parseInt(auth.channelId);
param.psk = auth.psk;
param.recordRegionID = item.recordId; // getCloudRecords 的 recordId
param.offsetTime = 0;
param.isOpt = true;
playWindow.playCloud(param);
}
停止与释放:
playWindow.stopRtspPlayback(true); // 本地回放
// 或 playWindow.stopCloud(true); // 云回放
playWindow.uninitPlayWindow();
踩坑 D(小林黑屏根因):把 LCOpenSDK_ParamReal 或预览窗口用于回放;或 fileName 填了时间字符串而非 recordId。
踩坑 E:云回放调 playRtspPlayback、本地回放调 playCloud → 方法用反。
7. 端到端时序(验收用)
用户登录 App
→ 后端 accessToken + 设备列表(含 playToken)
→ LCOpenSDK_Api.initOpenApi
→ 点「实时预览」:PlayRealWindow.playRtspReal
→ 点「录像」Tab:
后端 queryLocalRecords / getCloudRecords
→ 点某片段:PlayBackWindow.playRtspPlayback / playCloud
→ onDestroy:stop + uninit
验收清单:
□ appSecret 不在 APK 内;token 由服务端下发
□ 预览用 PlayRealWindow,回放用 PlayBackWindow
□ playToken 已从 listDeviceDetailsByPage 带入
□ 本地查询不跨天;云存 csStatus = using
□ 页面销毁必 stop + uninitPlayWindow
□ 联调日志 LCOpenSDK_Utils.enableLogPrint()
生产环境:生命周期、并发与架构
- 云云对接:OpenAPI(列表、云台、告警)走服务端;App 只拿 短期 token + playToken + 录像元数据(应用开发 推荐架构)。
- Token 刷新:管理员 token 3 天有效;SDK 内
accessToken过期会 TK1002,App 应静默向后端换 token 并initOpenApi或更新 Param。 - 并发与带宽:同一设备多窗口播放计多个并发;列表页缩略图用
picUrl+LCOpenSDK_Utils.decryptPic解密,勿对未可见通道预拉流。 - 子账号:多租户 SaaS 用
subAccountToken+ 设备授权,预览/回放 Param 传子账号 token。 - 权限合规:SDK 涉及网络、存储、麦克风、位置等(见资源下载隐私说明),隐私政策须明示。
与 HLS / 轻应用的边界
| 场景 | 推荐 |
|---|---|
| Android/iOS 原生 App,要低延迟 + 对讲 + 回放 | OpenSDK |
| 运营后台 Web、快速 PoC | 云直播 HLS / 轻应用 |
| 小程序 | 小程序组件 / 插件 |
不要在同一 App 里混用 WebView HLS 与 OpenSDK 播同一路设备——用户体验与并发统计都会乱。
排错速查
| 现象 | 可能原因 | 处理 |
|---|---|---|
| initOpenApi 失败 | host 带 https、token 空 | host=openapi.lechange.cn:443 |
| 预览慢 | 未传 playToken | listDeviceDetailsByPage 取 playToken |
| 回放黑屏 | 窗口类/方法用错 | PlayBackWindow + 正确 Param |
| 本地列表空 | 无 SD / 跨天查询 | 确认 TF 卡;单日 queryLocalRecords |
| 云列表空 | 云存未生效 | csStatus === using |
| TK1002 | token 过期 | 后端刷新 accessToken |
| 对讲无声 | 未开 RECORD_AUDIO | Manifest + 运行时权限 |
Demo 移植建议
官方 Android 标准化 Demo 模块划分清晰:
- 预览:
DeviceOnlineMediaPlayActivityNew.java - 回放:
DeviceRecordPlayActivityNew.java - OpenAPI 封装:
DeviceInfoOpenApiManager.java(生产改为服务端) - 设备添加:可整模块
DeviceAddModule接入,含LCOpenSDK_Api初始化
下载 Demo 后真机调试,文档 体验 Demo 说明:启动页输入 AppId/AppSecret 仅为体验管理员模式。
小结
Android 集成乐橙视频能力,可以压缩成三句话:
- 工程层:AAR + 权限 +
LCOpenSDK_Api.initOpenApi,host 为openapi.lechange.cn:443。 - 预览层:
LCOpenSDK_PlayRealWindow+playToken+playRtspReal。 - 回放层:后端
queryLocalRecords/getCloudRecords查列表,App 用LCOpenSDK_PlayBackWindow分别playRtspPlayback/playCloud。
远程巡店、智慧工地、社区安防——原生 App 要低延迟预览与回放,OpenSDK 私有协议通常是比 WebView HLS 更稳的路径;很多「能播直播不能播录像」的问题,并不是设备故障,而是 列表 API 与 SDK 播放类没有配对。
如何开始:访问 乐橙开放平台 open.imou.com 注册开发者并创建移动应用,免费领取 10 路设备接入额度 与 1 Mbps 媒体带宽;在 资源下载 获取 Android SDK 与 Demo,对照 OpenSDK 组件文档 与 标准化 Demo 说明 联调。如需企业并发扩容或多租户方案,可通过平台 商务咨询 / 企业微信 获取支持。以上为项目实践记录,生产以最新文档为准。
1450

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



