Skip to content

Ti 云存 Flutter API 参考 ​

本文列出 Flutter 中 Ti 云存和通用 Media 的公开 API、返回结果和行为约定。第一次接入时,播放录像先阅读查询与播放录像,导出本地 MP4 先阅读下载录像;需要确认参数、状态、重复调用或资源释放规则时,再查阅本文。

本文中的 API 和公开错误常量都从下面的 package 入口导出。常见错误优先使用公开常量判断;没有命名常量的错误仍会返回完整整数值,详见错误码。TiCloudStorage.errorToString(code) 只用于展示或诊断。

dart
import 'package:tirtc_flutter/tirtc_flutter.dart';

一、API 总览 ​

类型用途
TiCloudStorage静态方法初始化和关闭服务;实例绑定一台设备并提供查询、Replay 和导出
Resp<T>表示一次云端业务请求的结果;成功时通过 data 返回业务数据
TiCloudStorageRecordingRange表示一段可回放的录像
TiCloudStorageReplay回放云录像、提供播放控制并返回回放错误
TiCloudStorageAudioOutput播放 Replay 的一路音频
TiCloudStorageVideoOutput显示 Replay 的一路视频并保存当前画面
TiCloudStorageRecordingTask保存 Replay 正在播放的一段内容
TiCloudStorageExportTask把指定时间范围内的云录像直接导出到本地
TiRawDump按 Channel ID 采集 Replay 的原始音视频,用于问题诊断
TiCloudStorageRecordingFileSDK 缓存目录中的临时 MP4、实际媒体时长与后续文件操作
TiCloudStorageSnapshotFileSDK 缓存目录中的临时 JPEG 与后续文件操作
TiRtcLogging上传 SDK 日志并返回日志编号

常规的“列出录像并开始音视频回放”流程如下。示例只展示正常调用顺序;回放期间保持 replay、videoOutput 和 audioOutput 有效:

dart
final initCode = await TiCloudStorage.init(appId: appId);
if (initCode != kTiCloudStorageErrorOk) return;

final cloudStorage = TiCloudStorage(token: token);
final recordingsResp = await cloudStorage.listRecordings(
  startTimeMs: startTimeMs,
  endTimeMs: endTimeMs,
);
if (!recordingsResp.success) {
  return;
}

final recordings = recordingsResp.data!;
if (recordings.isEmpty) return;

final recording = recordings.first;
final replay = cloudStorage.createReplay();
final videoOutput = TiCloudStorageVideoOutput();
final audioOutput = TiCloudStorageAudioOutput();
replay.onError = showReplayError;
replay.onCompleted = markReplaySourceCompleted;

videoOutput.attach(
  replay: replay,
  channelId: videoChannelId,
);
audioOutput.attach(
  replay: replay,
  channelId: audioChannelId,
);
replay.play(
  startTimeMs: recording.startTimeMs,
  endTimeMs: recording.endTimeMs,
);

attach 或 play 返回非 kTiCloudStorageErrorOk 时,当前操作没有生效。回放过程中出现 Token、网络或录像读取错误时,replay.onError 返回对应错误码;Replay 来源范围自然耗尽时,replay.onCompleted 通知一次,此时 Output 可能仍未排空。Output 状态用于显示各路媒体的加载、播放、暂停和排空状态。

在播放页的 build 方法中使用 AspectRatio(child: videoOutput.view()) 显示画面,完整代码见查询与播放录像。页面退出时,先 stop Replay,再依次 detach、dispose 两个 Output 和 Replay。不再访问这台设备时释放 cloudStorage;应用不再使用 Ti 云存时,最后调用 TiCloudStorage.shutdown()。

二、共同约定 ​

时间 ​

  • startTimeMs、endTimeMs、timeMs 和 actualTimeMs 都是 UTC Unix 毫秒时间戳;
  • startTimeMs 必须小于 endTimeMs;
  • 时间戳直接使用 Dart int;普通 Unix 毫秒值都在支持范围内;
  • SDK 不根据本地时区对时间戳做隐式换算。

channel_id ​

  • Ti 云存使用设备写入云录像的 channel_id,有效范围是 0..255;
  • 视频和音频分别使用设备配置或业务服务提供的 Channel ID,两者可以相同,也可以不同;
  • Flutter Ti 云存 API 使用 channelId 作为参数名。Output 已经确定媒体类型,因此 attach 只选择一路对应媒体。每个 MP4 Task 必须选择一个视频通道,并且可以再选择一个音频通道。

Token、设备与录像范围 ​

  • APP Access Token 由应用服务端为一台目标设备申请,并且只授权该设备;
  • SDK 把 Token 作为不透明字符串使用,不解析其内容;
  • 使用 Token 创建一个 TiCloudStorage 实例;查询、Replay 和导出都属于这台设备;
  • 同时访问多台设备时,每台设备创建独立实例,不用 updateToken 把一个实例切换到另一台设备;
  • List、Play 和 Export 在启动时固定使用当时的 Token。updateToken 只影响之后启动的操作,不迁移、恢复或重试已经开始的操作;
  • 当前 Token 被云端明确判定为过期时,触发操作直接返回共享错误 token-expired。应用取得新 Token、调用 updateToken 后,自行重新发起失败的操作;SDK 不另发过期通知。

