tirtc-react-native 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +39 -6
  2. package/TiRtcReactNative.podspec +1 -1
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcModule.kt +140 -17
  5. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeCleanup.kt +54 -19
  6. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeCloudStorageCallbacks.kt +60 -0
  7. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventBinder.kt +2 -0
  8. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventSink.kt +20 -0
  9. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeMaps.kt +112 -5
  10. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeOptions.kt +2 -7
  11. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeRegistry.kt +3 -0
  12. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeStorageSupport.kt +46 -22
  13. package/ios/TiRtcReactNative+Conn.mm +61 -0
  14. package/ios/TiRtcReactNative+Output.mm +1 -7
  15. package/ios/TiRtcReactNative+Private.h +1 -0
  16. package/ios/TiRtcReactNative+Storage.mm +133 -35
  17. package/ios/TiRtcReactNative.mm +142 -51
  18. package/ios/TiRtcReactNativeCloudStorageCallbacks.h +13 -0
  19. package/ios/TiRtcReactNativeCloudStorageCallbacks.mm +67 -0
  20. package/ios/TiRtcReactNativeEventBinder.h +4 -0
  21. package/ios/TiRtcReactNativeEventBinder.mm +11 -0
  22. package/ios/TiRtcReactNativeEventSink.h +5 -0
  23. package/ios/TiRtcReactNativeEventSink.mm +18 -0
  24. package/ios/TiRtcReactNativeMaps.h +6 -0
  25. package/ios/TiRtcReactNativeMaps.mm +47 -0
  26. package/ios/TiRtcReactNativeOptions.mm +2 -6
  27. package/lib/module/audio_input.js +23 -4
  28. package/lib/module/audio_input.js.map +1 -1
  29. package/lib/module/build_identity.js +1 -0
  30. package/lib/module/conn.js +19 -0
  31. package/lib/module/conn.js.map +1 -1
  32. package/lib/module/events.js +16 -9
  33. package/lib/module/events.js.map +1 -1
  34. package/lib/module/index.js +3 -2
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/logging_internal.js +16 -0
  37. package/lib/module/logging_internal.js.map +1 -1
  38. package/lib/module/raw_dump.js +53 -0
  39. package/lib/module/raw_dump.js.map +1 -0
  40. package/lib/module/runtime.js +3 -1
  41. package/lib/module/runtime.js.map +1 -1
  42. package/lib/module/specs/NativeTiRtc.js.map +1 -1
  43. package/lib/module/storage.js +190 -33
  44. package/lib/module/storage.js.map +1 -1
  45. package/lib/module/types.js +0 -4
  46. package/lib/module/types.js.map +1 -1
  47. package/lib/module/video_input.js +16 -6
  48. package/lib/module/video_input.js.map +1 -1
  49. package/lib/typescript/build_identity.d.ts +6 -0
  50. package/lib/typescript/conn.d.ts +2 -0
  51. package/lib/typescript/index.d.ts +5 -3
  52. package/lib/typescript/logging_internal.d.ts +1 -0
  53. package/lib/typescript/native.d.ts +1 -1
  54. package/lib/typescript/raw_dump.d.ts +35 -0
  55. package/lib/typescript/specs/NativeTiRtc.d.ts +75 -3
  56. package/lib/typescript/storage.d.ts +84 -3
  57. package/lib/typescript/types.d.ts +5 -6
  58. package/package.json +3 -3
  59. package/src/audio_input.ts +30 -11
  60. package/src/build_identity.ts +6 -0
  61. package/src/conn.ts +23 -0
  62. package/src/events.ts +14 -9
  63. package/src/index.ts +19 -1
  64. package/src/logging_internal.ts +18 -0
  65. package/src/native.ts +7 -1
  66. package/src/raw_dump.ts +84 -0
  67. package/src/runtime.ts +2 -1
  68. package/src/specs/NativeTiRtc.ts +80 -3
  69. package/src/storage.ts +260 -39
  70. package/src/types.ts +5 -8
  71. package/src/video_input.ts +22 -12
@@ -36,6 +36,7 @@ export type NativeVideoOutputOptions = Readonly<{
36
36
  }>;
37
37
 
