tirtc-react-native 2.5.0-alpha.1 → 2.5.2

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 (62) 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 +137 -14
  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 +137 -48
  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 -1
  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/raw_dump.js +53 -0
  37. package/lib/module/raw_dump.js.map +1 -0
  38. package/lib/module/specs/NativeTiRtc.js.map +1 -1
  39. package/lib/module/storage.js +185 -33
  40. package/lib/module/storage.js.map +1 -1
  41. package/lib/module/types.js +0 -4
  42. package/lib/module/types.js.map +1 -1
  43. package/lib/module/video_input.js +16 -6
  44. package/lib/module/video_input.js.map +1 -1
  45. package/lib/typescript/conn.d.ts +2 -0
  46. package/lib/typescript/index.d.ts +5 -3
  47. package/lib/typescript/native.d.ts +1 -1
  48. package/lib/typescript/raw_dump.d.ts +35 -0
  49. package/lib/typescript/specs/NativeTiRtc.d.ts +74 -2
  50. package/lib/typescript/storage.d.ts +82 -3
  51. package/lib/typescript/types.d.ts +5 -6
  52. package/package.json +2 -2
  53. package/src/audio_input.ts +30 -11
  54. package/src/conn.ts +23 -0
  55. package/src/events.ts +14 -9
  56. package/src/index.ts +17 -1
  57. package/src/native.ts +7 -1
  58. package/src/raw_dump.ts +84 -0
  59. package/src/specs/NativeTiRtc.ts +79 -2
  60. package/src/storage.ts +256 -39
  61. package/src/types.ts +5 -8
  62. package/src/video_input.ts +22 -12