错误和异步结果 ​

  • 同步操作返回整数错误码;
  • 异步操作通过 Future 返回一次最终结果;
  • 云端业务查询使用 Resp<T>。success 表示请求是否成功,data 是当前 API 返回的业务数据;
  • SDK 运行错误不抛 Dart exception;
  • startRecording 和 exportRecording 同步返回 Resp<Task>;成功只表示 Task 已经建立;
  • RecordingTask 的最终结果通过 stop() 返回,ExportTask 自然完成时通过 result 返回基本文件结果;两者成功时都交付 TiCloudStorageRecordingFile。只有需要判断完整性或统计实际覆盖时长时,才需要等待 ExportTask 的 completion 并读取详细报告。

三、基础类型 ​

Resp<T> ​

dart
final class Resp<T> {
  // SDK 内部构造;不公开 constructor。
  final bool success;
  final int? code;
  final String? message;
  final T? data;
}
字段说明
success请求是否成功;为 true 时读取 data
code请求失败时的错误码;成功时为 null
message面向展示或诊断的错误说明;不要用它判断错误类型
data当前 API 的业务数据;请求失败时为 null

Resp<T> 由 SDK 创建,调用方只读取结果。success == true 时,data 一定非空,code 和 message 为 null;success == false 时,data 为 null,code 非空。SDK 保证这组不变量,因此检查成功后可以直接读取 data!。

TiCloudStorageRecordingRange ​

dart
final class TiCloudStorageRecordingRange {
  const TiCloudStorageRecordingRange(this.startTimeMs, this.endTimeMs);

  final int startTimeMs;
  final int endTimeMs;
}

TiCloudStorageRecordingDay ​

dart
final class TiCloudStorageRecordingDay {
  const TiCloudStorageRecordingDay({
    required this.date,
    required this.hasRecording,
  });

  final String date;
  final bool hasRecording;
}

date 是严格 YYYY-MM-DD 日期。hasRecording 为 true 只表示该自然日至少有一个当前可见的录像时点。

表示一段存在云录像的可用时间。

字段说明
startTimeMs开始时间,UTC Unix 毫秒
endTimeMs结束时间,UTC Unix 毫秒;必须晚于 startTimeMs

录像缺口与导出报告 ​

dart
enum TiCloudStorageRecordingTrackKind { video, audio }

final class TiCloudStorageRecordingTrack {
  final TiCloudStorageRecordingTrackKind kind;
  final int channelId;
}

enum TiCloudStorageRecordingGapReason {
  unknown,
  notFound,
  downloadFailed,
  integrityFailed,
  mediaUnreadable,
  noKeyFrame,
  noRecording,
  decryptionFailed,
  unsupportedMedia,
  trackUnavailable,
}

final class TiCloudStorageRecordingGap {
  final TiCloudStorageRecordingRange range;
  final List<TiCloudStorageRecordingTrack> tracks;
  final List<TiCloudStorageRecordingGapReason> reasons;
}

final class TiCloudStorageExportProgress {
  final double fraction;
  final Duration coveredDuration;
}

final class TiCloudStorageExportSegment {
  final TiCloudStorageRecordingRange sourceRange;
  final Duration outputStart;
  final Duration outputEnd;
}

enum TiCloudStorageExportTermination {
  exhausted,
  interrupted,
  cancelled,
  failed,
}

final class TiCloudStorageExportReport {
  final TiCloudStorageRecordingRange requestedRange;
  final Duration coveredDuration;
  final List<TiCloudStorageExportSegment> segments;
  final List<TiCloudStorageRecordingGap> gaps;
  final List<TiCloudStorageRecordingRange> unprocessedRanges;
  final bool complete;
  final TiCloudStorageExportTermination termination;
  final int cause;
}

final class TiCloudStorageExportOutcome {
  final int code;
  final TiCloudStorageRecordingFile? file;
  final TiCloudStorageExportReport? report;
}

TiCloudStorageRecordingGap 描述 SDK 已经确认的来源缺口,包含时间范围、受影响的音视频 Channel 和原因。TiCloudStorageExportReport 则记录请求范围、实际覆盖时长、输出片段、缺口、未处理区间和结束原因。它们是完整性展示或统计场景的可选信息;普通下载只需检查基本结果和文件。

本地媒体文件 ​

dart
final class TiCloudStorageGalleryAsset {
  final Uri uri;
}

final class TiCloudStorageRecordingFile {
  final String path;
  final Duration duration;

  Future<Resp<TiCloudStorageGalleryAsset>> moveToGallery({String? fileName});
  Future<int> delete();
}

final class TiCloudStorageSnapshotFile {
  final String path;

  Future<Resp<TiCloudStorageGalleryAsset>> moveToGallery({String? fileName});
  Future<int> delete();
}

path 在成功返回时指向 SDK 私有 cache。TiCloudStorageRecordingFile.duration 是 MP4 的实际媒体时长,不是 Task 从开始到结束经过的墙钟时间。需要长期保存时及时调用 moveToGallery() 或自行复制、移动;直接使用完成后调用 delete()。

moveToGallery({fileName}) 的平台目标、文件名规则和权限职责与 RTC 文件对象一致。Android、iOS 和 OHOS 写入系统媒体库;macOS 写入用户的 Downloads 目录。省略名字时使用毫秒时间戳默认名;非法名字返回 6000,不创建资产。

成功后返回不透明媒体资产 URI,并删除 cache 源文件;失败时保留源文件,可换名重试。delete() 只清理 SDK 生成的临时 MP4/JPEG,且可以重复调用。两项操作互斥;规范化后同名的并发 move 合并,不同名返回 in-use。成功后任何合法名字都返回第一次创建的 asset,path 只保留历史值。SDK 不申请图库权限,应用应在调用前完成 Android 或 iOS 授权;OHOS 的系统资产创建确认只由用户保存动作触发。

TiCloudStorageReplaySpeed ​

