物联网开发平台 应用端接入(云存服务)

这篇将介绍腾讯云物联网(IoT)应用端 SDK 中云存回放(TXIoTCloudStorage)的使用方法。完成快速接入并登录 SDK 后,可以以获取指定设备的云存对象,查询有云端录像的日期、拉取某一天的事件与录像列表。

相关产品
物联网开发平台 产品页,含版本规格、价格与购买入口。

一、前提条件

在调用这篇 API 前,请确保已完成以下准备工作:

·参考 集成与登录 在工程中集成 IoT 应用端 SDK,完成登录,并将目标设备绑定到当前登录账户名下。

·参考 开通云存服务,且设备已产生云端录像。

二、使用建议

·调用顺序:获取云存对象 → getDayList 查询有录像的日期 → 用户选中某天后 getEventList 拉取该天数据 → 循环翻页直到 nextPageToken 为空 → 选中某条录像交给播放器播放。

·一个云存对象对应一台设备:getCloudStorage 需传入设备 ID,切换设备时需重新获取。

·分页务必拉完:getEventList 单次调用不代表拿到当天全部数据,必须循环翻页。

·事件与录像分开渲染:事件列表用于展示“发生了什么”,录像文件用于“播放画面”,两者用途不同,建议在 UI 上区分。

三、获取云存对象

登录成功后,通过 TXIoTEngine 单例调用 getCloudStorage 并传入目标设备 ID,即可获取该设备的云存对象 TXIoTCloudStorage。

注意:云存对象与设备一一对应,不是全局单例。未登录时返回空值;切换设备时必须重新调用 getCloudStorage 获取新对象,不能复用其他设备的云存对象。

