React Native API 说明
React Native 通过 tirtc-react-native 查询、回放、截图和导出云录像。
安装和初始化见 React Native SDK 接入。查询、播放、下载和截图见播放录像。
同步命令返回 number 错误码,TISTORE_ERROR_OK 表示当前调用成功。异步方法通过 Promise 返回唯一的最终结果。需要业务分支处理的错误通过包根入口提供公开常量;完整含义和处理建议见错误码。TiStore.errorToString(code) 只用于展示或诊断。
基础类型
import type { ReactElement } from 'react';
import type { ViewProps } from 'react-native';
export type Resp<T> = Readonly<{
success: boolean;
code: number | null;
message: string | null;
data: T | null;
}>;
export type TiStoreRecordingRange = Readonly<{
startTimeMs: number;
endTimeMs: number;
}>;
export type TiStoreRecordingRangesResult = Readonly<{
code: number;
recordings: ReadonlyArray<TiStoreRecordingRange>;
}>;
export type TiStoreRecordingDay = Readonly<{
date: string;
hasRecording: boolean;
}>;
export type TiStoreRecordingDaysResult = Readonly<{
code: number;
days: ReadonlyArray<TiStoreRecordingDay>;
}>;
export const TiStoreReplaySpeed = {
x1: 'x1',
x2: 'x2',
x4: 'x4',
x8: 'x8',
} as const;
export type TiStoreReplaySpeed =
(typeof TiStoreReplaySpeed)[keyof typeof TiStoreReplaySpeed];| 类型或字段 | 说明 |
|---|---|
TiStoreRecordingRange.startTimeMs | 录像可用时间段的开始时间,UTC Unix 毫秒 |
TiStoreRecordingRange.endTimeMs | 录像可用时间段的结束时间,UTC Unix 毫秒;必须晚于 startTimeMs |
TiStoreRecordingRangesResult.code | 查询最终错误码 |
TiStoreRecordingRangesResult.recordings | 查询得到的录像可用时间段;成功但没有录像时为空数组 |
TiStoreRecordingDay.date | 严格的 YYYY-MM-DD 日期 |
TiStoreRecordingDay.hasRecording | 该自然日至少有一个当前可见的录像时点时为 true |
TiStoreRecordingDaysResult | 日期查询的最终错误码和完整逐日结果 |
| 倍速 | 说明 |
|---|---|
x1 | 1 倍速,播放音频和视频 |
x2 | 2 倍速,视频播放,音频静音 |
x4 | 4 倍速,视频播放,音频静音 |
x8 | 8 倍速,视频播放,音频静音 |
所有时间必须是 JavaScript 安全整数;SDK 不进行本地时区换算。
最小回放示例
selectedRecording 是从 listRecordings 返回结果中选择的一段录像。回放期间保持 replay、videoOutput 和 audioOutput 有效。
const initCode = await TiStore.init({appId});
if (initCode !== TISTORE_ERROR_OK) return;
const store = new TiStore(token);
const replay = store.createReplay();
const videoOutput = new TiStoreVideoOutput();
const audioOutput = new TiStoreAudioOutput();
replay.onError = showReplayError;
replay.onCompleted = showReplayCompleted;
videoOutput.attach(replay, videoChannelId);
audioOutput.attach(replay, audioChannelId);
replay.play(
selectedRecording.startTimeMs,
selectedRecording.endTimeMs,
);
function StorePlaybackVideo(): ReactElement {
return videoOutput.view({style: styles.video});
}同步方法返回非 TISTORE_ERROR_OK 时,当前操作没有生效。通过两个 Output 的状态更新播放界面;Token、网络或录像读取失败时,在 Replay 的错误回调中处理。
页面退出时,先停止 Replay、detach 两个 Output,并从组件树卸载 Video Output 的 Fabric View。再依次 dispose Output 和 Replay。不再访问这台设备时,dispose store。
TiStore
export class TiStore {
constructor(token: string);
static init(options: Readonly<{
appId: string;
endpoint?: string;
consoleLogEnabled?: boolean;
}>): Promise<number>;
static errorToString(code: number): string;
onTokenExpired: (() => void) | null;
updateToken(token: string): number;
listRecordingDays(
startDate: string,
endDate: string,
timeZoneId?: string,
): Promise<TiStoreRecordingDaysResult>;
listRecordings(
startTimeMs: number,
endTimeMs: number,
): Promise<TiStoreRecordingRangesResult>;
createReplay(): TiStoreReplay;
exportRecording(
options: TiStoreExportOptions,
onProgress?: (progress: number) => void,
): Resp<TiStoreExportTask>;
dispose(): number;
}Promise resolve 统一结果码,不以 reject 表示 SDK 运行错误。JavaScript 参数类型错误或 React Native 模块不可用时,Promise 仍可能 reject。
使用任何云存储 API 前先调用 TiStore.init。初始化后用一台设备的非空 Token 创建 TiStore 实例;构造不发起网络请求。同时访问多台设备时创建多个实例。
listRecordingDays 的日期范围起止均包含,单次最多 31 天。日期使用严格的 YYYY-MM-DD 格式,时区使用 IANA ID,省略时默认为 Asia/Shanghai。成功结果按日期升序返回范围内每一天,包括 hasRecording == false 的日期。
listRecordings 单次查询范围最长为 10 天;没有录像时,recordings 为空列表。
List 结果按 startTimeMs 升序排列并裁剪在请求的 [startTimeMs, endTimeMs) 内;重叠或首尾相接的时间段会合并,任何大于 0 的空洞都保留为不同项。结果是设备级完整快照,不保证每个 Channel 都有媒体,也不分页。合并后最多返回 10000 项;查询跨度或结果数量超限时整体 resolve range-too-large,不返回部分结果;成功但没有录像时返回空数组。
exportRecording 同步返回 Task 接受结果。创建成功不表示文件已经生成;通过 TiStoreExportTask.result 等待任务自然完成。
| API | 说明 |
|---|---|
init(options) | 初始化。appId 来自申请开通;endpoint 仅用于自定义服务地址;consoleLogEnabled 默认为 false。Promise 返回初始化错误码 |
errorToString(code) | 返回错误码对应的稳定名称,用于日志和诊断 |
updateToken(token) | 为同一设备更新 Token,只影响之后启动的 List、Play 和 Export |
listRecordingDays(startDate, endDate, timeZoneId) | 查询哪些自然日有录像;时区默认 Asia/Shanghai,Promise 返回完整的逐日升序结果 |
listRecordings(startTimeMs, endTimeMs) | 查询当前设备指定 UTC 时间范围内的录像可用时间段;单次跨度最长 10 天 |
createReplay() | 创建属于当前设备的 Replay |
exportRecording(options, onProgress) | 开始一次录像导出;进度回调可选,同步返回 Resp<TiStoreExportTask> |
dispose() | 释放当前设备实例;仍有查询、Replay、ExportTask 或回调正在执行时返回 in-use |
onTokenExpired 只在当前 Token 被云端明确判定为过期时通知,同一个 Token 至多一次。触发过期的 List、Play 或 Export 先通过自身结果/回调报告错误;Token 无效或权限不足不触发该回调。当前 Token 已经确定过期时,新操作直接返回 token-expired。updateToken 保存不同的非空 Token,之后启动的操作使用新 Token;相同 Token 不清除过期状态,已经开始的操作也不自动换证或重试。它只用于同一设备续签,访问另一台设备时创建新的 TiStore。
TiStoreReplay
export class TiStoreReplay {
readonly speed: TiStoreReplaySpeed;
readonly currentTimeMs: number | null;
onTimeChanged: ((timeMs: number) => void) | null;
onError: ((code: number) => void) | null;
onCompleted: (() => void) | null;
private constructor();
play(
startTimeMs: number,
endTimeMs: number,
initialTimeMs?: number,
): number;
pause(): number;
resume(): number;
seek(timeMs: number): number;
setSpeed(speed: TiStoreReplaySpeed): number;
startRecording(
options: TiStoreStartRecordingOptions,
): Resp<TiStoreRecordingTask>;
stop(): number;
dispose(): number;
}| 成员 | 说明 |
|---|---|
speed | 当前回放倍速,初始值为 x1 |
currentTimeMs | 当前回放位置,UTC Unix 毫秒;首次取得位置前和 stop 后为 null |
onTimeChanged | 回放位置变化通知;晚设置时不会补发之前的事件 |
onError | 当前 play 的 Token、权限、网络或录像读取错误;一次回放最多通知一次 |
onCompleted | 当前 play 的时间范围自然耗尽通知;一次回放最多通知一次 |
play(startTimeMs, endTimeMs, initialTimeMs) | 播放所属设备的指定 UTC 时间范围;initialTimeMs 可选,省略时从范围开头播放 |
pause() | 暂停当前回放;重复调用成功 |
resume() | 继续当前回放;重复调用成功 |
seek(timeMs) | 定位到当前回放范围内的 UTC 时间;同步返回操作是否被接受 |
setSpeed(speed) | 设置 x1、x2、x4 或 x8 倍速;非 x1 时 Audio Output 静音 |
startRecording(options) | 保存当前回放的一段内容;同步返回 Resp<TiStoreRecordingTask> |
stop() | 停止当前回放并清除 currentTimeMs;保留 Output 绑定和倍速 |
dispose() | 释放 Replay;调用后不能继续使用该实例 |
Replay 只能由 store.createReplay() 创建,并固定属于该设备实例。Replay 不公开 state。onError 返回当前 play 过程中发生的 Token、权限、网络或录像读取错误。错误发生时恰好调用一次;同步拒绝、主动 stop、被新 play 替换或自然完成不会触发。每个被接受的 play 仅在其时间范围自然耗尽时调用一次 onCompleted;同步拒绝、主动 stop、被新 play 替换或错误终止均不触发。开始 play 前设置回调,晚设置不会重放此前错误或完成事件。接受 Play 时固定使用 Store 实例当前的 Token,之后更新 Token 不改变本次回放。
录像文件已被删除、缺失、损坏或无法读取时,onError 返回 TISTORE_ERROR_RECORDING_UNREADABLE。网络或服务暂时不可用时,返回 TISTORE_ERROR_STORE_UNAVAILABLE。
currentTimeMs在首次取得位置前和stop后为null;暂停时保留最后位置,自然完成时为请求的endTimeMs,回放失败时保留最后已知位置。晚设置onTimeChanged不重放旧事件,先读取属性再订阅。speed初始为x1;非x1时 Audio Output 静音。play前必须至少绑定一个 Output。onCompleted不取代 Output 的completed:前者表示 Replay 来源自然耗尽,后者只表示对应 Output 已排空。自然完成会自动终结活动 RecordingTask。play要求startTimeMs < endTimeMs;传入的initialTimeMs必须位于[startTimeMs, endTimeMs)。参数校验失败同步返回invalid-argument,原回放不受影响。成功接受后从初始位置建立新回放,不会先输出范围开头的媒体;初始位置落在空洞中时从其后的第一段录像开始,其后没有录像时按自然结束处理。- 最后一个 Output 离开且没有活动 RecordingTask 时,Replay 停止读取来源,但不触发
onCompleted。 - 新
play被接受后原回放停止,但保留 Output 绑定、Audio Output 音量和速度;同步拒绝时原回放不受影响。 pause和resume分别幂等;尚未开始、已经stop、自然完成或失败时返回not-started。- Seek 只能在当前
play仍在运行或暂停时调用,目标必须位于当前[startTimeMs, endTimeMs)。回放未活动或目标越界时,分别同步返回not-started或invalid-argument,当前回放不受影响。自然完成后要从指定位置重新播放时,调用带initialTimeMs的play。 - Seek 保留速度和暂停意图,连续调用采用 latest-wins。定位生效后,
currentTimeMs和onTimeChanged更新为第一个不早于目标的可播放位置。目标合法但其后没有录像时,位置推进到endTimeMs并按自然结束触发onCompleted。 stop幂等,不触发onCompleted,也不把 Output 标记为completed;它会清除currentTimeMs,并保留 Output 绑定和速度。
Audio/Video Output
export const TiStoreAudioOutputState = {
idle: 'idle',
buffering: 'buffering',
playing: 'playing',
failed: 'failed',
paused: 'paused',
completed: 'completed',
} as const;
export type TiStoreAudioOutputState =
(typeof TiStoreAudioOutputState)[keyof typeof TiStoreAudioOutputState];
export const TiStoreVideoOutputState = {
idle: 'idle',
buffering: 'buffering',
rendering: 'rendering',
failed: 'failed',
paused: 'paused',
completed: 'completed',
} as const;
export type TiStoreVideoOutputState =
(typeof TiStoreVideoOutputState)[keyof typeof TiStoreVideoOutputState];
export type TiStoreVideoOutputViewProps = ViewProps &
Readonly<{
output: TiStoreVideoOutput;
resizeMode?: 'contain' | 'cover' | 'stretch';
}>;
export class TiStoreAudioOutput {
readonly state: TiStoreAudioOutputState;
onStateChanged: ((state: TiStoreAudioOutputState) => void) | null;
onError: ((code: number) => void) | null;
attach(replay: TiStoreReplay, channelId: number): number;
setVolume(volumePercent: number): number;
detach(): number;
dispose(): number;
}
export class TiStoreVideoOutput {
readonly state: TiStoreVideoOutputState;
onStateChanged: ((state: TiStoreVideoOutputState) => void) | null;
onError: ((code: number) => void) | null;
view(
props?: Omit<TiStoreVideoOutputViewProps, 'output'>,
): ReactElement;
attach(replay: TiStoreReplay, channelId: number): number;
takeSnapshot(): Promise<Resp<TiStoreSnapshotFile>>;
detach(): number;
dispose(): number;
}
export function TiStoreVideoOutputView(
props: TiStoreVideoOutputViewProps,
): ReactElement;setVolume 接收 0..100 的安全整数;0 表示静音。它只调整当前 Audio Output,不修改系统全局音量。新一次 play 会保留该值。
Output 状态
| Audio 状态 | Video 状态 | 说明 |
|---|---|---|
idle | idle | 尚未输出媒体,或已经 detach |
buffering | buffering | 正在等待足够的媒体数据 |
playing | rendering | 正在播放音频或渲染视频 |
paused | paused | Replay 已暂停 |
completed | completed | Replay 来源已经自然耗尽,并且该 Output 已排空 |
failed | failed | 当前 Output 无法继续输出媒体 |
| 成员 | 适用对象 | 说明 |
|---|---|---|
state | Audio、Video | 当前 Output 状态 |
onStateChanged | Audio、Video | Output 状态变化通知 |
onError | Audio、Video | 当前 Output 的解码、播放或渲染错误 |
view(props) / TiStoreVideoOutputView | Video | 创建由该 Output 驱动的 Fabric 视频组件;resizeMode 默认为 contain |
attach(replay, channelId) | Audio、Video | 绑定 Replay 中指定 channel_id 的媒体;channelId 取值为 0..255 |
takeSnapshot() | Video | 把当前视频画面保存为唯一的临时 JPEG;Promise 成功时返回 TiStoreSnapshotFile |
detach() | Audio、Video | 解除当前 Replay 绑定;对象仍可再次 attach |
dispose() | Audio、Video | 释放 Output;调用后不能继续使用该实例 |
view(props) 是 TiStoreVideoOutputView({ output: this, ...props }) 的惯用入口。Fabric 组件的 mount/unmount 只建立和解除宿主渲染面,不改变 Replay 绑定。
Snapshot 保存当前绑定和本次播放中最近一次成功呈现、且仍可读取的画面,不包含字幕、按钮等页面 UI。成功时 data.path 是 SDK 私有 cache 中的临时 JPEG。需要长期保存时调用文件对象的 moveToGallery(),直接使用完成后调用 delete()。当前绑定和本次播放尚未成功出画时返回 no-frame;暂停保留当前画面,自然播放完成后只要最终画面仍在就可以截图。重新 attach、开始新一次 play、detach、卸载 View 或清空画面后,必须等新画面成功呈现才能再次截图。
同一个 Video Output 同时只接受一个 Snapshot;已有请求未完成时,新 Promise resolve 为 in-use。不同 Output 仍可能因全局资源上限 resolve 为 resource-exhausted。Snapshot 完成前,dispose() 返回 in-use,Output 及其绑定关系保持不变。
Video Output 和 Audio Output 分别使用设备配置提供的 channelId,取值范围是 0..255;两者可以相同,也可以不同。处理所有 union 分支时,需要包含 paused 和 completed。
重复绑定同一个 Replay 的同一个 channel_id 会成功;已经绑定时传入不同 Replay 或 channel_id 返回 in-use 并保留原关系,必须先显式 detach。Replay 正在运行时新增或 detach 后重新绑定 Output,从绑定生效后的下一可独立解码位置开始,不补发此前媒体。同一个 Replay 的同一个 channel_id 只能绑定一个同类播放 Output。stop、自然完成和新 play 都保留绑定;dispose 不隐式解绑,关系仍存在时返回 in-use。某个 Output 解码、播放或渲染失败时,它的 onError 返回错误,并且只终止该 Output。Token、网络、所选范围过大或录像读取失败时,Replay 的 onError 返回一次错误,并使本次回放的全部 Output 进入 failed。
本地媒体文件
export type TiStoreGalleryAsset = Readonly<{ uri: string }>;
export interface TiStoreRecordingFile {
readonly path: string;
readonly durationMs: number;
moveToGallery(fileName?: string): Promise<Resp<TiStoreGalleryAsset>>;
delete(): Promise<number>;
}
export interface TiStoreSnapshotFile {
readonly path: string;
moveToGallery(fileName?: string): Promise<Resp<TiStoreGalleryAsset>>;
delete(): Promise<number>;
}两个文件对象的 path 在成功返回时指向 SDK 私有 cache。moveToGallery(fileName?) 可以为 MP4 或 JPEG 指定保存名称。省略名称时,SDK 使用 Unix 毫秒时间戳生成名称;省略扩展名时,SDK 按文件类型补上 .mp4 或 .jpg。如果名称包含扩展名,只接受对应类型。名称主体按 UTF-8 计为 1~200 字节,不能为空、.、..,也不能包含 /、\ 或空字符;非法名称 resolve 为 6000,不创建资产。
SDK 不主动申请图库权限,应用应在调用前完成 Android 或 iOS 授权。保存成功后返回系统媒体库 URI,并删除源文件;失败时保留源文件,可以换名重试。delete() 受限、幂等。移动与删除互斥;并发移动只有规范化后的同名请求会合并,不同名称返回 in-use。第一次移动成功后,后续合法名称都返回已经创建的同一个资产。
TiStoreRecordingTask
export type TiStoreStartRecordingOptions = Readonly<{
videoChannelId: number;
audioChannelId?: number;
}>;
export class TiStoreRecordingTask {
stop(): Promise<Resp<TiStoreRecordingFile>>;
}TiStoreReplay.startRecording(options) 同步返回 Resp<TiStoreRecordingTask>。Replay 必须正在回放;视频 Channel 必填,音频 Channel 可空。同步失败不创建 Task。
stop() 停止接收新媒体,排空已接收数据并完成 MP4。第一次调用开始终结;重复调用返回同一个 Promise。Replay 自然结束时 Task 自动完成,随后调用 stop() 返回缓存结果。
Task 不公开 state、实时 duration、cancel 或 dispose。相同 Channel 可以创建多个 Task,SDK 为每个 Task 生成不同文件;两个视频 Channel 使用两个 Task。
TiStoreExportTask
export type TiStoreExportOptions = Readonly<{
startTimeMs: number;
endTimeMs: number;
videoChannelId: number;
audioChannelId?: number;
}>;
export class TiStoreExportTask {
readonly progress: number;
readonly result: Promise<Resp<TiStoreRecordingFile>>;
stop(): Promise<Resp<TiStoreRecordingFile>>;
}store.exportRecording(options, onProgress) 在启动 Task 前固定实例当前的 Token,并保存可选的进度回调,然后同步返回 Resp<TiStoreExportTask>。成功只表示 Task 已经开始。
同步失败不创建 Task,也不调用回调。回调不会在 exportRecording 返回前触发。
progress 位于 0.0..1.0 且单调不回退,只有 MP4 完整生成后才到达 1.0。中间通知可以合并,progress 始终返回当前快照。
活动 Task 调用 stop() 会停止导出、清理临时文件,并以 stopped 返回,不生成部分 MP4。自然完成或失败后调用 stop() 返回缓存的同一结果。重复调用返回同一个 Promise。
Task 完成后自动释放底层资源,不提供 dispose。不同 Task 相互独立并生成不同 cache 文件;资源达到上限返回 resource-exhausted。
错误码
| 错误码 | 常量 | 常见含义 | 建议处理 |
|---|---|---|---|
| 0 | TISTORE_ERROR_OK | 操作成功 | 继续后续流程 |
| 6000 | TISTORE_ERROR_INVALID_ARGUMENT | 参数缺失、取值越界或时间范围无效 | 修正参数后重试 |
| 6001 | — | 尚未初始化云存储能力,或已经关闭 | 先调用 TiStore.init |
| 6014 | TISTORE_ERROR_TOKEN_EXPIRED | Token 已过期 | 为同一设备更新 Token 后重新发起请求 |
| 6022 | — | 已使用另一组配置初始化 | 保持初始化参数一致;需要更换配置时,重启应用后再初始化 |
| 6024 | TISTORE_ERROR_PERMISSION_DENIED | Token 无效,或无权访问目标设备或录像 | 检查授权并重新取得 Token |
| 6026 | — | 对象仍在播放、录制或执行任务,当前操作不能执行 | 先结束活动操作,再重试 |
| 6027 | — | 尚未开始播放或录制 | 先启动对应操作 |
| 6029 | — | 尚未建立当前操作要求的绑定 | 完成对应绑定后重试 |
| 6030 | — | 缺少必要的目标或媒体通道配置 | 补充通道或目标配置后重试 |
| 6043 | — | 当前设备没有足够资源创建或继续任务 | 结束其他任务并释放资源后重试 |
| 6046 | TISTORE_ERROR_FILE_WRITE_FAILED | 无法创建或写入输出文件 | 检查目录权限和磁盘空间 |
| 6113 | TISTORE_ERROR_UNSUPPORTED_FORMAT | 录像的音视频格式不受支持 | 提示当前录像无法播放或导出 |
| 6115 | TISTORE_ERROR_CANCELLED | List 请求被取消 | 结束对应等待 |
| 6117 | TISTORE_ERROR_RANGE_TOO_LARGE | 日期查询超过 31 天,或时间段查询超过 10 天或合并结果超过 10000 项 | 缩短查询范围后重试 |
| 6118 | TISTORE_ERROR_NO_FRAME | Video Output 尚无可用于截图的视频帧 | 等待出画后重试 |
| 6119 | TISTORE_ERROR_NO_RECORDABLE_MEDIA | 所选范围或通道没有可写入的媒体 | 重新选择录像范围或通道 |
| 6120 | TISTORE_ERROR_RECORDING_OVERRUN | 本地写入持续跟不上回放数据,保存任务已经终止 | 释放设备资源、检查存储性能后重新保存 |
| 6122 | TISTORE_ERROR_RECORDING_UNREADABLE | 录像文件缺失、损坏,或上传的格式与帧数据无法读取 | 文件缺失或损坏时停止重试;其他情况检查设备上传的格式与帧要求 |
| 6123 | TISTORE_ERROR_STORE_UNAVAILABLE | 网络、云存储服务或录像数据读取暂时不可用 | 稍后重试 |
| 6124 | TISTORE_ERROR_STOPPED | 活动 ExportTask 已被主动停止 | 结束导出进度,不使用输出文件 |
常量列为“—”的错误通常表示调用顺序或资源状态问题,记录数值用于定位即可,不需要在业务流程中逐项判断。不要根据 errorToString 返回值或日志文本判断错误类型。
事件与生命周期
- 同一对象的事件按产生顺序串行投递;最终 Promise resolve 是该次运行的最后一次通知。
- JavaScript runtime reload 时,SDK 会停止该 runtime 的活动 Task、清理临时文件并释放句柄。旧 runtime 已不存在,因此不会再向旧 Promise 返回最终结果。显式退出页面时,仍应在 runtime 存活期间结束 Task 并等待结果。
dispose会检查对象是否仍在使用,并允许在清理后重试。Replay 仍在播放、仍有 Output 绑定或活动 RecordingTask 时返回in-use。Output 仍绑定 Replay、仍被 Fabric View 承载或仍有活动 Snapshot 时也返回in-use。失败时对象及关系保持不变。dispose只有在 native destroy 真正成功后才返回TISTORE_ERROR_OK;重复调用幂等返回TISTORE_ERROR_OK。成功后普通方法返回not-initialized,getter 保留最后提交值,对象不再接收普通事件。- MP4 Task 完成后自动释放底层资源,不需要额外 dispose。
- Store 和 Output 构造器只创建对应对象,不抛 SDK 运行错误;
createReplay同样只创建 Replay。底层资源创建失败由第一次需要该资源的方法返回。