dart
enum TiCloudStorageReplaySpeed {
  x0_125,
  x0_25,
  x0_5,
  x1,
  x2,
  x4,
  x8,
}
枚举值说明
x0_1251/8 倍速,视频慢放,音频静音
x0_251/4 倍速,视频慢放,音频静音
x0_51/2 倍速,视频慢放,音频静音
x11 倍速,播放音频和视频
x22 倍速,视频播放,音频静音
x44 倍速,视频播放,音频静音
x88 倍速,视频播放,音频静音

四、TiCloudStorage ​

dart
final class TiCloudStorage {
  TiCloudStorage({required String token});

  static Future<int> init({
    required String appId,
    String endpoint = '',
    bool consoleLogEnabled = false,
  });

  static int shutdown();

  static String errorToString(int code);

  int updateToken(String token);

  Future<Resp<List<TiCloudStorageRecordingDay>>> listRecordingDays({
    required String startDate,
    required String endDate,
    String timeZoneId = 'Asia/Shanghai',
  });

  Future<Resp<List<TiCloudStorageRecordingRange>>> listRecordings({
    required int startTimeMs,
    required int endTimeMs,
  });

  TiCloudStorageReplay createReplay();

  Resp<TiCloudStorageExportTask> exportRecording({
    required int startTimeMs,
    required int endTimeMs,
    required int videoChannelId,
    int? audioChannelId,
    void Function(double progress)? onProgress,
    void Function(TiCloudStorageExportProgress progress)? onProgressDetail,
    void Function(TiCloudStorageRecordingGap gap)? onRecordingGap,
  });

  int dispose();
}

appId 必须非空,使用控制台分配的 AppId,并与应用服务端申请 Token 时使用的 AppId 一致。endpoint 只在专有环境或测试环境中设置;它不是对象存储地址。consoleLogEnabled 默认关闭。

使用任何 Ti 云存 API 前先调用 TiCloudStorage.init。使用相同配置重复初始化会成功,配置不同则返回 already-initialized。初始化后用一台设备的非空 Token 创建 TiCloudStorage 实例;创建实例不发起网络请求。同时访问多台设备时创建多个实例。释放全部 TiCloudStorage 实例、Replay、Output 和任务后调用 TiCloudStorage.shutdown。

TiRTC 与 Ti 云存可以使用不同的 App ID 和 endpoint。Flutter package 从平台 cache 目录解析 tirtc 工作根目录,并将同一路径用于 TiRTC 与 TiCloudStorage;两者同时运行时,consoleLogEnabled 配置也必须一致。Runtime 在该目录下管理日志、日志归档和临时媒体文件。调用 TiCloudStorage.shutdown() 不会停止 TiRTC。

TiCloudStorage.shutdown() 在尚未初始化或已经关闭时幂等返回成功;仍有活动资源时返回 in-use。在对象回调中同步停止、解绑或释放当前对象及其关联资源,也会返回 in-use 且不改变状态。其他无关实例和资源不受此限制。

cloudStorage.dispose() 只有在底层资源释放成功后才使实例失效。若返回 in-use 或其他错误,实例和当前 Token 仍然有效;清理占用资源后可以重试。SDK 不负责清除调用方持有的 Token 字符串。

API说明
init(appId, endpoint, consoleLogEnabled)初始化 Ti 云存。appId 必填;endpoint 留空时使用默认服务地址;consoleLogEnabled 默认为 false。Future 返回初始化错误码
shutdown()关闭 Ti 云存。调用前必须释放所有 TiCloudStorage 实例、Replay、Output,并等待查询、截图和文件 Task 结束
errorToString(code)返回错误码对应的稳定名称,用于日志和诊断
updateToken(token)为同一设备更新 Token;只影响之后启动的 List、Play 和 Export
listRecordingDays(startDate, endDate, timeZoneId)查询包含式日期范围,单次最多 31 天。timeZoneId 使用 IANA ID,默认 Asia/Shanghai;Future 返回完整、升序的 Resp<List<TiCloudStorageRecordingDay>>
listRecordings(startTimeMs, endTimeMs)查询当前设备在 [startTimeMs, endTimeMs) 内的录像可用时间段。Future 返回 Resp<List<TiCloudStorageRecordingRange>>
createReplay()创建属于当前设备的 Replay
exportRecording(...)开始一次录像导出。视频 Channel 必填,音频 Channel 和进度、缺口回调可选。同步返回 Resp<TiCloudStorageExportTask>
dispose()释放当前设备实例;仍有查询、Replay、ExportTask 或回调正在使用时返回 in-use

Token 过期由触发它的 List、Play 或 Export 直接报告 token-expired。SDK 不另发过期通知,也不自动刷新或重试。updateToken 的非空新值在返回前被 SDK 保存,供之后启动的操作使用。传入相同 Token 是幂等操作,不会清除已经确定的过期状态。updateToken 只用于同一设备续签,访问另一台设备时创建新的 TiCloudStorage。

listRecordingDays ​

dart
Future<Resp<List<TiCloudStorageRecordingDay>>> listRecordingDays({
  required String startDate,
  required String endDate,
  String timeZoneId = 'Asia/Shanghai',
});

startDate 和 endDate 必须是真实存在的 YYYY-MM-DD 日期,起止都包含在结果中。日期倒置、超过 31 天、空时区或云端无法识别的 IANA 时区会使整次查询失败。成功结果精确包含范围内的每一天,包括 hasRecording=false 的日期。

listRecordings ​

dart
Future<Resp<List<TiCloudStorageRecordingRange>>> listRecordings({
  required int startTimeMs,
  required int endTimeMs,
});