AndroidiOSTXIoTDeviceId deviceId = new TXIoTDeviceId();deviceId.productId = "your_product_id";deviceId.deviceName = "your_device_name";TXIoTCloudStorage cloudStorage = TXIoTEngine.getInstance(context).getCloudStorage(deviceId);if (cloudStorage == null) { // 未登录或参数异常,需先完成登录 return;}TXIoTDeviceId *deviceId = [[TXIoTDeviceId alloc] init];deviceId.productId = @"your_product_id";deviceId.deviceName = @"your_device_name";TXIoTCloudStorage *cloudStorage = [[TXIoTEngine sharedInstance] getCloudStorage:deviceId];if (cloudStorage == nil) { // 未登录或参数异常,需先完成登录 return;}

四、查询有录像的日期

调用 getDayList 获取指定通道下有云端录像的日期列表,返回格式为 yyyy-MM-dd 的字符串列表,通常用于渲染日期选择器,避免用户选到没有录像的日期。

参数 说明
channelId 通道 ID,从 0 开始计数;单通道设备传 0。
timeZone 按天统计所使用的时区。请与后续 getEventList 保持一致,否则会出现日期与录像对不上的问题。

注意:传空指针时会自动使用系统默认时区,为保证多端行为一致,建议始终显式传值。

AndroidiOScloudStorage.getDayList(0, TimeZone.getDefault(), new TXIoTCallback<List<String>>() { @Override public void onSuccess(List<String> dayList) { // dayList 形如 ["2026-08-18", "2026-08-19", "2026-08-20"] renderDatePicker(dayList); } @Override public void onError(TXIoTErrorCode errorCode, String errorMessage) { // 统一错误处理 }});TXIoTCallback<NSArray<NSString *> *> *callback = [[TXIoTCallback alloc] init];callback.onSuccess = ^(NSArray<NSString *> *dayList) { // dayList 形如 @[@"2026-08-18", @"2026-08-19", @"2026-08-20"] [self renderDatePicker:dayList];};callback.onError = ^(TXIoTErrorCode errorCode, NSString *errorMessage) { // 统一错误处理};[cloudStorage getDayList:0 timeZone:[NSTimeZone localTimeZone] callback:callback];

五、拉取某一天的事件与录像

用户选中某个日期后,调用 getEventList 拉取该天数据。由于数据分页返回,需要循环翻页直到 nextPageToken 为空,并在拉取过程中按 eventType 分流。

参数 说明
channelId 通道 ID,需与 getDayList 一致。
timeZone 时区,需与 getDayList 一致。
date 日期,格式 yyyy-MM-dd,取自 getDayList 的返回结果。不可为空字符串。
nextPageToken 分页游标。首次传空字符串,翻页时原样回传上一次的返回值。

AndroidiOS// recordSegments:录像段(时间轴);alarmEvents:事件段(事件列表)private void loadPage(TXIoTCloudStorage storage, String date, String pageToken) { storage.getEventList(0, TimeZone.getDefault(), date, pageToken, new TXIoTCallback<TXIoTPageResult<TXIoTEvent>>() { @Override public void onSuccess(TXIoTPageResult<TXIoTEvent> result) { for (TXIoTEvent event : result.dataList) { // eventType 为空是录像段,非空是事件段 if (TextUtils.isEmpty(event.eventType)) { recordSegments.add(event); } else { alarmEvents.add(event); } } if (!TextUtils.isEmpty(result.nextPageToken)) { loadPage(storage, date, result.nextPageToken); // 游标原样回传 return; } refreshUI(recordSegments, alarmEvents); // 拉完再统一刷新 } @Override public void onError(TXIoTErrorCode errorCode, String errorMessage) { // 统一错误处理 } });}// 首次拉取:清空缓存后从空游标开始loadPage(cloudStorage, date, "");// recordSegments:录像段(时间轴);alarmEvents:事件段(事件列表)- (void)loadPage:(NSString *)date storage:(TXIoTCloudStorage *)storage pageToken:(NSString *)pageToken { TXIoTCallback<TXIoTPageResult<TXIoTEvent *> *> *callback = [[TXIoTCallback alloc] init]; callback.onSuccess = ^(TXIoTPageResult<TXIoTEvent *> *result) { for (TXIoTEvent *event in result.dataList) { // eventType 为空是录像段,非空是事件段 if (event.eventType.length == 0) { [self.recordSegments addObject:event]; } else { [self.alarmEvents addObject:event]; } } if (result.nextPageToken.length > 0) { [self loadPage:date storage:storage pageToken:result.nextPageToken]; // 游标原样回传 return; } [self refreshUI]; // 拉完再统一刷新 }; callback.onError = ^(TXIoTErrorCode errorCode, NSString *errorMessage) { // 统一错误处理 }; [storage getEventList:0 timeZone:[NSTimeZone localTimeZone] date:date nextPageToken:pageToken callback:callback];}// 首次拉取:清空缓存后传空游标// [self loadPage:date storage:cloudStorage pageToken:@""];

六、理解云存数据模型

云存查询返回的是事件列表,每个事件内部又包含录像文件列表,形成两级结构。理解这两级的分工是正确接入的前提。

七、两级结构说明

层级 含义与用途
TXIoTEvent eventType 表示事件类型(如移动侦测、人形侦测),实际值需要 App 与设备端对齐。thumbnailUrl 为该事件的预览图,如果没有视频文件,说明该时间为抓图事件。eventTimeMs / durationMs 为事件的起始时间与时长,用于渲染事件列表、时间轴标记。
TXIoTVideoFile 一个可播放的实体文件,包含播放所需的地址(未加密时为 HLS/m3u8 地址)或云点播三要素(开启云存加密时下发),以及该文件自身的起止时间。

一个事件可能对应0个、1个或多个录像文件:

·1个:最常见,事件完整落在一个录像文件内。

·多个:事件跨越了录像文件的切片边界(云端录像按固定时长切片存储),此时需按 startTimeMs 升序依次播放,才能覆盖完整事件时长。

·0个:该事件为抓图事件,云端没有关联录像(例如设备仅上报了告警但未录制)。

八、时间字段的口径

TXIoTEvent 与 TXIoTVideoFile 都有时间字段,但口径不同,请勿混用:

字段 口径
TXIoTEvent.eventTimeMs 事件开始时间(Unix 毫秒时间戳)。用于事件列表排序、时间轴打点。
TXIoTEvent.durationMs 事件持续时长(毫秒)。可能大于单个录像文件的时长。
TXIoTVideoFile.startTimeMs 该录像文件的起始时间(Unix 毫秒时间戳)。用于多文件排序、计算播放进度对应的绝对时刻。
TXIoTVideoFile.durationMs 该录像文件的时长(毫秒)。

注意:所有时间字段均为 Unix 毫秒级时间戳,若需展示为本地时间,请按传入 getEventList 的同一时区做格式化,避免跨日错位。

九、如何播放一个录像文件

TXIoTVideoFile 的播放方式取决于设备的云存加密配置,两种方式互斥,服务端只会下发其中一种:

1. URL 直播放(未开启云存加密,默认):videoUrl 为 HLS(m3u8)地址,直接交给播放器播放即可。此时云点播三要素为空。

2. 云点播播放(已开启云存加密):开启云存加密后,服务端会下发 vodAppId、vodFileId、vodPlaySign 三要素,需使用 云点播播放器 按 FileId 播放(IOT SDK 已包含点播播放器,不需要再次集成),由云点播完成解密与播放授权。

注意:两种播放方式由设备的云存加密配置决定,不需要业务侧切换套餐或改接口:未开启加密时只返回 videoUrl(m3u8),开启加密后才返回云点播三要素。

AndroidiOS小程序private boolean startPlay(TXVodPlayer player, TXIoTVideoFile file) { // 已开启云存加密:走云点播播放 if (!TextUtils.isEmpty(file.vodAppId) && !TextUtils.isEmpty(file.vodFileId)) { TXPlayInfoParams params = new TXPlayInfoParams( Integer.parseInt(file.vodAppId), file.vodFileId, file.vodPlaySign); player.startVodPlay(params); return true; } // 未开启云存加密:videoUrl 为 m3u8 地址,直接播放 if (!TextUtils.isEmpty(file.videoUrl)) { return player.startVodPlay(file.videoUrl) == 0; } // 无可播放源 return false;}- (BOOL)startPlayWithPlayer:(TXVodPlayer *)player file:(TXIoTVideoFile *)file { // 已开启云存加密:走云点播播放 if (file.vodAppId.length > 0 && file.vodFileId.length > 0) { TXPlayerAuthParams *params = [[TXPlayerAuthParams alloc] init]; params.appId = [file.vodAppId intValue]; params.fileId = file.vodFileId; params.sign = file.vodPlaySign; [player startVodPlayWithParams:params]; return YES; } // 未开启云存加密:videoUrl 为 m3u8 地址,直接播放 if (file.videoUrl.length > 0) { return [player startVodPlay:file.videoUrl] == 0; } // 无可播放源 return NO;}function resolvePlayUrl(file: TXIoTVideoFile): string { // 未开启云存加密时,videoUrl 为 m3u8 地址,可直接交给播放器,小程序不支持加密云存播放。 return file.videoUrl || '';}

十、播放跨多个文件的事件

当事件跨越录像切片边界时,videoFiles 会包含多个文件。此时需按 startTimeMs 升序排序,在前一个文件播放结束时依次切换到下一个,才能完整播完整个事件。

AndroidiOSprivate List<TXIoTVideoFile> playQueue = new ArrayList<>();private int playIndex = 0;private void playEvent(TXVodPlayer player, TXIoTEvent event) { if (event.videoFiles == null || event.videoFiles.isEmpty()) { // 纯告警事件,无关联录像,不可播放 showToast("该事件无录像"); return; } playQueue = new ArrayList<>(event.videoFiles); // 多文件必须按起始时间升序播放 Collections.sort(playQueue, (a, b) -> Long.compare(a.startTimeMs, b.startTimeMs)); playIndex = 0; startPlay(player, playQueue.get(playIndex));}// 在播放器的播放结束回调中切换下一段private void onPlayFinished(TXVodPlayer player) { playIndex++; if (playIndex < playQueue.size()) { startPlay(player, playQueue.get(playIndex)); }}@property(nonatomic, strong) NSArray<TXIoTVideoFile *> *playQueue;@property(nonatomic, assign) NSInteger playIndex;- (void)playEvent:(TXIoTEvent *)event player:(TXVodPlayer *)player { if (event.videoFiles.count == 0) { // 纯告警事件,无关联录像,不可播放 [self showToast:@"该事件无录像"]; return; } // 多文件必须按起始时间升序播放 self.playQueue = [event.videoFiles sortedArrayUsingComparator: ^NSComparisonResult(TXIoTVideoFile *a, TXIoTVideoFile *b) { return a.startTimeMs < b.startTimeMs ? NSOrderedAscending : NSOrderedDescending; }]; self.playIndex = 0; [self startPlayWithPlayer:player file:self.playQueue[self.playIndex]];}// 在播放器的播放结束回调中切换下一段- (void)onPlayFinishedWithPlayer:(TXVodPlayer *)player { self.playIndex += 1; if (self.playIndex < self.playQueue.count) { [self startPlayWithPlayer:player file:self.playQueue[self.playIndex]]; }}

注意:说明:

注意:若需实现“从事件中间某个时刻开始播放”,可用目标时刻减去当前文件的 startTimeMs,得到在该文件内的相对 seek 位置(毫秒)。

十一、为什么前几页返回的事件 eventType 都是空字符串?

这是正常现象,getEventList 是分页返回数据,请继续翻页直到 nextPageToken 为空,即可拿到事件段数据。

十二、为什么某个事件的 videoFiles 是空列表?

该事件为抓图事件,云端没有关联录像(例如设备仅上报告警但未录制)。此类事件只能展示缩略图与时间,请在 UI 上置为不可播放,切勿直接访问 videoFiles[0]。

十三、为什么事件的 durationMs 比 videoFiles 里单个文件的时长长?

云端录像按固定时长切片存储,事件跨越切片边界时会关联多个文件。事件时长是整段事件的时长,单个文件时长只是其中一片。请按 startTimeMs 升序依次播放全部文件。

十四、为什么日期选择器有数据,但拉取事件列表返回为空?

请检查 getDayList 与 getEventList 传入的 channelId 和 timeZone 是否完全一致。时区不一致会导致按天切分的边界错位,进而查不到数据。

十五、能否自己拼接 nextPageToken 以跳页?

不可以。nextPageToken 是 SDK 内部的不透明游标,包含用于区分数据段的内部标识,自行构造会返回参数错误。请始终原样回传上一次的返回值。

十六、播放时应该用 videoUrl 还是云点播三要素?

取决于设备是否开启了云存加密,两者不是优先级关系而是互斥关系:未开启加密时(默认)服务端只返回 videoUrl,为 HLS(m3u8)地址,直接播放即可;开启加密后服务端才返回云点播三要素(vodAppId / vodFileId / vodPlaySign),需用云点播播放器按 FileId 播放,由云点播完成解密。

十七、接口参考

云存模块的完整接口定义、参数与数据结构说明,参见 API 文档:

·TXIoTCloudStorage(Android)

·TXIoTCloudStorage(iOS)

赞 (0)
上一篇 4天前
下一篇 4天前