Flutter API 说明
Flutter 通过 tirtc_flutter 查询、回放、截图和导出云录像。
安装和初始化见 Flutter SDK 接入。查询、播放、下载和截图见播放录像。
API 和公开错误常量都从这个 package 入口导出。需要业务分支处理的错误使用常量判断;完整数值见错误码。TiStore.errorToString(code) 只用于展示或诊断。
import 'package:tirtc_flutter/tirtc_flutter.dart';最小回放示例
这段代码从查询录像开始,创建 Replay 并输出音视频。回放期间保持 store、replay、videoOutput 和 audioOutput 有效:
final initCode = await TiStore.init(appId: appId);
if (initCode != kTiStoreErrorOk) return;
final store = TiStore(token: token);
final recordingsResp = await store.listRecordings(
startTimeMs: startTimeMs,
endTimeMs: endTimeMs,
);
if (!recordingsResp.success) {
return;
}
final recordings = recordingsResp.data!;
if (recordings.isEmpty) return;
final recording = recordings.first;
final replay = store.createReplay();
final videoOutput = TiStoreVideoOutput();
final audioOutput = TiStoreAudioOutput();
replay.onError = showReplayError;
replay.onCompleted = showReplayCompleted;
videoOutput.attach(
replay: replay,
channelId: videoChannelId,
);
audioOutput.attach(
replay: replay,
channelId: audioChannelId,
);
replay.play(
startTimeMs: recording.startTimeMs,
endTimeMs: recording.endTimeMs,
);attach 或 play 返回非 kTiStoreErrorOk 时,当前操作没有生效。回放过程中出现 Token、网络或录像读取错误时,replay.onError 返回对应错误码;请求范围自然播放结束时,replay.onCompleted 通知一次。Output 状态用于显示各路媒体的加载、播放、暂停和排空状态。
在播放页的 build 方法中使用 AspectRatio(child: videoOutput.view()) 显示画面。页面退出时先 stop Replay,再依次 detach、dispose 两个 Output 和 Replay;不再访问这台设备时释放 store。
基础类型
Resp<T>
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!。SDK 运行错误通过返回值或 Resp<T> 报告,不抛 Dart exception。
TiStoreRecordingRange
final class TiStoreRecordingRange {
final int startTimeMs;
final int endTimeMs;
}表示一段存在云录像的可用时间。
| 字段 | 说明 |
|---|---|
startTimeMs | 开始时间,UTC Unix 毫秒 |
endTimeMs | 结束时间,UTC Unix 毫秒;必须晚于 startTimeMs |
TiStoreRecordingDay
final class TiStoreRecordingDay {
final String date;
final bool hasRecording;
}| 字段 | 说明 |
|---|---|
date | 严格的 YYYY-MM-DD 日期 |
hasRecording | 该自然日至少有一个当前可见的录像时点时为 true |
本地媒体文件
final class TiStoreGalleryAsset {
final Uri uri;
}
final class TiStoreRecordingFile {
final String path;
final Duration duration;
Future<Resp<TiStoreGalleryAsset>> moveToGallery({String? fileName});
Future<int> delete();
}
final class TiStoreSnapshotFile {
final String path;
Future<Resp<TiStoreGalleryAsset>> moveToGallery({String? fileName});
Future<int> delete();
}path 在成功返回时指向 SDK 私有 cache。TiStoreRecordingFile.duration 是 MP4 的实际媒体时长,不是 Task 从开始到结束经过的墙钟时间。需要长期保存时及时调用 moveToGallery() 或自行复制、移动;直接使用完成后调用 delete()。
moveToGallery({fileName}) 可以为 MP4 或 JPEG 指定保存名称。省略名称时,SDK 使用 Unix 毫秒时间戳生成名称;省略扩展名时,SDK 按文件类型补上 .mp4 或 .jpg。如果名称包含扩展名,只接受对应类型。名称主体按 UTF-8 计为 1~200 字节,不能为空、.、..,也不能包含 /、\ 或空字符;非法名称返回 6000,不创建资产。
移动端写入系统媒体库,Windows 和 macOS 写入 Downloads 根目录。应用应在调用前准备所需权限,SDK 不主动申请图库权限。成功后返回不透明媒体资产 URI,并删除 cache 源文件;失败时保留源文件,可以换名重试。delete() 只清理 SDK 生成的临时 MP4/JPEG,且可以重复调用。移动与删除互斥;并发移动只有规范化后的同名请求会合并,不同名称返回 in-use。成功后 path 只保留历史值。
TiStoreReplaySpeed
enum TiStoreReplaySpeed {
x1,
x2,
x4,
x8,
}| 枚举值 | 说明 |
|---|---|
x1 | 1 倍速,播放音频和视频 |
x2 | 2 倍速,视频播放,音频静音 |
x4 | 4 倍速,视频播放,音频静音 |
x8 | 8 倍速,视频播放,音频静音 |
TiStore
final class TiStore {
TiStore({required String token});
static Future<int> init({
required String appId,
String endpoint = '',
bool consoleLogEnabled = false,
});
static String errorToString(int code);
void Function()? onTokenExpired;
int updateToken(String token);
Future<Resp<List<TiStoreRecordingDay>>> listRecordingDays({
required String startDate,
required String endDate,
String timeZoneId = 'Asia/Shanghai',
});
Future<Resp<List<TiStoreRecordingRange>>> listRecordings({
required int startTimeMs,
required int endTimeMs,
});
TiStoreReplay createReplay();
Resp<TiStoreExportTask> exportRecording({
required int startTimeMs,
required int endTimeMs,
required int videoChannelId,
int? audioChannelId,
void Function(double progress)? onProgress,
});
int dispose();
}appId 必须非空,使用申请开通下发的 AppId,并与签发 Token 时使用的值一致。consoleLogEnabled 默认关闭。
使用任何云存储 API 前先调用 TiStore.init。相同配置重复初始化会成功;改用不同配置则返回 already-initialized。初始化后用一台设备的非空 app_access_token 创建 TiStore 实例;SDK 把 Token 作为不透明字符串使用,创建实例时不发起网络请求。查询、Replay 和导出都访问这个 Token 所绑定的设备;同时访问多台设备时,为每台设备创建独立实例。Flutter package 自行管理日志和缓存目录。
| API | 说明 |
|---|---|
init(appId, endpoint, consoleLogEnabled) | 初始化。appId 来自申请开通;endpoint 留空时使用默认服务地址;consoleLogEnabled 默认为 false。Future 返回初始化错误码 |
errorToString(code) | 返回错误码对应的稳定名称,用于日志和诊断 |
updateToken(token) | 为同一设备更新 Token;只影响之后启动的 List、Play 和 Export |
listRecordingDays(startDate, endDate, timeZoneId) | 查询包含式日期范围,单次最多 31 天。时区使用 IANA ID,默认 Asia/Shanghai;Future 返回完整的逐日升序结果 |
listRecordings(startTimeMs, endTimeMs) | 查询当前设备在 [startTimeMs, endTimeMs) 内的录像可用时间段。Future 返回 Resp<List<TiStoreRecordingRange>> |
createReplay() | 创建属于当前设备的 Replay |
exportRecording(startTimeMs, endTimeMs, videoChannelId, audioChannelId, onProgress) | 开始一次录像导出。视频 Channel 必填,音频 Channel 和进度回调可选。同步返回 Resp<TiStoreExportTask> |
dispose() | 释放当前设备实例;仍有查询、Replay、ExportTask 或回调正在使用时返回 in-use |
onTokenExpired 在当前 Token 被云端明确判定为过期后通知,供应用统一刷新凭据。触发过期的 List、Play 或 Export 会先通过自己的结果报告错误;无效 Token 和权限不足不触发该回调。同一个 Token 至多通知一次,晚设置回调不补发历史事件。
updateToken 的非空新值在返回前被 SDK 保存,之后启动的操作使用这个 Token。传入相同 Token 是幂等操作,不会清除已经确定的过期状态。当前 Token 已经确定过期时,新 List、Play 和 Export 直接返回共享错误 token-expired,不创建操作。updateToken 只用于同一设备续签,访问另一台设备时创建新的 TiStore。
listRecordingDays
Future<Resp<List<TiStoreRecordingDay>>> listRecordingDays({
required String startDate,
required String endDate,
String timeZoneId = 'Asia/Shanghai',
});startDate 和 endDate 使用严格的 YYYY-MM-DD 格式,起止日期均包含。timeZoneId 使用 IANA 时区 ID;不要传 UTC+8 或 Local。单次最多查询 31 天,超过上限时返回 range-too-large。
成功结果按日期升序返回范围内每一天,包括 hasRecording == false 的日期。hasRecording == true 表示该自然日至少有一个当前可见的录像时点;要取得可回放时间段,再调用 listRecordings。
listRecordings
Future<Resp<List<TiStoreRecordingRange>>> listRecordings({
required int startTimeMs,
required int endTimeMs,
});查询设备在指定 UTC 时间范围内的云录像。startTimeMs 和 endTimeMs 使用 UTC Unix 毫秒,SDK 不根据本地时区换算;startTimeMs 必须早于 endTimeMs。单次查询范围最长为 10 天,最多返回 10000 段;没有录像时,成功结果的 data 为空列表。
结果按 startTimeMs 升序排列并裁剪到 [startTimeMs, endTimeMs)。重叠或首尾相接的时间段会合并,真实空洞会保留;超过范围或结果数量上限时,本次查询整体失败,不返回部分结果。
exportRecording
Resp<TiStoreExportTask> exportRecording({
required int startTimeMs,
required int endTimeMs,
required int videoChannelId,
int? audioChannelId,
void Function(double progress)? onProgress,
});同步检查参数和本地资源。成功时 data 是已经开始运行的独立 ExportTask;失败时不创建 Task,也不留下临时文件。Runtime 在初始化时确定的私有 cache 中为每个 Task 生成唯一 MP4,调用方不传输出路径。
需要进度通知时传入 onProgress。SDK 在启动 Task 前保存回调,并且不会在 exportRecording 返回前调用它。
startTimeMs和endTimeMs使用 UTC Unix 毫秒,范围为[startTimeMs, endTimeMs),并且startTimeMs必须早于endTimeMs;videoChannelId必填,audioChannelId可空,值必须位于0..255;两者可以相同;- 录像中没有媒体的时间会被跳过,不写黑帧或静音;
- 多次调用彼此独立并生成不同文件;超过并发资源上限返回
resource-exhausted。
输入格式、转码和关键帧规则见播放录像。
TiStoreReplay
final class TiStoreReplay {
TiStoreReplay._();
TiStoreReplaySpeed get speed;
int? get currentTimeMs;
void Function(int timeMs)? onTimeChanged;
void Function()? onCompleted;
void Function(int code)? onError;
int play({
required int startTimeMs,
required int endTimeMs,
int? initialTimeMs,
});
int pause();
int resume();
int seek(int timeMs);
int setSpeed(TiStoreReplaySpeed speed);
Resp<TiStoreRecordingTask> startRecording({
required int videoChannelId,
int? audioChannelId,
});
int stop();
int dispose();
}Replay 只能由 store.createReplay() 创建,并固定属于该设备实例。它提供播放、暂停、恢复、Seek、倍速和停止操作。对应 Output 返回音频和视频的缓冲、输出、暂停、完成或失败状态。Token、网络或录像读取错误通过 Replay 返回。
属性和时间回调
speed:当前速度,初始值为TiStoreReplaySpeed.x1;currentTimeMs:当前已知的绝对 UTC 回放时间;尚未开始、尚未获得首个时间位置或已经停止时为null。暂停时保留最后位置,自然完成时为请求的endTimeMs,回放失败时保留最后已知位置;onTimeChanged:回放时间变化通知。中间通知可以合并,应用不能用通知次数计算时长。onCompleted:当前play请求的时间范围自然耗尽时恰好调用一次。主动stop、被新的play替换、最后一个消费者解除绑定或回放失败都不会触发;它表示 Replay 已经自然结束,不表示各 Output 已经排空;onError:返回当前play过程中发生的 Token、权限、网络或录像读取错误。错误发生时恰好调用一次;同步拒绝、主动stop、被新play替换或自然完成不会触发。
晚设置回调不会重放此前事件。开始 play 前设置 onCompleted 和 onError;需要立即显示当前位置时,先读取 currentTimeMs,再设置 onTimeChanged。范围自然耗尽时,Replay 会自动终结活动 RecordingTask;应用稍后调用 Task 的 stop() 取得缓存结果。
play
int play({
required int startTimeMs,
required int endTimeMs,
int? initialTimeMs,
});开始回放所属设备的指定 UTC 时间范围。initialTimeMs 省略时从 startTimeMs 开始;传入时从指定位置开始。Replay 必须已经绑定至少一个 Audio/Video Output。方法接受请求时固定使用实例当前的 Token;之后调用 updateToken 不改变本次 Play。
返回 kTiStoreErrorOk 表示请求已经接受,不表示媒体已经开始输出。通过 Output 状态更新播放界面;Token、网络或录像读取失败时,在 Replay onError 中处理。
startTimeMs 必须早于 endTimeMs,initialTimeMs 必须位于 [startTimeMs, endTimeMs)。参数无效时同步返回 invalid-argument,当前回放不受影响。请求被接受后直接从初始位置建立回放,不会先输出范围开头的媒体。初始位置落在录像空洞中时,从其后的第一个可播放位置开始;直到范围末尾都没有录像时,按自然结束处理。
同一个 Replay 可以再次调用 play:
- 新请求被接受后,前一个回放停止;
- 已绑定的 Output、Audio Output 音量和当前速度保留;
- 新回放从非暂停状态开始;
- 新请求被同步拒绝时,原来的回放不受影响。
pause / resume
int pause();
int resume();控制当前 Replay 的暂停与继续。
- 暂停成功后,已绑定 Output 进入
paused; - 恢复后,Output 根据当前数据进入
buffering、playing或rendering; - 已经暂停时再次
pause、已经继续时再次resume,都会成功且不产生额外任务。 - 尚未
play、已经stop、播放完成或回放失败时返回not-started。
seek
int seek(int timeMs);通知 Replay 跳转到当前回放范围内的绝对 UTC 时间。返回 kTiStoreErrorOk 表示定位命令已经接受,不表示媒体已经到达目标位置。
timeMs必须位于当前[startTimeMs, endTimeMs);越界时同步返回invalid-argument,当前回放不受影响;- Seek 保留当前速度和暂停意图,连续调用采用 latest-wins;
- 定位生效后,
currentTimeMs和onTimeChanged更新为第一个不早于timeMs的可播放位置; - 目标合法但其后直到范围末尾都没有录像时,位置推进到
endTimeMs,并按自然结束触发onCompleted。
Seek 只能在当前 play 仍在运行或暂停时调用。尚未 play、已经 stop、自然完成或回放失败时返回 not-started。自然完成后要从指定位置重新播放时,调用带 initialTimeMs 的 play。
setSpeed
int setSpeed(TiStoreReplaySpeed speed);设置当前和后续 play 使用的速度。设置相同速度是幂等操作。
startRecording
Resp<TiStoreRecordingTask> startRecording({
required int videoChannelId,
int? audioChannelId,
});在当前回放中开始保存一段 MP4。Replay 必须正在运行。videoChannelId 必填,audioChannelId 可空;两者都必须位于 0..255。
成功只表示 Task 已经建立并开始接收媒体。Replay 未活动或资源达到上限时,同步返回失败且 data 为 null。同一个 Channel 可以启动多个 Task,SDK 为每个 Task 生成不同文件。两个视频 Channel 使用两个 Task。
stop
int stop();停止当前回放并清除 currentTimeMs。已绑定 Output 回到 idle。尚未开始、已经完成或已经失败时调用 stop 也会成功。
stop 不产生 completed,并且保留 Output 绑定和速度。之后再次调用 play 不需要重新 attach。
dispose
int dispose();永久释放 Replay。调用前必须先 stop,结束全部 RecordingTask,并显式 detach 全部 Output;否则返回 in-use,对象保持有效,可完成清理后重试。成功后不再投递 Replay 回调,且对象不能再次使用。
Audio/Video Output
云录像任务使用以下 Output 成员。
final class TiStoreAudioOutput {
TiStoreAudioOutput();
TiStoreAudioOutputState get state;
void Function(TiStoreAudioOutputState state)? onStateChanged;
void Function(int code)? onError;
int attach({required TiStoreReplay replay, required int channelId});
int setVolume(int volumePercent);
int detach();
int dispose();
}
final class TiStoreVideoOutput {
TiStoreVideoOutput();
TiStoreVideoOutputState get state;
Size? get renderSize;
void Function(TiStoreVideoOutputState state)? onStateChanged;
void Function(Size size)? onRenderSizeChanged;
void Function(int code)? onError;
int attach({required TiStoreReplay replay, required int channelId});
Future<Resp<TiStoreSnapshotFile>> takeSnapshot();
int detach();
Widget view();
int dispose();
}属性与回调
| 成员 | 适用对象 | 说明 |
|---|---|---|
state | Audio、Video | 当前 Output 状态 |
renderSize | Video | 最近一次视频画面尺寸;尚未出画时为 null |
onStateChanged | Audio、Video | Output 状态变化通知 |
onRenderSizeChanged | Video | 视频画面尺寸变化通知 |
onError | Audio、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 返回 TiStoreSnapshotFile |
detach() | Audio、Video | 解除当前 Replay 绑定;对象仍可再次 attach |
view() | Video | 返回该 Output 的渲染 Widget;同一个 Output 同时只能挂载一个 view() |
dispose() | Audio、Video | 释放已解除绑定的 Output;返回错误码,成功后不能继续使用该实例 |
Output 状态
enum TiStoreAudioOutputState {
idle,
buffering,
playing,
failed,
paused,
completed,
}
enum TiStoreVideoOutputState {
idle,
buffering,
rendering,
failed,
paused,
completed,
}| 状态 | Replay 场景中的含义 |
|---|---|
idle | 没有活动回放、已经 stop 或已经解除绑定 |
buffering | 回放活动,但当前没有足够媒体 |
playing / rendering | 正在输出音频或视频 |
paused | Replay 已暂停 |
completed | 目标时间范围结束,当前 Output 已排空 |
failed | 当前 Output 已无法继续;可能是回放失败,也可能是该 Output 自己失败 |
completed 只表示范围耗尽,不证明这个 Output 曾经输出媒体。所选 channel_id 在范围内没有匹配帧时也会完成;需要区分时,记录本次是否曾进入 playing 或 rendering。
某个 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
Future<Resp<TiStoreSnapshotFile>> takeSnapshot();保存 Video Output 当前仍可取得的最近画面。SDK 在可写缓存目录生成唯一的 JPEG。
- 成功时
data.path是 SDK 私有 cache 中的临时 JPEG,失败时code给出错误; - 需要长期保存时调用返回文件对象的
moveToGallery(),直接使用后调用delete(); - 当前绑定还没有画面时返回
no-frame;暂停或自然播放完成后,只要最后画面仍然保留,就可以继续截图; - attach 新来源、新的
play生效、detach或移除渲染 View 后,在新画面到达前返回no-frame; - Snapshot 不捕获 Flutter Widget、字幕、按钮或其他页面叠加内容;
- 同一个 Video Output 同时只接受一个 Snapshot;已有请求未完成时,新的 Future 以
in-use完成。不同 Output 的请求仍可能因全局资源上限以resource-exhausted完成; - Snapshot Future 完成前,
dispose()返回in-use,对象与原有绑定保持不变。先等待 Future,再按正常顺序解除绑定并释放 Output。
TiStoreRecordingTask
final class TiStoreRecordingTask {
TiStoreRecordingTask._();
Future<Resp<TiStoreRecordingFile>> stop();
}RecordingTask 只能由活动 Replay 的 startRecording 创建。Replay、视频 Channel 和可选音频 Channel 在创建时一次确定;输出路径由 SDK 在私有 cache 中生成。
stop() 停止接收新媒体,排空已经接收的数据,完成 MP4 并返回文件结果。第一次调用开始终结;重复调用返回同一个 Future 和同一份结果。
- 成功时
data.path是临时 MP4 路径,data.duration是文件实际媒体时长; - 还没有形成可写媒体时返回
no-recordable-media; - codec、磁盘、媒体处理或来源失败时返回对应错误,且不保留半成品;
- Replay 自然结束时 Task 自动完成;随后调用
stop()取得已经缓存的结果; - 停止一个 RecordingTask 不影响 Replay 和其他 Task。
Task 没有 state、实时 duration、cancel、result 属性或 dispose。页面显示录制秒数时,由应用从开始动作自行计时;最终准确时长以 TiStoreRecordingFile.duration 为准。
TiStoreExportTask
final class TiStoreExportTask {
TiStoreExportTask._();
double get progress;
Future<Resp<TiStoreRecordingFile>> get result;
Future<Resp<TiStoreRecordingFile>> stop();
}TiStoreExportTask 只能由设备实例的 exportRecording 创建。它与 Replay 和 Output 相互独立,并在启动时固定使用实例当前的 Token。
| 成员 | 说明 |
|---|---|
progress | 当前导出进度,取值为 0.0..1.0 |
result | 完整处理指定范围后的最终文件;每次读取返回同一个 Future |
stop() | 停止活动导出并等待清理;返回同一个最终结果 Future |
progress
- 进度范围是
0.0..1.0,并且不会回退; - 跨越录像空洞时,进度可能直接跃迁;
- 只有文件完整写入并关闭后,进度才会到达
1.0; exportRecording传入的进度通知可以合并,progress始终返回当前快照。
Export result
Future<Resp<TiStoreRecordingFile>> get result;同一个 Task 始终返回同一个 Future,并且只完成一次。
- 文件完整写入并关闭后才返回成功;
- 成功结果包含临时文件
path和实际媒体duration; - 失败或主动停止会删除临时文件;
- result 完成后自动释放任务使用的底层资源,不提供
dispose。
stop
Future<Resp<TiStoreRecordingFile>> stop();活动任务调用 stop() 会停止读取并删除未完成文件,以 stopped 返回;它不会把已处理部分当作成功文件。任务已经自然完成或失败时,stop() 返回缓存的同一结果。重复调用返回同一个 Future。
生命周期与回调
并发规则
- 不同 RecordingTask、ExportTask 和不同 Video Output 的 Snapshot 是独立操作,可以同时运行;
- 每个 Task 生成一个 MP4,同一个 Video Output 同时只接受一个 Snapshot;
- 相同 Channel 可以创建多个 Task,SDK 为每个 Task 生成不同文件;两个视频 Channel 使用两个 Task;
- SDK 对并发网络、媒体处理和文件任务设置全局资源上限;超过上限的请求返回
resource-exhausted,不会建立无界队列;
回调与关闭
TiStore实例绑定一台设备并提供查询、Replay 和导出;Output、RecordingTask 和 ExportTask 是具体资源;- 回调和 Future completion 返回创建对象的 Dart isolate,通常就是应用的 UI isolate;
- 状态、时间和进度回调表示当前事实,中间变化可以合并;
- Replay 和 Output 的状态或时间回调晚设置时不会重放此前事件,需要立即显示时先读取对象当前属性;Export 的进度回调在
exportRecording中注册; - 最终
result不会被合并或丢失; - 对象
dispose或 Task 完成后,不再投递对应状态事件;已经接受的一次性 Future 仍会恰好完成; - Store 和 Output 构造器只创建对应对象,不抛 SDK 运行错误;
createReplay同样只创建 Replay。第一次需要底层资源的方法返回创建错误; - 页面退出时先结束活动 Task 并等待已接受的 Snapshot,再停止 Replay、释放 Output 和 Replay;
- 不再访问对应设备时释放 Store 实例。
错误码
需要业务分支处理的错误在 package 根入口提供公开常量。方法仍返回完整整数错误码;业务判断使用常量,不解析错误消息。
| 错误码 | Flutter 常量 | 常见含义 | 建议处理 |
|---|---|---|---|
| 0 | kTiStoreErrorOk | 操作成功 | 继续后续流程 |
| 6000 | kTiStoreErrorInvalidArgument | 参数缺失、取值越界或时间范围无效 | 修正参数后重试 |
| 6001 | kTiStoreErrorNotInitialized | 尚未初始化云存储能力,或已经关闭 | 先调用 TiStore.init |
| 6014 | kTiStoreErrorTokenExpired | Token 已过期 | 为同一设备更新 Token 后重新发起请求 |
| 6022 | kTiStoreErrorAlreadyInitialized | 已使用另一组配置初始化 | 保持初始化参数一致;需要更换配置时,重启应用后再初始化 |
| 6024 | kTiStoreErrorPermissionDenied | Token 无效、无权访问目标录像,或应用尚未取得系统相册写入权限 | 检查对应授权;云端访问失败时重新取得 Token |
| 6026 | kTiStoreErrorInUse | 对象仍在播放、录制或执行任务,当前操作不能执行 | 先结束活动操作,再重试 |
| 6027 | kTiStoreErrorNotStarted | 尚未开始播放或录制 | 先启动对应操作 |
| 6029 | kTiStoreErrorNotBound | 对象尚未绑定 Replay | 完成 attach 后重试 |
| 6030 | kTiStoreErrorNotConfigured | 尚未选择媒体通道或完成必要配置 | 补充通道或配置后重试 |
| 6043 | kTiStoreErrorResourceExhausted | 当前设备没有足够资源创建或继续任务 | 结束其他任务并释放资源后重试 |
| 6046 | kTiStoreErrorFileWriteFailed | 无法创建或写入输出文件 | 检查目录权限和磁盘空间 |
| 6113 | kTiStoreErrorUnsupportedFormat | 录像的音视频格式不受支持 | 提示当前录像无法播放或导出 |
| 6115 | kTiStoreErrorCancelled | List 请求被取消 | 结束对应等待 |
| 6117 | kTiStoreErrorRangeTooLarge | 日期查询超过 31 天,或时间段查询超过 10 天或合并结果超过 10000 项 | 缩短查询范围后重试 |
| 6118 | kTiStoreErrorNoFrame | Video Output 尚无可用于截图的视频帧 | 等待出画后重试 |
| 6119 | kTiStoreErrorNoRecordableMedia | 所选范围或通道没有可写入的媒体 | 重新选择录像范围或通道 |
| 6120 | kTiStoreErrorRecordingOverrun | 本地写入持续跟不上回放数据,保存任务已经终止 | 释放设备资源、检查存储性能后重新保存 |
| 6122 | kTiStoreErrorRecordingUnreadable | 录像文件缺失、损坏,或上传的格式与帧数据无法读取 | 文件缺失或损坏时停止重试;其他情况检查设备上传的格式与帧要求 |
| 6123 | kTiStoreErrorUnavailable | 网络、云存储服务或录像数据读取暂时不可用 | 稍后重试 |
| 6124 | kTiStoreErrorStopped | 活动 ExportTask 已被主动停止 | 结束导出进度,不使用输出文件 |
使用错误常量判断错误类型。TiStore.errorToString(code) 和错误消息只用于展示或诊断。