查询设备在指定 UTC 时间范围内的云录像。单次查询范围最长为 10 天,最多返回 10000 段;没有录像时,成功结果的 data 为空列表。

结果按 startTimeMs 升序排列并裁剪到 [startTimeMs, endTimeMs)。重叠或首尾相接的时间段会合并,真实空洞会保留;超过范围或结果数量上限时,本次查询整体失败,不返回部分结果。

exportRecording ​

dart
Resp<TiCloudStorageExportTask> exportRecording({
  required int startTimeMs,
  required int endTimeMs,
  required int videoChannelId,
  int? audioChannelId,
  void Function(double progress)? onProgress,
  void Function(TiCloudStorageExportProgress progress)? onProgressDetail,
  void Function(TiCloudStorageRecordingGap gap)? onRecordingGap,
});

同步检查参数和本地资源。成功时 data 是已经开始运行的独立 ExportTask;失败时不创建 Task,也不留下临时文件。Runtime 在初始化时确定的私有 cache 中为每个 Task 生成唯一 MP4,调用方不传输出路径。

只需要显示普通进度时传入 onProgress。需要统计实际覆盖时长或展示录像缺口时,再按需传入 onProgressDetail 和 onRecordingGap。SDK 在启动 Task 前保存回调,并且不会在 exportRecording 返回前调用它们。

  • videoChannelId 必填,audioChannelId 可空,值必须位于 0..255;两者可以相同;
  • 录像中没有媒体的时间会被跳过,不写黑帧或静音;
  • onProgressDetail 中的 fraction 表示请求范围的扫描进度,coveredDuration 表示已经纳入输出的媒体时长;
  • onRecordingGap 只返回导出过程中已经确认的缺口,最终是否完整以 completion.report 为准;
  • 多次调用彼此独立并生成不同文件;超过并发资源上限返回 resource-exhausted。

输入格式、转码和关键帧规则见下载录像。

五、TiCloudStorageReplay ​

dart
final class TiCloudStorageReplay {
  TiCloudStorageReplay._();

  TiCloudStorageReplaySpeed get speed;
  int? get currentTimeMs;
  void Function(int timeMs)? onTimeChanged;
  void Function()? onCompleted;
  void Function(int code)? onError;
  void Function(TiCloudStorageRecordingGap gap)? onRecordingGap;

  int play({
    required int startTimeMs,
    required int endTimeMs,
    int? initialTimeMs,
  });

  int pause();
  int resume();

  int seek(int timeMs);

  int setSpeed(TiCloudStorageReplaySpeed speed);

  Resp<TiCloudStorageRecordingTask> startRecording({
    required int videoChannelId,
    int? audioChannelId,
  });

  // 由 TiCloudStorageReplayRawDump 扩展提供。
  Future<Resp<TiRawDump>> startRawDump(
    TiCloudStorageRawDumpOptions options,
  );

  int stop();
  int dispose();
}

Replay 只能由 cloudStorage.createReplay() 创建,并固定属于该设备实例。它提供播放、暂停、恢复、Seek、倍速和停止操作。对应 Output 返回音频和视频的缓冲、输出、暂停、完成或失败状态。Token、网络或录像读取错误通过 Replay 返回。

属性和时间回调 ​

  • speed:当前速度,初始值为 TiCloudStorageReplaySpeed.x1;
  • currentTimeMs:当前已知的绝对 UTC 回放时间;尚未开始、尚未获得首个时间位置或已经停止时为 null。暂停、自然完成或回放失败时保留最后一次实际回调的媒体位置,不会因为来源读取完成自动改成 endTimeMs;
  • onTimeChanged:回放时间变化通知。中间通知可以合并,应用不能用通知次数计算时长。
  • onCompleted:当前 play 请求的时间范围自然耗尽时恰好调用一次。主动 stop、被新的 play 替换、最后一个消费者解除绑定或回放失败都不会触发;它表示 Replay 来源已自然结束,不表示各 Output 已经排空;
  • onError:返回当前 play 过程中发生的 Token、权限、网络或录像读取错误。错误发生时恰好调用一次;同步拒绝、主动 stop、被新 play 替换或自然完成不会触发。
  • onRecordingGap:Replay 确认并越过录像缺口时触发,返回时间范围、受影响的音视频 Channel 和原因。缺口后仍有可解码媒体时,Replay 可继续播放。

晚设置回调不会重放此前事件。开始 play 前设置 onCompleted、onError 和按需设置的 onRecordingGap;需要立即显示当前位置时,先读取 currentTimeMs,再设置 onTimeChanged。范围自然耗尽时,Replay 会自动终结活动 RecordingTask;应用稍后调用 Task 的 stop() 取得缓存结果。

onRecordingGap 只报告 SDK 已经确认并越过的来源缺口,不表示所有下载、解密或解析错误都能继续。无法继续时,Replay 仍通过 onError 结束。回调返回创建 Replay 的 Dart isolate,通常是 UI isolate;回调内应快速返回,不要同步停止或释放当前 Replay。

原始音视频诊断采集 ​

dart
Future<Resp<TiRawDump>> startRawDump(
  TiCloudStorageRawDumpOptions options,
);

TiCloudStorageRawDumpOptions 通过 audioChannelIds 和 videoChannelIds 选择要采集的原始音视频,两组 Channel ID 至少填一组,每个值位于 0..255。保留成功返回的 TiRawDump 采集任务对象,复现问题后调用它的 stop() 取得 TiRawDumpArchive,再检查 captureComplete、stopReason、unsavedPacketCount、unsavedByteCount 和 empty。