package/src/storage.ts CHANGED
@@ -12,7 +12,14 @@ import {
12
12
  import {registerNativeEventHandler, unregisterNativeEventHandler} from './events';
13
13
  import {TiRtcLogging} from './logging';
14
14
  import {logBuildIdentity} from './logging_internal';
15
- import {NativeTiRtc, NativeTiRtcVideoOutputView, type NativeTiRtcEvent} from './native';
15
+ import {
16
+ NativeTiRtc,
17
+ NativeTiRtcVideoOutputView,
18
+ type NativeTiRtcEvent,
19
+ type NativeTiCloudStorageExportProgress,
20
+ type NativeTiCloudStorageExportReport,
21
+ type NativeTiCloudStorageRecordingGap,
22
+ } from './native';
16
23
  import {
17
24
  clearNativeIdentity,
18
25
  readNativeIdentity,
@@ -21,6 +28,12 @@ import {
21
28
  type NativeIdentity,
22
29
  } from './native_object_id';
23
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';
24
37
 
25
38
  export const TI_CLOUD_STORAGE_ERROR_OK = 0 as const;
26
39
  export const TI_CLOUD_STORAGE_ERROR_INVALID_ARGUMENT = 6000 as const;
@@ -66,6 +79,70 @@ export type TiCloudStorageRecordingDaysResult = Readonly<{
66
79
  days: ReadonlyArray<TiCloudStorageRecordingDay>;
67
80
  }>;
68
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
+
69
146
  export const TiCloudStorageReplaySpeed = {
70
147
  x0_125: 'x0_125',
71
148
  x0_25: 'x0_25',
@@ -147,6 +224,37 @@ function success<T>(data: T): Resp<T> {
147
224
  function validTime(value: number): boolean {
148
225
  return isFiniteSafeInteger(value) && value >= 0;
149
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
+ }
150
258
  function speedToNative(speed: TiCloudStorageReplaySpeed): number | null {
151
259
  switch (speed) {
152
260
  case TiCloudStorageReplaySpeed.x0_125:
@@ -217,6 +325,7 @@ function videoStateFromNative(state: unknown): TiCloudStorageVideoOutputState {
217
325
  export class TiCloudStorage {
218
326
  private identity: NativeIdentity | null = null;
219
327
  private disposed = false;
328
+ private pendingAsyncOperations = 0;
220
329
  constructor(private token: string) {}
221
330
 
222
331
  static init(
@@ -272,10 +381,12 @@ export class TiCloudStorage {
272
381
  return Promise.resolve({code: identity, recordings: []});
273
382
  }
274
383
  TiRtcLogging.i(tiCloudStorageLogTag, `[recording_list] outcome=started range_duration_ms=${endTimeMs - startTimeMs}`);
275
- return NativeTiRtc.tiCloudStorageListRecordings(identity.objectId, startTimeMs, endTimeMs).then((result) => {
276
- tiCloudStorageLogOutcome('recording_list', result.code, ` recording_count=${result.recordings.length}`);
277
- return result;
278
- });
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
+ );
279
390
  }
280
391
  listRecordingDays(
281
392
  startDate: string,
@@ -296,20 +407,25 @@ export class TiCloudStorage {
296
407
  return Promise.resolve({code: identity, days: []});
297
408
  }
298
409
  TiRtcLogging.i(tiCloudStorageLogTag, '[recording_days_list] outcome=started');
299
- return NativeTiRtc.tiCloudStorageListRecordingDays(
300
- identity.objectId,
301
- startDate,
302
- endDate,
303
- timeZoneId,
304
- ).then((result) => {
305
- tiCloudStorageLogOutcome('recording_days_list', result.code, ` day_count=${result.days.length}`);
306
- return result;
307
- });
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
+ );
308
421
  }
309
422
  createReplay(): TiCloudStorageReplay {
310
423
  return new TiCloudStorageReplay(this);
311
424
  }
312
- exportRecording(options: TiCloudStorageExportOptions, onProgress?: (progress: number) => void): Resp<TiCloudStorageExportTask> {
425
+ exportRecording(
426
+ options: TiCloudStorageExportOptions,
427
+ observer?: ((progress: number) => void) | TiCloudStorageExportObserver,
428
+ ): Resp<TiCloudStorageExportTask> {
313
429
  if (
314
430
  !validTime(options.startTimeMs) ||
315
431
  !validTime(options.endTimeMs) ||
@@ -330,11 +446,12 @@ export class TiCloudStorage {
330
446
  );
331
447
  tiCloudStorageLogOutcome('export_start', result.code, ` range_duration_ms=${options.endTimeMs - options.startTimeMs}`);
332
448
  return result.code === 0 && result.taskId !== null
333
- ? success(new TiCloudStorageExportTask(result.taskId, this, onProgress))
449
+ ? success(new TiCloudStorageExportTask(result.taskId, result.generation, this, observer))
334
450
  : recordingFailure(result.code);
335
451
  }
336
452
  dispose(): number {
337
453
  if (this.disposed) return 0;
454
+ if (this.pendingAsyncOperations > 0) return TI_CLOUD_STORAGE_ERROR_IN_USE;
338
455
  if (this.identity === null) {
339
456
  this.disposed = true;
340
457
  this.token = '';
@@ -351,6 +468,17 @@ export class TiCloudStorage {
351
468
  }
352
469
  return code;
353
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
+ }
354
482
  private ensureIdentity(): NativeIdentity | number {
355
483
  if (this.disposed) return TI_CLOUD_STORAGE_ERROR_NOT_INITIALIZED;
356
484
  if (this.identity !== null) return this.identity;
@@ -373,11 +501,13 @@ function tiCloudStorageNativeIdentity(cloudStorage: TiCloudStorage): NativeIdent
373
501
  export class TiCloudStorageReplay {
374
502
  private identity: NativeIdentity | null = null;
375
503
  private disposed = false;
504
+ private inCallback = false;
376
505
  private currentSpeed: TiCloudStorageReplaySpeed = 'x1';
377
506
  private currentTime: number | null = null;
378
507
  onTimeChanged: ((timeMs: number) => void) | null = null;
379
508
  onError: ((code: number) => void) | null = null;
380
509
  onCompleted: (() => void) | null = null;
510
+ onRecordingGap: ((gap: TiCloudStorageRecordingGap) => void) | null = null;
381
511
  constructor(private readonly cloudStorage: TiCloudStorage) {}
382
512
  get speed(): TiCloudStorageReplaySpeed {
383
513
  if (this.identity !== null) {
@@ -394,6 +524,7 @@ export class TiCloudStorageReplay {
394
524
  return this.currentTime;
395
525
  }
396
526
  play(startTimeMs: number, endTimeMs: number, initialTimeMs?: number): number {
527
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
397
528
  if (
398
529
  !validTime(startTimeMs) ||
399
530
  !validTime(endTimeMs) ||
@@ -421,6 +552,7 @@ export class TiCloudStorageReplay {
421
552
  return code;
422
553
  }
423
554
  seek(timeMs: number): number {
555
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
424
556
  if (!validTime(timeMs)) return TI_CLOUD_STORAGE_ERROR_INVALID_ARGUMENT;
425
557
  const identity = this.ensureIdentity();
426
558
  const code = typeof identity === 'number' ? identity : NativeTiRtc.tiCloudStorageReplaySeek(identity.objectId, timeMs);
@@ -456,19 +588,38 @@ export class TiCloudStorageReplay {
456
588
  ? success(new TiCloudStorageRecordingTask(result.taskId, this))
457
589
  : recordingFailure(result.code);
458
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
+ }
459
607
  stop(): number {
608
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
460
609
  const code = this.command(NativeTiRtc.tiCloudStorageReplayStop);
461
610
  if (code === 0) this.currentTime = null;
462
611
  tiCloudStorageLogOutcome('replay_stop', code);
463
612
  return code;
464
613
  }
465
614
  dispose(): number {
615
+ if (this.inCallback) return TI_CLOUD_STORAGE_ERROR_IN_USE;
466
616
  if (this.disposed) return 0;
467
617
  if (this.identity === null) {
468
618
  this.disposed = true;
469
619
  this.onTimeChanged = null;
470
620
  this.onError = null;
471
621
  this.onCompleted = null;
622
+ this.onRecordingGap = null;
472
623
  return 0;
473
624
  }
474
625
  const identity = this.identity;
@@ -481,6 +632,7 @@ export class TiCloudStorageReplay {
481
632
  this.onTimeChanged = null;
482
633
  this.onError = null;
483
634
  this.onCompleted = null;
635
+ this.onRecordingGap = null;
484
636
  }
485
637
  tiCloudStorageLogOutcome('replay_dispose', code);
486
638
  return code;
@@ -505,11 +657,18 @@ export class TiCloudStorageReplay {
505
657
  return this.identity;
506
658
  }
507
659
  private handleEvent(event: NativeTiRtcEvent): void {
508
- if (event.type === 'tiCloudStorageReplayTimeChanged' && event.timestampMs !== undefined) {
509
- this.currentTime = event.timestampMs;
510
- this.onTimeChanged?.(event.timestampMs);
511
- } else if (event.type === 'tiCloudStorageReplayCompleted') this.onCompleted?.();
512
- 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
+ }
513
672
  }
514
673
  }
515
674
 
@@ -736,40 +895,98 @@ export class TiCloudStorageRecordingTask {
736
895
 
737
896
  export class TiCloudStorageExportTask {
738
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
+
739
905
  constructor(
740
906
  private readonly taskId: number,
907
+ generation: number,
741
908
  private readonly owner: TiCloudStorage,
742
- private readonly onProgress?: (progress: number) => void,
909
+ observer?: ((progress: number) => void) | TiCloudStorageExportObserver,
743
910
  ) {
744
- 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);
745
924
  tiCloudStorageLogOutcome('export_terminal', result.code, ` duration_ms=${result.durationMs ?? 0}`);
746
- return result.code === 0 && result.fileId !== null && result.filePath !== null && result.durationMs !== null
747
- ? success(new RecordingFile(result.filePath, result.durationMs, result.fileId))
748
- : 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};
749
937
  });
750
- if (onProgress !== undefined) {
751
- let lastProgress = -1;
752
- const timer = setInterval(() => {
753
- const progress = NativeTiRtc.tiCloudStorageExportProgress(taskId);
754
- if (progress !== lastProgress) {
755
- lastProgress = progress;
756
- onProgress(progress);
757
- }
758
- }, 100);
759
- const clear = () => clearInterval(timer);
760
- void this.resultPromise.then(clear, clear);
761
- }
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
+ );
762
943
  }
763
944
  get progress(): number {
764
945
  void this.owner;
765
- 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;
766
962
  }
767
963
  get result(): Promise<Resp<TiCloudStorageRecordingFile>> {
768
964
  return this.resultPromise;
769
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
+ }
770
972
  stop(): Promise<Resp<TiCloudStorageRecordingFile>> {
973
+ if (this.terminal) return this.resultPromise;
771
974
  const code = NativeTiRtc.tiCloudStorageExportStop(this.taskId);
772
975
  tiCloudStorageLogOutcome('export_stop', code);
773
976
  return this.resultPromise;
774
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
+ }
775
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;
@@ -43,7 +43,7 @@ export class TiRtcVideoInput {
43
43
  private currentOutputSize: TiRtcSize | null = null;
44
44
  private currentOutputFps: number | null = null;
45
45
  private options: Required<TiRtcVideoInputOptions> = {
46
- codec: 'h264',
46
+ media: 66,
47
47
  width: 640,
48
48
  height: 480,
49
49
  frameRate: 'fps15',
@@ -92,7 +92,7 @@ export class TiRtcVideoInput {
92
92
  return Promise.resolve(TIRTC_ERROR_INVALID_ARGUMENT);
93
93
  }
94
94
  const normalized = {
95
- codec: options.codec ?? 'h264',
95
+ media: options.media === undefined ? 66 : options.media,
96
96
  width: options.width ?? 640,
97
97
  height: options.height ?? 480,
98
98
  frameRate: options.frameRate ?? 'fps15',
@@ -100,12 +100,16 @@ export class TiRtcVideoInput {
100
100
  cameraFacing: options.cameraFacing ?? 'front',
101
101
  };
102
102
  if (
103
+ !Number.isInteger(normalized.media) ||
104
+ ![65, 66].includes(normalized.media) ||
103
105
  !isPositiveInt32Size(normalized.width) ||
104
106
  !isPositiveInt32Size(normalized.height) ||
105
107
  !Number.isSafeInteger(normalized.bitrateKbps) ||
106
108
  normalized.bitrateKbps < 0 ||
107
109
  normalized.bitrateKbps > 4294967295
108
110
  ) {
111
+ logRtcOutcome('video_input_set_options', TIRTC_ERROR_INVALID_ARGUMENT,
112
+ ` nano_media=${typeof normalized.media === 'number' ? normalized.media : typeof normalized.media}`);
109
113
  return Promise.resolve(TIRTC_ERROR_INVALID_ARGUMENT);
110
114
  }
111
115
  return this.enqueue(async () => {
@@ -114,6 +118,8 @@ export class TiRtcVideoInput {
114
118
  return identity;
115
119
  }
116
120
  const code = await NativeTiRtc.videoInputSetOptions(identity.objectId, normalized);
121
+ logRtcOutcome('video_input_set_options', code,
122
+ ` nano_media=${normalized.media} width=${normalized.width} height=${normalized.height} fps=${['fps10', 'fps15', 'fps30'].includes(normalized.frameRate) ? normalized.frameRate : 'invalid'}`);
117
123
  if (code === TIRTC_ERROR_OK) {
118
124
  this.options = normalized;
119
125
  }
@@ -122,16 +128,20 @@ export class TiRtcVideoInput {
122
128
  }
123
129
 
124
130
  start(): Promise<number> {
125
- return this.logged(
126
- 'video_input_start',
127
- this.enqueue(async () => {
128
- const identity = this.ensureIdentity();
129
- if (typeof identity === 'number') {
130
- return identity;
131
- }
132
- return NativeTiRtc.videoInputStart(identity.objectId);
133
- }),
134
- );
131
+ return this.enqueue(async () => {
132
+ const identity = this.ensureIdentity();
133
+ if (typeof identity === 'number') {
134
+ logRtcOutcome('video_input_start', identity);
135
+ return identity;
136
+ }
137
+ const wasRunning = this.state === 'running';
138
+ const code = await NativeTiRtc.videoInputStart(identity.objectId);
139
+ if (code !== TIRTC_ERROR_OK || !wasRunning) {
140
+ logRtcOutcome('video_input_start', code,
141
+ ` object_id=${identity.objectId} nano_media=${this.options.media} width=${this.options.width} height=${this.options.height} fps=${this.options.frameRate}`);
142
+ }
143
+ return code;
144
+ });
135
145
  }
136
146
 
137
147
  attach(connection: TiRtcConn, streamId: number): Promise<number> {