38
38
  export type NativeAudioInputOptions = Readonly<{
39
+ media: number;
39
40
  codec: 'g711a' | 'aac' | 'pcm' | 'opus' | 'amr';
40
41
  sampleRate: 'rate8k' | 'rate16k';
41
42
  channels: 'mono';
@@ -45,7 +46,7 @@ export type NativeAudioInputOptions = Readonly<{
45
46
  }>;
46
47
 
47
48
  export type NativeVideoInputOptions = Readonly<{
48
- codec: 'h264' | 'mjpeg';
49
+ media: number;
49
50
  width: number;
50
51
  height: number;
51
52
  frameRate: 'fps10' | 'fps15' | 'fps30';
@@ -102,11 +103,63 @@ export type NativeLoggingUploadResult = Readonly<{
102
103
  logId: string | null;
103
104
  }>;
104
105
 
106
+ export type NativeRawDumpStartResult = Readonly<{code: number; dumpId: NativeObjectId | null}>;
107
+ export type NativeRawDumpArchive = Readonly<{
108
+ captureId: string; path: string; size: number; sha256: string; capturedDurationMs: number;
109
+ captureComplete: boolean; stopReason: number;
110
+ unsavedPacketCount: number; unsavedByteCount: number; empty: boolean;
111
+ }>;
112
+ export type NativeRawDumpStopResult = Readonly<{
113
+ code: number; archive: NativeRawDumpArchive | null; terminal: boolean;
114
+ }>;
115
+
105
116
  export type NativeTiCloudStorageRecordingRange = Readonly<{
106
117
  startTimeMs: number;
107
118
  endTimeMs: number;
108
119
  }>;
109
120
 
121
+ export type NativeTiCloudStorageRecordingTrack = Readonly<{
122
+ kind: number;
123
+ channelId: number;
124
+ }>;
125
+
126
+ export type NativeTiCloudStorageRecordingGap = Readonly<{
127
+ range: NativeTiCloudStorageRecordingRange;
128
+ tracks: ReadonlyArray<NativeTiCloudStorageRecordingTrack>;
129
+ reasons: ReadonlyArray<number>;
130
+ }>;
131
+
132
+ export type NativeTiCloudStorageExportProgress = Readonly<{
133
+ fraction: number;
134
+ coveredDurationMs: number;
135
+ }>;
136
+
137
+ export type NativeTiCloudStorageExportSegment = Readonly<{
138
+ sourceRange: NativeTiCloudStorageRecordingRange;
139
+ outputStartMs: number;
140
+ outputEndMs: number;
141
+ }>;
142
+
143
+ export type NativeTiCloudStorageExportReport = Readonly<{
144
+ requestedRange: NativeTiCloudStorageRecordingRange;
145
+ coveredDurationMs: number;
146
+ segments: ReadonlyArray<NativeTiCloudStorageExportSegment>;
147
+ gaps: ReadonlyArray<NativeTiCloudStorageRecordingGap>;
148
+ unprocessedRanges: ReadonlyArray<NativeTiCloudStorageRecordingRange>;
149
+ complete: boolean;
150
+ termination: number;
151
+ cause: number;
152
+ }>;
153
+
154
+ export type NativeTiCloudStorageExportResult = Readonly<{
155
+ code: number;
156
+ fileId: NativeObjectId | null;
157
+ filePath: string | null;
158
+ durationMs: number | null;
159
+ report: NativeTiCloudStorageExportReport | null;
160
+ progressDetail: NativeTiCloudStorageExportProgress | null;
161
+ }>;
162
+
110
163
  export type NativeTiCloudStorageRecordingRangesResult = Readonly<{
111
164
  code: number;
112
165
  recordings: ReadonlyArray<NativeTiCloudStorageRecordingRange>;
@@ -131,6 +184,7 @@ export type NativeTiCloudStorageReplayInfo = Readonly<{
131
184
  export type NativeTiCloudStorageTaskStartResult = Readonly<{
132
185
  code: number;
133
186
  taskId: NativeObjectId | null;
187
+ generation: number;
134
188
  }>;
135
189
 
136
190
  export type NativeTiRtcEvent = Readonly<{
@@ -147,6 +201,8 @@ export type NativeTiRtcEvent = Readonly<{
147
201
  data?: NativeBytes;
148
202
  size?: NativeSize;
149
203
  fps?: number;
204
+ exportProgress?: NativeTiCloudStorageExportProgress;
205
+ recordingGap?: NativeTiCloudStorageRecordingGap;
150
206
  }>;
151
207
 
152
208
  type NativeConnMetricsResult = Readonly<{
@@ -242,15 +298,22 @@ type NativeVideoOutputDebugSnapshotResult = Readonly<{
242
298
  }> | null;
243
299
  }>;
244
300
 
301
+ type NativeTiCloudStorageCallback = Readonly<{
302
+ deliveryId: number;
303
+ event: NativeTiRtcEvent;
304
+ }>;
305
+
245
306
  interface Spec extends TurboModule {
246
307
  readonly onTiRtcEvent: CodegenTypes.EventEmitter<NativeTiRtcEvent>;
247
308
 
248
309
  initialize(options: NativeInitOptions): Promise<number>;
249
310
  shutdown(): number;
250
311
  errorToString(code: number): string;
251
- writeLog(level: number, tag: string, message: string): void;
312
+ writeLog(level: number, tag: string, message: string): number;
252
313
  uploadLogs(): Promise<NativeLoggingUploadResult>;
253
314
 
315
+ tiCloudStorageTakeCallback(): NativeTiCloudStorageCallback | null;
316
+ tiCloudStorageAcknowledgeCallback(deliveryId: number): number;
254
317
  tiCloudStorageInitialize(options: NativeInitOptions): Promise<number>;
255
318
  tiCloudStorageShutdown(): number;
256
319
  tiCloudStorageCreate(token: string): NativeCreateResult;
@@ -275,7 +338,9 @@ interface Spec extends TurboModule {
275
338
  audioChannelId: number | null,
276
339
  ): NativeTiCloudStorageTaskStartResult;
277
340
  tiCloudStorageExportProgress(id: NativeObjectId): number;
278
- tiCloudStorageExportResult(id: NativeObjectId): Promise<NativeMp4Result>;
341
+ tiCloudStorageExportProgressDetail(id: NativeObjectId): NativeTiCloudStorageExportProgress;
342
+ tiCloudStorageExportResult(id: NativeObjectId): Promise<NativeTiCloudStorageExportResult>;
343
+ tiCloudStorageExportCancel(id: NativeObjectId): number;
279
344
  tiCloudStorageExportStop(id: NativeObjectId): number;
280
345
  tiCloudStorageDispose(id: NativeObjectId): number;
281
346
 
@@ -290,6 +355,11 @@ interface Spec extends TurboModule {
290
355
  videoChannelId: number,
291
356
  audioChannelId: number | null,
292
357
  ): NativeTiCloudStorageTaskStartResult;
358
+ tiCloudStorageReplayStartRawDump(
359
+ id: NativeObjectId,
360
+ audioChannelIds: ReadonlyArray<number>,
361
+ videoChannelIds: ReadonlyArray<number>,
362
+ ): Promise<NativeRawDumpStartResult>;
293
363
  tiCloudStorageRecordingTaskStop(id: NativeObjectId): Promise<NativeMp4Result>;
294
364
  tiCloudStorageReplayStop(id: NativeObjectId): number;
295
365
  tiCloudStorageReplayDispose(id: NativeObjectId): number;
@@ -326,6 +396,13 @@ interface Spec extends TurboModule {
326
396
  id: NativeObjectId,
327
397
  options: NativeRecordingOptions,
328
398
  ): NativeRecordingStartResult;
399
+ connStartRawDump(
400
+ id: NativeObjectId,
401
+ audioStreamIds: ReadonlyArray<number>,
402
+ videoStreamIds: ReadonlyArray<number>,
403
+ uplinkAudioStreamIds: ReadonlyArray<number>,
404
+ ): Promise<NativeRawDumpStartResult>;
405
+ rawDumpStop(id: NativeObjectId): Promise<NativeRawDumpStopResult>;
329
406
  recordingTaskStop(id: NativeObjectId): Promise<NativeMp4Result>;
330
407
  mediaFileMoveToGallery(id: NativeObjectId, fileName: string | null): Promise<NativeGalleryResult>;
331
408
  mediaFileDelete(id: NativeObjectId): Promise<number>;
package/src/storage.ts CHANGED
@@ -11,7 +11,15 @@ import {
11
11
  } from './errors';
12
12
  import {registerNativeEventHandler, unregisterNativeEventHandler} from './events';
13
13
  import {TiRtcLogging} from './logging';
14
- import {NativeTiRtc, NativeTiRtcVideoOutputView, type NativeTiRtcEvent} from './native';
14
+ import {logBuildIdentity} from './logging_internal';
15
+ import {
16
+ NativeTiRtc,
17
+ NativeTiRtcVideoOutputView,
18
+ type NativeTiRtcEvent,
19
+ type NativeTiCloudStorageExportProgress,
20
+ type NativeTiCloudStorageExportReport,
21
+ type NativeTiCloudStorageRecordingGap,
22
+ } from './native';
15
23
  import {
16
24
  clearNativeIdentity,
17
25
  readNativeIdentity,
@@ -20,6 +28,12 @@ import {
20
28
  type NativeIdentity,
21
29
  } from './native_object_id';
22
30
  import {NativeMediaFileBase, recordingFailure, type Resp, type TiRtcGalleryAsset} from './recording';
31
+ import {
32
+ createRawDumpFromNative,
33
+ normalizeRawDumpIds,
34
+ TiRawDump,
35
+ type TiCloudStorageRawDumpOptions,
36
+ } from './raw_dump';
23
37
 
24
38
  export const TI_CLOUD_STORAGE_ERROR_OK = 0 as const;
25
39
  export const TI_CLOUD_STORAGE_ERROR_INVALID_ARGUMENT = 6000 as const;
@@ -45,6 +59,8 @@ export const TI_CLOUD_STORAGE_ERROR_UNAVAILABLE = 6123 as const;
45
59
  export const TI_CLOUD_STORAGE_ERROR_STOPPED = 6124 as const;
46
60
  export const TI_CLOUD_STORAGE_ERROR_RECORDING_NOT_FOUND = 6134 as const;
47
61
  export const TI_CLOUD_STORAGE_ERROR_RECORDING_DOWNLOAD_FAILED = 6135 as const;
62
+ export const TI_CLOUD_STORAGE_ERROR_NETWORK_UNAVAILABLE = 6136 as const;
63
+ export const TI_CLOUD_STORAGE_ERROR_ENDPOINT_DNS_RESOLUTION_FAILED = 6137 as const;
48
64
 
49
65
  export type TiCloudStorageRecordingRange = Readonly<{
50
66
  startTimeMs: number;
@@ -63,6 +79,70 @@ export type TiCloudStorageRecordingDaysResult = Readonly<{
63
79
  days: ReadonlyArray<TiCloudStorageRecordingDay>;
64
80
  }>;
65
81
 
82
+ export const TiCloudStorageRecordingTrackKind = {video: 'video', audio: 'audio'} as const;
83
+ export type TiCloudStorageRecordingTrackKind =
84
+ (typeof TiCloudStorageRecordingTrackKind)[keyof typeof TiCloudStorageRecordingTrackKind];
85
+ export type TiCloudStorageRecordingTrack = Readonly<{
86
+ kind: TiCloudStorageRecordingTrackKind;
87
+ channelId: number;
88
+ }>;
89
+ export const TiCloudStorageRecordingGapReason = {
90
+ unknown: 'unknown',
91
+ notFound: 'notFound',
92
+ downloadFailed: 'downloadFailed',
93
+ integrityFailed: 'integrityFailed',
94
+ mediaUnreadable: 'mediaUnreadable',
95
+ noKeyFrame: 'noKeyFrame',
96
+ noRecording: 'noRecording',
97
+ decryptionFailed: 'decryptionFailed',
98
+ unsupportedMedia: 'unsupportedMedia',
99
+ trackUnavailable: 'trackUnavailable',
100
+ } as const;
101
+ export type TiCloudStorageRecordingGapReason =
102
+ (typeof TiCloudStorageRecordingGapReason)[keyof typeof TiCloudStorageRecordingGapReason];
103
+ export type TiCloudStorageRecordingGap = Readonly<{
104
+ range: TiCloudStorageRecordingRange;
105
+ tracks: ReadonlyArray<TiCloudStorageRecordingTrack>;
106
+ reasons: ReadonlyArray<TiCloudStorageRecordingGapReason>;
107
+ }>;
108
+ export type TiCloudStorageExportProgress = Readonly<{
109
+ fraction: number;
110
+ coveredDurationMs: number;
111
+ }>;
112
+ export type TiCloudStorageExportSegment = Readonly<{
113
+ sourceRange: TiCloudStorageRecordingRange;
114
+ outputStartMs: number;
115
+ outputEndMs: number;
116
+ }>;
117
+ export const TiCloudStorageExportTermination = {
118
+ exhausted: 'exhausted',
119
+ interrupted: 'interrupted',
120
+ cancelled: 'cancelled',
121
+ failed: 'failed',
122
+ } as const;
123
+ export type TiCloudStorageExportTermination =
124
+ (typeof TiCloudStorageExportTermination)[keyof typeof TiCloudStorageExportTermination];
125
+ export type TiCloudStorageExportReport = Readonly<{
126
+ requestedRange: TiCloudStorageRecordingRange;
127
+ coveredDurationMs: number;
128
+ segments: ReadonlyArray<TiCloudStorageExportSegment>;
129
+ gaps: ReadonlyArray<TiCloudStorageRecordingGap>;
130
+ unprocessedRanges: ReadonlyArray<TiCloudStorageRecordingRange>;
131
+ complete: boolean;
132
+ termination: TiCloudStorageExportTermination;
133
+ cause: number;
134
+ }>;
135
+ export type TiCloudStorageExportOutcome = Readonly<{
136
+ code: number;
137
+ file: TiCloudStorageRecordingFile | null;
138
+ report: TiCloudStorageExportReport | null;
139
+ }>;
140
+ export type TiCloudStorageExportObserver = Readonly<{
141
+ onProgress?: (progress: number) => void;
142
+ onProgressDetail?: (progress: TiCloudStorageExportProgress) => void;
143
+ onRecordingGap?: (gap: TiCloudStorageRecordingGap) => void;
144
+ }>;
145
+
66
146
  export const TiCloudStorageReplaySpeed = {
67
147
  x0_125: 'x0_125',
68
148
  x0_25: 'x0_25',
@@ -144,6 +224,37 @@ function success<T>(data: T): Resp<T> {
144
224
  function validTime(value: number): boolean {
145
225
  return isFiniteSafeInteger(value) && value >= 0;
146
226
  }
227
+
228
+ function recordingGapFromNative(value: NativeTiCloudStorageRecordingGap): TiCloudStorageRecordingGap {
229
+ const kinds = [TiCloudStorageRecordingTrackKind.video, TiCloudStorageRecordingTrackKind.audio] as const;
230
+ const reasons = Object.values(TiCloudStorageRecordingGapReason);
231
+ return {
232
+ range: value.range,
233
+ tracks: value.tracks.flatMap((track) => {
234
+ const kind = kinds[track.kind - 1];
235
+ return kind === undefined ? [] : [{kind, channelId: track.channelId}];
236
+ }),
237
+ reasons: value.reasons.map((reason) => reasons[reason] ?? TiCloudStorageRecordingGapReason.unknown),
238
+ };
239
+ }
240
+
241
+ function exportProgressFromNative(value: NativeTiCloudStorageExportProgress): TiCloudStorageExportProgress {
242
+ return {fraction: value.fraction, coveredDurationMs: value.coveredDurationMs};
243
+ }
244
+
245
+ function exportReportFromNative(value: NativeTiCloudStorageExportReport): TiCloudStorageExportReport {
246
+ const terminations = Object.values(TiCloudStorageExportTermination);
247
+ return {
248
+ requestedRange: value.requestedRange,
249
+ coveredDurationMs: value.coveredDurationMs,
250
+ segments: value.segments,
251
+ gaps: value.gaps.map(recordingGapFromNative),
252
+ unprocessedRanges: value.unprocessedRanges,
253
+ complete: value.complete,
254
+ termination: terminations[value.termination] ?? TiCloudStorageExportTermination.failed,
255
+ cause: value.cause,
256
+ };
257
+ }
147
258
  function speedToNative(speed: TiCloudStorageReplaySpeed): number | null {
148
259
  switch (speed) {
149
260
  case TiCloudStorageReplaySpeed.x0_125:
@@ -214,6 +325,7 @@ function videoStateFromNative(state: unknown): TiCloudStorageVideoOutputState {
214
325
  export class TiCloudStorage {
215
326
  private identity: NativeIdentity | null = null;
216
327
  private disposed = false;
328
+ private pendingAsyncOperations = 0;
217
329
  constructor(private token: string) {}
218
330
 
219
331
  static init(
@@ -232,6 +344,7 @@ export class TiCloudStorage {
232
344
  endpoint: options.endpoint ?? '',
233
345
  consoleLogEnabled: options.consoleLogEnabled ?? false,
234
346
  }).then((code) => {
347
+ if (code === 0) logBuildIdentity('cloud_storage');
235
348
  tiCloudStorageLogOutcome('cloud_storage_initialize', code);
236
349
  return code;
237
350
  });
@@ -268,10 +381,12 @@ export class TiCloudStorage {
268
381
  return Promise.resolve({code: identity, recordings: []});
269
382
  }
270
383
  TiRtcLogging.i(tiCloudStorageLogTag, `[recording_list] outcome=started range_duration_ms=${endTimeMs - startTimeMs}`);
271
- return NativeTiRtc.tiCloudStorageListRecordings(identity.objectId, startTimeMs, endTimeMs).then((result) => {
272
- tiCloudStorageLogOutcome('recording_list', result.code, ` recording_count=${result.recordings.length}`);
273
- return result;
274
- });
384
+ return this.trackAsyncOperation(() =>
385
+ NativeTiRtc.tiCloudStorageListRecordings(identity.objectId, startTimeMs, endTimeMs).then((result) => {
386
+ tiCloudStorageLogOutcome('recording_list', result.code, ` recording_count=${result.recordings.length}`);
387
+ return result;
388
+ }),
389
+ );
275
390
  }
276
391
  listRecordingDays(
277
392
  startDate: string,
@@ -292,20 +407,25 @@ export class TiCloudStorage {
292
407
  return Promise.resolve({code: identity, days: []});
293
408
  }
294
409
  TiRtcLogging.i(tiCloudStorageLogTag, '[recording_days_list] outcome=started');
295
- return NativeTiRtc.tiCloudStorageListRecordingDays(
296
- identity.objectId,
297
- startDate,
298
- endDate,
299
- timeZoneId,
300
- ).then((result) => {
301
- tiCloudStorageLogOutcome('recording_days_list', result.code, ` day_count=${result.days.length}`);
302
- return result;
303
- });
410
+ return this.trackAsyncOperation(() =>
411
+ NativeTiRtc.tiCloudStorageListRecordingDays(
412
+ identity.objectId,
413
+ startDate,
414
+ endDate,
415
+ timeZoneId,
416
+ ).then((result) => {
417
+ tiCloudStorageLogOutcome('recording_days_list', result.code, ` day_count=${result.days.length}`);
418
+ return result;
419
+ }),
420
+ );
304
421
  }
305
422
  createReplay(): TiCloudStorageReplay {
306
423
  return new TiCloudStorageReplay(this);
307
424
  }
308
- exportRecording(options: TiCloudStorageExportOptions, onProgress?: (progress: number) => void): Resp<TiCloudStorageExportTask> {
425
+ exportRecording(
426
+ options: TiCloudStorageExportOptions,
427
+ observer?: ((progress: number) => void) | TiCloudStorageExportObserver,
428
+ ): Resp<TiCloudStorageExportTask> {
309
429
  if (
310
430
  !validTime(options.startTimeMs) ||
311
431
  !validTime(options.endTimeMs) ||
@@ -326,11 +446,12 @@ export class TiCloudStorage {
326
446
  );
327
447
  tiCloudStorageLogOutcome('export_start', result.code, ` range_duration_ms=${options.endTimeMs - options.startTimeMs}`);
328
448
  return result.code === 0 && result.taskId !== null
329
- ? success(new TiCloudStorageExportTask(result.taskId, this, onProgress))
449
+ ? success(new TiCloudStorageExportTask(result.taskId, result.generation, this, observer))
330
450
  : recordingFailure(result.code);
331
451
  }
332
452
  dispose(): number {
333
453
  if (this.disposed) return 0;
454
+ if (this.pendingAsyncOperations > 0) return TI_CLOUD_STORAGE_ERROR_IN_USE;
334
455
  if (this.identity === null) {
335
456
  this.disposed = true;
336
457
  this.token = '';
@@ -347,6 +468,17 @@ export class TiCloudStorage {
347
468
  }
348
469
  return code;
349
470
  }
471
+ private trackAsyncOperation<T>(operation: () => Promise<T>): Promise<T> {
472
+ this.pendingAsyncOperations += 1;
473
+ try {
474
+ return operation().finally(() => {
475
+ this.pendingAsyncOperations -= 1;
476
+ });
477
+ } catch (error) {
478
+ this.pendingAsyncOperations -= 1;
479
+ return Promise.reject(error);
480
+ }
481
+ }
350
482
  private ensureIdentity(): NativeIdentity | number {
351
483
  if (this.disposed) return TI_CLOUD_STORAGE_ERROR_NOT_INITIALIZED;
352
484
  if (this.identity !== null) return this.identity;
@@ -369,11 +501,13 @@ function tiCloudStorageNativeIdentity(cloudStorage: TiCloudStorage): NativeIdent
369
501
  export class TiCloudStorageReplay {
370
502
  private identity: NativeIdentity | null = null;
371
503
  private disposed = false;
504
+ private inCallback = false;
372
505
  private currentSpeed: TiCloudStorageReplaySpeed = 'x1';
373
506
  private currentTime: number | null = null;
374
507
  onTimeChanged: ((timeMs: number) => void) | null = null;
375
508
  onError: ((code: number) => void) | null = null;
376
509
  onCompleted: (() => void) | null = null;
510
+ onRecordingGap: ((gap: TiCloudStorageRecordingGap) => void) | null = null;
377
511
  constructor(private readonly cloudStorage: TiCloudStorage) {}
378
512
  get speed(): TiCloudStorageReplaySpeed {
379
513
  if (this.identity !== null) {
@@ -390,6 +524,7 @@ export class TiCloudStorageReplay {
390
524
  return this.currentTime;
391
525
  }
392
526
  play(startTimeMs: number, endTimeMs: number, initialTimeMs?: number): number {
527
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
393
528
  if (
394
529
  !validTime(startTimeMs) ||
395
530
  !validTime(endTimeMs) ||
@@ -417,6 +552,7 @@ export class TiCloudStorageReplay {
417
552
  return code;
418
553
  }
419
554
  seek(timeMs: number): number {
555
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
420
556
  if (!validTime(timeMs)) return TI_CLOUD_STORAGE_ERROR_INVALID_ARGUMENT;
421
557
  const identity = this.ensureIdentity();
422
558
  const code = typeof identity === 'number' ? identity : NativeTiRtc.tiCloudStorageReplaySeek(identity.objectId, timeMs);
@@ -452,19 +588,38 @@ export class TiCloudStorageReplay {
452
588
  ? success(new TiCloudStorageRecordingTask(result.taskId, this))
453
589
  : recordingFailure(result.code);
454
590
  }
591
+ async startRawDump(options: TiCloudStorageRawDumpOptions): Promise<Resp<TiRawDump>> {
592
+ if (options === null || typeof options !== 'object')
593
+ return recordingFailure(TI_CLOUD_STORAGE_ERROR_INVALID_ARGUMENT);
594
+ const audio = normalizeRawDumpIds(options.audioChannelIds ?? [], 255);
595
+ const video = normalizeRawDumpIds(options.videoChannelIds ?? [], 255);
596
+ if (audio === null || video === null || (audio.length === 0 && video.length === 0))
597
+ return recordingFailure(TI_CLOUD_STORAGE_ERROR_INVALID_ARGUMENT);
598
+ const identity = this.ensureIdentity();
599
+ if (typeof identity === 'number') return recordingFailure(identity);
600
+ const result = await NativeTiRtc.tiCloudStorageReplayStartRawDump(
601
+ identity.objectId, audio, video,
602
+ );
603
+ return result.code === 0 && result.dumpId !== null
604
+ ? {success: true, code: null, message: null, data: createRawDumpFromNative(result.dumpId)}
605
+ : recordingFailure(result.code);
606
+ }
455
607
  stop(): number {
608
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
456
609
  const code = this.command(NativeTiRtc.tiCloudStorageReplayStop);
457
610
  if (code === 0) this.currentTime = null;
458
611
  tiCloudStorageLogOutcome('replay_stop', code);
459
612
  return code;
460
613
  }
461
614
  dispose(): number {
615
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
462
616
  if (this.disposed) return 0;
463
617
  if (this.identity === null) {
464
618
  this.disposed = true;
465
619
  this.onTimeChanged = null;
466
620
  this.onError = null;
467
621
  this.onCompleted = null;
622
+ this.onRecordingGap = null;
468
623
  return 0;
469
624
  }
470
625
  const identity = this.identity;
@@ -477,6 +632,7 @@ export class TiCloudStorageReplay {
477
632
  this.onTimeChanged = null;
478
633
  this.onError = null;
479
634
  this.onCompleted = null;
635
+ this.onRecordingGap = null;
480
636
  }
481
637
  tiCloudStorageLogOutcome('replay_dispose', code);
482
638
  return code;
@@ -501,11 +657,18 @@ export class TiCloudStorageReplay {
501
657
  return this.identity;
502
658
  }
503
659
  private handleEvent(event: NativeTiRtcEvent): void {
504
- if (event.type === 'tiCloudStorageReplayTimeChanged' && event.timestampMs !== undefined) {
505
- this.currentTime = event.timestampMs;
506
- this.onTimeChanged?.(event.timestampMs);
507
- } else if (event.type === 'tiCloudStorageReplayCompleted') this.onCompleted?.();
508
- else if (event.type === 'tiCloudStorageReplayError') this.onError?.(event.code ?? TI_CLOUD_STORAGE_ERROR_UNAVAILABLE);
660
+ this.inCallback = true;
661
+ try {
662
+ if (event.type === 'tiCloudStorageReplayTimeChanged' && event.timestampMs !== undefined) {
663
+ this.currentTime = event.timestampMs;
664
+ this.onTimeChanged?.(event.timestampMs);
665
+ } else if (event.type === 'tiCloudStorageReplayCompleted') this.onCompleted?.();
666
+ else if (event.type === 'tiCloudStorageReplayError') this.onError?.(event.code ?? TI_CLOUD_STORAGE_ERROR_UNAVAILABLE);
667
+ else if (event.type === 'tiCloudStorageReplayRecordingGap' && event.recordingGap !== undefined)
668
+ this.onRecordingGap?.(recordingGapFromNative(event.recordingGap));
669
+ } finally {
670
+ this.inCallback = false;
671
+ }
509
672
  }
510
673
  }
511
674
 
@@ -732,40 +895,98 @@ export class TiCloudStorageRecordingTask {
732
895
 
733
896
  export class TiCloudStorageExportTask {
734
897
  private resultPromise: Promise<Resp<TiCloudStorageRecordingFile>>;
898
+ private completionPromise: Promise<TiCloudStorageExportOutcome>;
899
+ private readonly identity: NativeIdentity;
900
+ private currentProgressDetail: TiCloudStorageExportProgress = {fraction: 0, coveredDurationMs: 0};
901
+ private currentReport: TiCloudStorageExportReport | null = null;
902
+ private readonly observer: TiCloudStorageExportObserver;
903
+ private terminal = false;
904
+
735
905
  constructor(
736
906
  private readonly taskId: number,
907
+ generation: number,
737
908
  private readonly owner: TiCloudStorage,
738
- private readonly onProgress?: (progress: number) => void,
909
+ observer?: ((progress: number) => void) | TiCloudStorageExportObserver,
739
910
  ) {
740
- this.resultPromise = NativeTiRtc.tiCloudStorageExportResult(taskId).then((result) => {
911
+ this.identity = {objectId: taskId, generation};
912
+ this.observer = typeof observer === 'function' ? {onProgress: observer} : (observer ?? {});
913
+ registerNativeEventHandler(this.identity, (event) => this.handleEvent(event));
914
+ this.completionPromise = NativeTiRtc.tiCloudStorageExportResult(taskId).then((result) => {
915
+ if (result.progressDetail != null) {
916
+ const progress = exportProgressFromNative(result.progressDetail);
917
+ this.currentProgressDetail = {
918
+ fraction: Math.max(this.currentProgressDetail.fraction, progress.fraction),
919
+ coveredDurationMs: Math.max(this.currentProgressDetail.coveredDurationMs, progress.coveredDurationMs),
920
+ };
921
+ }
922
+ this.terminal = true;
923
+ unregisterNativeEventHandler(this.identity);
741
924
  tiCloudStorageLogOutcome('export_terminal', result.code, ` duration_ms=${result.durationMs ?? 0}`);
742
- return result.code === 0 && result.fileId !== null && result.filePath !== null && result.durationMs !== null
743
- ? success(new RecordingFile(result.filePath, result.durationMs, result.fileId))
744
- : recordingFailure(result.code);
925
+ const file =
926
+ result.fileId !== null && result.filePath !== null && result.durationMs !== null
927
+ ? new RecordingFile(result.filePath, result.durationMs, result.fileId)
928
+ : null;
929
+ this.currentReport = result.report === null ? null : exportReportFromNative(result.report);
930
+ if (this.currentReport !== null) {
931
+ this.currentProgressDetail = {
932
+ fraction: this.currentProgressDetail.fraction,
933
+ coveredDurationMs: this.currentReport.coveredDurationMs,
934
+ };
935
+ }
936
+ return {code: result.code, file, report: this.currentReport};
745
937
  });
746
- if (onProgress !== undefined) {
747
- let lastProgress = -1;
748
- const timer = setInterval(() => {
749
- const progress = NativeTiRtc.tiCloudStorageExportProgress(taskId);
750
- if (progress !== lastProgress) {
751
- lastProgress = progress;
752
- onProgress(progress);
753
- }
754
- }, 100);
755
- const clear = () => clearInterval(timer);
756
- void this.resultPromise.then(clear, clear);
757
- }
938
+ this.resultPromise = this.completionPromise.then((outcome) =>
939
+ outcome.code === TI_CLOUD_STORAGE_ERROR_OK && outcome.file !== null
940
+ ? success(outcome.file)
941
+ : recordingFailure(outcome.code),
942
+ );
758
943
  }
759
944
  get progress(): number {
760
945
  void this.owner;
761
- return NativeTiRtc.tiCloudStorageExportProgress(this.taskId);
946
+ return this.progressDetail.fraction;
947
+ }
948
+ get progressDetail(): TiCloudStorageExportProgress {
949
+ if (this.terminal) return this.currentProgressDetail;
950
+ const value = exportProgressFromNative(NativeTiRtc.tiCloudStorageExportProgressDetail(this.taskId));
951
+ this.currentProgressDetail = {
952
+ fraction: Math.max(this.currentProgressDetail.fraction, value.fraction),
953
+ coveredDurationMs: Math.max(this.currentProgressDetail.coveredDurationMs, value.coveredDurationMs),
954
+ };
955
+ return this.currentProgressDetail;
956
+ }
957
+ get report(): TiCloudStorageExportReport | null {
958
+ return this.currentReport;
959
+ }
960
+ get completion(): Promise<TiCloudStorageExportOutcome> {
961
+ return this.completionPromise;
762
962
  }
763
963
  get result(): Promise<Resp<TiCloudStorageRecordingFile>> {
764
964
  return this.resultPromise;
765
965
  }
966
+ cancel(): number {
967
+ if (this.terminal) return TI_CLOUD_STORAGE_ERROR_OK;
968
+ const code = NativeTiRtc.tiCloudStorageExportCancel(this.taskId);
969
+ tiCloudStorageLogOutcome('export_cancel', code);
970
+ return code;
971
+ }
766
972
  stop(): Promise<Resp<TiCloudStorageRecordingFile>> {
973
+ if (this.terminal) return this.resultPromise;
767
974
  const code = NativeTiRtc.tiCloudStorageExportStop(this.taskId);
768
975
  tiCloudStorageLogOutcome('export_stop', code);
769
976
  return this.resultPromise;
770
977
  }
978
+ private handleEvent(event: NativeTiRtcEvent): void {
979
+ if (this.terminal) return;
980
+ if (event.type === 'tiCloudStorageExportProgress' && event.exportProgress !== undefined) {
981
+ const progress = exportProgressFromNative(event.exportProgress);
982
+ this.currentProgressDetail = {
983
+ fraction: Math.max(this.currentProgressDetail.fraction, progress.fraction),
984
+ coveredDurationMs: Math.max(this.currentProgressDetail.coveredDurationMs, progress.coveredDurationMs),
985
+ };
986
+ this.observer.onProgress?.(this.currentProgressDetail.fraction);
987
+ this.observer.onProgressDetail?.(this.currentProgressDetail);
988
+ } else if (event.type === 'tiCloudStorageExportRecordingGap' && event.recordingGap !== undefined) {
989
+ this.observer.onRecordingGap?.(recordingGapFromNative(event.recordingGap));
990
+ }
991
+ }
771
992
  }
package/src/types.ts CHANGED
@@ -84,13 +84,6 @@ export const TiRtcVideoCodec = {
84
84
  } as const;
85
85
  export type TiRtcVideoCodec = (typeof TiRtcVideoCodec)[keyof typeof TiRtcVideoCodec];
86
86
 
87
- export const TiRtcVideoEncoderCodec = {
88
- h264: 'h264',
89
- mjpeg: 'mjpeg',
90
- } as const;
91
- export type TiRtcVideoEncoderCodec =
92
- (typeof TiRtcVideoEncoderCodec)[keyof typeof TiRtcVideoEncoderCodec];
93
-
94
87
  export const TiRtcVideoFrameRate = {
95
88
  fps10: 'fps10',
96
89
  fps15: 'fps15',
@@ -183,6 +176,9 @@ export type TiRtcVideoOutputViewProps = ViewProps &
183
176
  }>;
184
177
 
185
178
  export type TiRtcAudioInputOptions = Readonly<{
179
+ /** Nano audio media: 1 PCM, 2 G711A, 3 AAC, 4 Opus, 5 AMR. Omitted or -1 uses codec. */
180
+ media?: number;
181
+ /** @deprecated Use media. The legacy default is g711a. */
186
182
  codec?: TiRtcAudioCodec;
187
183
  sampleRate?: TiRtcAudioSampleRate;
188
184
  channels?: TiRtcAudioChannelCount;
@@ -192,7 +188,8 @@ export type TiRtcAudioInputOptions = Readonly<{
192
188
  }>;
193
189
 
194
190
  export type TiRtcVideoInputOptions = Readonly<{
195
- codec?: TiRtcVideoEncoderCodec;
191
+ /** Nano video media: 66 H.264 (default) or 65 MJPEG. */
192
+ media?: number;
196
193
  width?: number;
197
194
  height?: number;
198
195
  frameRate?: TiRtcVideoFrameRate;