同一进程中 TiRTC 和 Ti 云存共用诊断采集资源,同时只能进行一个原始音视频采集任务。采集文件在停止采集后随下一次 TiRtcLogging.upload() 上传;完整示例、限制和隐私要求见接入客户端诊断能力。

play ​

dart
int play({
  required int startTimeMs,
  required int endTimeMs,
  int? initialTimeMs,
});

开始回放当前 TiCloudStorage 实例所绑定设备的指定 UTC 时间范围。initialTimeMs 省略时从 startTimeMs 开始;传入时从指定位置开始。Replay 必须已经绑定至少一个 Audio/Video Output。方法接受请求时固定使用实例当前的 Token;之后调用 updateToken 不改变本次 Play。

返回 kTiCloudStorageErrorOk 表示请求已经接受,不表示媒体已经开始输出。通过 Output 状态更新播放界面;Token、网络或录像读取失败时,在 Replay onError 中处理。

startTimeMs 必须早于 endTimeMs,initialTimeMs 必须位于 [startTimeMs, endTimeMs)。参数无效时同步返回 invalid-argument,当前回放不受影响。请求被接受后直接从初始位置建立回放,不会先输出范围开头的媒体。初始位置落在录像空洞中时,从其后的第一个可播放位置开始;直到范围末尾都没有录像时,按自然结束处理。

同一个 Replay 可以再次调用 play:

  • 新请求被接受后,前一个回放停止;
  • 已绑定的 Output、Audio Output 音量和当前速度保留;
  • 新回放从非暂停状态开始;
  • 新请求被同步拒绝时,原来的回放不受影响。

pause / resume ​

dart
int pause();
int resume();

控制当前 Replay 的暂停与继续。

  • 暂停成功后,已绑定 Output 进入 paused;
  • 恢复后,Output 根据当前数据进入 buffering、playing 或 rendering;
  • 已经暂停时再次 pause、已经继续时再次 resume,都会成功且不产生额外任务。
  • 尚未 play、已经 stop、播放完成或回放失败时返回 not-started。

seek ​

dart
int seek(int timeMs);

通知 Replay 跳转到当前回放范围内的绝对 UTC 时间。返回 kTiCloudStorageErrorOk 表示定位命令已经接受,不表示媒体已经到达目标位置。

  • timeMs 必须位于当前 [startTimeMs, endTimeMs);越界时同步返回 invalid-argument,当前回放不受影响;
  • Seek 保留当前速度和暂停意图;连续调用时,以最后一次 Seek 的目标为准;
  • 定位生效后,currentTimeMs 和 onTimeChanged 更新为第一个不早于 timeMs 的可播放位置;
  • 目标合法但其后直到范围末尾都没有录像时,位置推进到 endTimeMs,并按自然结束触发 onCompleted。

Seek 只能在当前 play 仍在运行或暂停时调用。尚未 play、已经 stop、自然完成或回放失败时返回 not-started。自然完成后要从指定位置重新播放时,调用带 initialTimeMs 的 play。

setSpeed ​

dart
int setSpeed(TiCloudStorageReplaySpeed speed);

设置当前和后续 play 使用的固定速度,可选 1/8x、1/4x、1/2x、1x、2x、4x 和 8x。非 1x 时 Audio Output 静音;慢放画面采用持帧显示。设置相同速度是幂等操作。

startRecording ​

dart
Resp<TiCloudStorageRecordingTask> startRecording({
  required int videoChannelId,
  int? audioChannelId,
});

在当前回放中开始保存一段 MP4。Replay 必须正在运行。videoChannelId 必填,audioChannelId 可空;两者都必须位于 0..255。

成功只表示 Task 已经建立并开始接收媒体。Replay 未活动或资源达到上限时,同步返回失败且 data 为 null。同一个 Channel 可以启动多个 Task,SDK 为每个 Task 生成不同文件。两个视频 Channel 使用两个 Task。

stop ​

dart
int stop();

停止当前回放并清除 currentTimeMs。已绑定 Output 回到 idle。尚未开始、已经完成或已经失败时调用 stop 也会成功。

stop 不产生 completed,并且保留 Output 绑定和速度。之后再次调用 play 不需要重新 attach。

dispose ​

dart
int dispose();

永久释放 Replay。调用前必须先 stop,结束全部 RecordingTask 和原始音视频采集任务,并显式 detach 全部 Output;否则返回 in-use,对象保持有效,可完成清理后重试。成功后不再投递 Replay 回调,且对象不能再次使用。

六、Audio/Video Output ​

下面列出云录像任务会用到的 Output 成员。

dart
final class TiCloudStorageAudioOutput {
  TiCloudStorageAudioOutput();

  TiCloudStorageAudioOutputState get state;
  void Function(TiCloudStorageAudioOutputState state)? onStateChanged;
  void Function(int code)? onError;

  int attach({required TiCloudStorageReplay replay, required int channelId});
  int setVolume(int volumePercent);
  int detach();
  int dispose();
}

final class TiCloudStorageVideoOutput {
  TiCloudStorageVideoOutput();

  TiCloudStorageVideoOutputState get state;
  Size? get renderSize;
  void Function(TiCloudStorageVideoOutputState state)? onStateChanged;
  void Function(Size size)? onRenderSizeChanged;
  void Function(int code)? onError;

  int attach({required TiCloudStorageReplay replay, required int channelId});
  Future<Resp<TiCloudStorageSnapshotFile>> takeSnapshot();
  int detach();
  Widget view();
  int dispose();
}

属性与回调 ​

成员适用对象说明
stateAudio、Video当前 Output 状态
renderSizeVideo最近一次视频画面尺寸;尚未出画时为 null
onStateChangedAudio、VideoOutput 状态变化通知
onRenderSizeChangedVideo视频画面尺寸变化通知
onErrorAudio、Video当前 Output 的解码、播放或渲染错误;不会报告 Token、网络或录像读取错误

方法 ​

API适用对象说明
attach(replay, channelId)Audio、Video绑定 Replay 中指定 channel_id 的音频或视频;channelId 取值为 0..255
setVolume(volumePercent)Audio设置当前 Output 音量,取值 0..100;0 表示静音,不修改系统全局音量
takeSnapshot()Video把当前视频画面保存为唯一的临时 JPEG;Future 成功时通过 data 返回 TiCloudStorageSnapshotFile
detach()Audio、Video解除当前 Replay 绑定;对象仍可再次 attach
view()Video返回该 Output 的渲染 Widget;同一个 Output 同时只能挂载一个 view()
dispose()Audio、Video释放已解除绑定的 Output;返回错误码,成功后不能继续使用该实例

Output 状态 ​

dart
enum TiCloudStorageAudioOutputState {
  idle,
  buffering,
  playing,
  failed,
  paused,
  completed,
}

enum TiCloudStorageVideoOutputState {
  idle,
  buffering,
  rendering,
  failed,
  paused,
  completed,
}
状态Replay 场景中的含义
idle没有活动回放、已经 stop 或已经解除绑定
buffering回放活动,但当前没有足够媒体
playing / rendering正在输出音频或视频
pausedReplay 已暂停
completed目标时间范围结束,当前 Output 已排空
failed当前 Output 已无法继续;可能是回放失败,也可能是该 Output 自己失败

completed 只表示范围耗尽,不证明这个 Output 曾经输出媒体。如果回放范围内完全没有该媒体类型的帧,Output 不执行 Channel ID 匹配检查,可以直接进入 completed。不要用 completed 判断 Channel ID 是否正确,应结合是否收到媒体和 onError 判断播放结果。

某个 Output 解码、播放或渲染失败时,它通过自己的 onError 返回错误,其他 Output 可以继续工作。Token、网络、所选范围过大或录像读取失败时,Replay 的 onError 返回一次错误,相关 Output 进入 failed。修正问题后再次调用 play,有效的 Output 会从 buffering 重新开始。

绑定规则 ​

  • attach 绑定 Replay 和对应媒体的 channelId;
  • 重复绑定同一个 Replay 和 channel_id 会成功;
  • 已有不同 Replay 或 channel_id 的绑定时返回 in-use 并保留原绑定;换绑前必须先 detach;
  • Replay 正在运行时新增 Output,从绑定生效后的下一可独立解码位置开始,不补发此前媒体;
  • 同一个 Replay 的同一个 channel_id 同时只能绑定一个同类播放 Output;
  • stop、completed 和新的 play 都保留绑定;
  • Output 只有 detach 会解除 Replay 绑定;dispose 不隐式解绑;
  • 最后一个 Output 离开且没有活动 RecordingTask 时,当前回放自动停止,但不触发 onCompleted;
  • RecordingTask 与播放 Output 相互独立;释放一个 Output 不会停止其他 Output 或活动 RecordingTask。

Snapshot ​

dart
Future<Resp<TiCloudStorageSnapshotFile>> takeSnapshot();

保存 Video Output 当前仍可取得的最近画面。SDK 在可写缓存目录生成唯一的 JPEG。

  • 成功时 data.path 是 SDK 私有 cache 中的临时 JPEG,失败时 code 给出错误;
  • 需要长期保存时调用返回文件对象的 moveToGallery(),直接使用后调用 delete();
  • 当前 binding generation 尚未出画时返回 no-frame;暂停或自然播放完成后,只要最后画面仍然保留,就可以继续截图;
  • attach 新来源、新的 play 生效、detach 或移除渲染 View 后,在新画面到达前返回 no-frame;
  • Snapshot 不捕获 Flutter Widget、字幕、按钮或其他页面叠加内容;
  • 同一个 Video Output 同时只接受一个 Snapshot;已有请求未完成时,新的 Future 以 in-use 完成。不同 Output 的请求仍可能因全局资源上限以 resource-exhausted 完成;
  • Snapshot Future 完成前,dispose() 和 TiCloudStorage.shutdown() 返回 in-use,对象与原有绑定保持不变。先等待 Future,再按正常顺序解除绑定并释放 Output。

七、TiCloudStorageRecordingTask ​

dart
final class TiCloudStorageRecordingTask {
  TiCloudStorageRecordingTask._();

  Future<Resp<TiCloudStorageRecordingFile>> stop();
}

RecordingTask 只能由活动 Replay 的 startRecording 创建。Replay、视频 Channel 和可选音频 Channel 在创建时一次确定;输出路径由 SDK 在私有 cache 中生成。

RecordingTask 和 ExportTask 的 MP4 格式范围见确认录像可以生成 MP4。一个 Task 期间,所选视频 Channel 的编码格式和分辨率必须保持不变;跨越格式变化点时,按格式稳定的时间范围分别创建 Task。

stop() 停止接收新媒体,排空已经接收的数据,完成 MP4 并返回文件结果。第一次调用开始终结;重复调用返回同一个 Future 和同一份结果。

  • 成功时 data.path 是临时 MP4 路径,data.duration 是文件实际媒体时长;
  • 还没有形成可写媒体时返回 no-recordable-media;
  • codec、磁盘、媒体处理或来源失败时返回对应错误,且不保留半成品;
  • Replay 自然结束时 Task 自动完成;随后调用 stop() 取得已经缓存的结果;
  • 停止一个 RecordingTask 不影响 Replay 和其他 Task。

Task 没有 state、实时 duration、cancel、result 属性或 dispose。页面显示录制秒数时,由应用从开始动作自行计时;最终准确时长以 TiCloudStorageRecordingFile.duration 为准。

八、TiCloudStorageExportTask ​

dart
final class TiCloudStorageExportTask {
  TiCloudStorageExportTask._();

  double get progress;
  TiCloudStorageExportProgress get progressDetail;
  TiCloudStorageExportReport? get report;

  Future<Resp<TiCloudStorageRecordingFile>> get result;
  Future<TiCloudStorageExportOutcome> get completion;

  int cancel();
  Future<Resp<TiCloudStorageRecordingFile>> stop();
}

TiCloudStorageExportTask 只能由设备实例的 exportRecording 创建。它与 Replay 和 Output 相互独立,并在启动时固定使用实例当前的 Token。

成员说明
progress当前导出进度,取值为 0.0..1.0
progressDetail当前扫描比例和已覆盖媒体时长
result基本文件结果;普通导出等待这个 Future 即可
completion包含错误码、可用文件和详细报告的最终结果;有完整性或统计需求时使用
report任务终结后的详细报告;终结前为 null
cancel()发出非阻塞取消请求;最终结果仍通过 result 或 completion 取得
stop()停止活动导出并等待清理;返回同一个最终结果 Future

progress ​

  • 进度范围是 0.0..1.0,并且不会回退;
  • 跨越录像空洞时,进度可能直接跃迁;
  • 进度到达 1.0 表示请求范围扫描完成,不表示录像必然覆盖整个范围;
  • exportRecording 传入的进度通知可以合并,progress 始终返回当前快照。

Export result ​

dart
Future<Resp<TiCloudStorageRecordingFile>> get result;

同一个 Task 始终返回同一个 Future,并且只完成一次。

  • 成功生成可用文件后返回成功;
  • 成功结果包含临时文件 path 和实际媒体 duration;
  • 失败或主动停止会删除临时文件;
  • result 完成后自动释放底层执行资源,不提供 dispose。

普通下载不需要额外读取报告:判断 result.success 和 result.data 就可以取得 MP4。只有业务要求完整覆盖请求范围、展示缺口或统计实际媒体时长时,才需要等待 completion,并读取 report.complete、report.gaps、report.unprocessedRanges 和 report.coveredDuration。

completion 和 report ​

dart
Future<TiCloudStorageExportOutcome> get completion;
TiCloudStorageExportReport? get report;

completion 和 result 描述同一个终局,都只完成一次。completion.code 是最终错误码,file 是可用的输出文件,report 是详细报告。报告中 complete == false 表示实际媒体没有完整覆盖请求范围;它不会自动让一个已经成功生成的可播放 MP4 变成不可用。业务是否接受这份文件,由自己的完整性要求决定。

cancel ​

dart
int cancel();

cancel() 只发出取消请求,返回成功不表示导出已经停止。取消与自然完成可能竞争;继续等待 result 或 completion,以最终结果为准。

stop ​

dart
Future<Resp<TiCloudStorageRecordingFile>> stop();

活动任务调用 stop() 会停止读取并删除未完成文件,以 stopped 返回;它不会把已处理部分当作成功文件。任务已经自然完成或失败时,stop() 返回缓存的同一结果。重复调用返回同一个 Future。

九、TiRtcLogging ​

TiRtcLogging 用于上传 SDK 日志。完成 Ti 云存初始化后调用;上传成功后,把返回的 logId 提供给支持人员。开发阶段的入口设计和接入流程见接入客户端诊断能力。

dart
// 上传当前 SDK 日志;code == 0 且 logId 非空表示上传成功。
static Future<({int code, String? logId})> upload()

示例:

dart
final ({int code, String? logId}) result = await TiRtcLogging.upload();
if (result.code == 0 && (result.logId?.isNotEmpty ?? false)) {
  debugPrint('Ti Cloud Storage logId=${result.logId}');
}

调用前必须完成 TiCloudStorage.init,并等待 Future 返回后再调用 TiCloudStorage.shutdown()。非 0 错误码或空 logId 表示没有取得可提交的日志编号;按问题排查保留错误码和原始日志。

十、生命周期与回调 ​

并发规则 ​

  • 不同 RecordingTask、ExportTask 和不同 Video Output 的 Snapshot 是独立操作,可以同时运行;
  • 每个 Task 生成一个 MP4,同一个 Video Output 同时只接受一个 Snapshot;
  • 相同 Channel 可以创建多个 Task,SDK 为每个 Task 生成不同文件;两个视频 Channel 使用两个 Task;
  • SDK 对并发网络、媒体处理和文件任务设置全局资源上限;超过上限的请求返回 resource-exhausted,不会建立无界队列;

回调与关闭 ​

  • TiCloudStorage 实例绑定一台设备并提供查询、Replay 和导出;Output、RecordingTask 和 ExportTask 是具体资源;
  • 回调和 Future completion 返回创建对象的 Dart isolate,通常就是应用的 UI isolate;
  • 状态、时间和进度回调表示当前事实,中间变化可以合并;
  • Replay 和 Output 的状态或时间回调晚设置时不会重放此前事件,需要立即显示时先读取对象当前属性;Export 的进度回调在 exportRecording 中注册;
  • 最终 result 和 completion 不会被合并或丢失;
  • 对象 dispose、Task 返回最终结果或 TiCloudStorage.shutdown 成功后,不再投递对应状态事件;已经接受的一次性 Future 仍会恰好完成;
  • Ti 云存和 Output 构造器只创建 Dart 对象,不申请底层资源,也不抛 SDK 运行错误;createReplay 同样只创建 Replay 对象。第一次需要底层资源的方法会返回创建错误;
  • 页面退出时先结束活动 Task 并等待已接受的 Snapshot,再停止 Replay、释放 Output 和 Replay;
  • 不再访问对应设备时释放 TiCloudStorage 实例;应用释放全部 TiCloudStorage 实例、Replay、Output 和任务后,再调用 TiCloudStorage.shutdown。

十一、错误码 ​

方法和回调会返回完整整数错误码。需要业务分支处理的常见错误在 package 根入口提供公开常量;常量列为“—”时没有对应的命名常量,错误码仍会原样返回。

错误码Flutter 常量常见含义建议处理
0kTiCloudStorageErrorOk操作成功继续后续流程
6000kTiCloudStorageErrorInvalidArgument参数缺失、取值越界或时间范围无效修正参数后重试
6001kTiCloudStorageErrorNotInitialized尚未初始化 TiCloudStorage,或已经关闭先调用 TiCloudStorage.init
6014kTiCloudStorageErrorTokenExpiredTi 云存 Token 已过期为同一设备更新 Token 后重新发起请求
6022kTiCloudStorageErrorAlreadyInitialized已使用另一组配置初始化保持初始化参数一致,或在全部资源释放后重新初始化
6024kTiCloudStorageErrorPermissionDeniedToken 无效、无权访问目标录像,或应用尚未取得系统相册写入权限检查对应授权;云端访问失败时重新取得 Token
6026kTiCloudStorageErrorInUse对象仍在播放、录制或执行任务,当前操作不能执行先结束活动操作,再重试
6027kTiCloudStorageErrorNotStarted尚未开始播放或录制先启动对应操作
6029kTiCloudStorageErrorNotBound对象尚未绑定 Replay完成 attach 后重试
6030kTiCloudStorageErrorNotConfigured尚未选择媒体通道或完成必要配置补充通道或配置后重试
6043kTiCloudStorageErrorResourceExhausted当前设备没有足够资源创建或继续任务结束其他任务并释放资源后重试
6044—初始化时无法创建或打开 SDK 工作目录检查应用缓存目录和文件系统状态后重试
6045—移动到系统媒体库时无法读取源缓存文件,或源文件已经删除确认文件对象仍有效,并检查缓存文件是否可读
6046kTiCloudStorageErrorFileWriteFailed无法创建或写入输出文件检查目录权限和磁盘空间
6113kTiCloudStorageErrorUnsupportedFormat当前操作遇到不支持的音视频格式;保存或导出任务内的视频编码格式或分辨率发生变化时也可能返回先确认失败的是播放、保存还是导出;保存或导出时按格式稳定的时间范围拆分任务
6114kTiCloudStorageErrorIoFailed底层 I/O 或运行时边界失败保留错误码并检查运行环境
6115kTiCloudStorageErrorCancelledList 请求被取消结束对应等待
6117kTiCloudStorageErrorRangeTooLargeList 查询跨度超过 10 天或合并结果超过 10000 项缩短查询范围后重试
6118kTiCloudStorageErrorNoFrameVideo Output 尚无可用于截图的视频帧等待出画后重试
6119kTiCloudStorageErrorNoRecordableMedia所选范围或通道没有可写入的媒体重新选择录像范围或通道
6120kTiCloudStorageErrorRecordingOverrun本地写入持续跟不上回放数据,保存任务已经终止释放设备资源、检查存储性能后重新保存
6122kTiCloudStorageErrorRecordingUnreadable录像文件已经下载,但 SDK 无法解密或解析出有效的音视频帧,当前任务无法继续不要持续重试同一文件;AAC 录像应确认每次写入都保留完整 ADTS 头,采样率和声道数与实际音频及 flags 一致,ADTS 声明的帧长度与本次写入字节数一致
6123kTiCloudStorageErrorUnavailableSDK 无法完成录像查询或取得下载信息,通常是网络中断、Ti 云存服务暂时不可用或服务响应异常稍后重新查询;持续失败时上报日志
6124kTiCloudStorageErrorStopped活动 ExportTask 已被 stop() 停止,或 cancel() 的取消请求先于自然完成生效结束导出进度,不使用输出文件
6130—解码型 Output 持续收到同类媒体,但 5 秒内没有匹配绑定的 Channel ID核对设备上传与客户端绑定的 Channel ID,修正后重新播放
6134kTiCloudStorageErrorRecordingNotFound云端明确返回目标录像文件不存在;查询结果可能已过期,或文件已被删除刷新录像列表后重新选择,不持续重试同一文件
6135kTiCloudStorageErrorRecordingDownloadFailedSDK 未能成功下载目标录像文件;常见原因包括请求签名失败、连接超时、HTTP 错误、响应不完整或有限重试耗尽稍后重试一次;持续失败时上报日志
6136kTiCloudStorageErrorNetworkUnavailable当前本地网络不可用等待网络恢复后重新发起操作
6137kTiCloudStorageErrorEndpointDnsResolutionFailed配置的 Ti 云存服务域名无法解析检查 endpoint、DNS 和当前网络;默认地址持续失败时上报日志

有公开常量时优先使用常量判断错误类型;没有命名常量时直接比较返回的整数错误码。TiCloudStorage.errorToString(code) 和错误消息只用于展示或诊断。

Ti 云存开发文档