tirtc-react-native 2.2.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 (104) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +140 -0
  3. package/TiRtcReactNative.podspec +35 -0
  4. package/android/build.gradle +37 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcModule.kt +482 -0
  7. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcPackage.kt +43 -0
  8. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeErrors.kt +32 -0
  9. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventBinder.kt +99 -0
  10. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventSink.kt +168 -0
  11. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeMaps.kt +49 -0
  12. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeMetricsMaps.kt +173 -0
  13. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeModuleSupport.kt +91 -0
  14. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeOptions.kt +127 -0
  15. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeRegistry.kt +71 -0
  16. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcVideoInputPreviewManager.kt +109 -0
  17. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcVideoOutputViewManager.kt +109 -0
  18. package/ios/TiRtcReactNative+Conn.mm +112 -0
  19. package/ios/TiRtcReactNative+Input.mm +196 -0
  20. package/ios/TiRtcReactNative+Output.mm +172 -0
  21. package/ios/TiRtcReactNative+Private.h +28 -0
  22. package/ios/TiRtcReactNative.h +6 -0
  23. package/ios/TiRtcReactNative.mm +189 -0
  24. package/ios/TiRtcReactNativeErrors.h +18 -0
  25. package/ios/TiRtcReactNativeErrors.mm +105 -0
  26. package/ios/TiRtcReactNativeEventBinder.h +19 -0
  27. package/ios/TiRtcReactNativeEventBinder.mm +226 -0
  28. package/ios/TiRtcReactNativeEventSink.h +29 -0
  29. package/ios/TiRtcReactNativeEventSink.mm +125 -0
  30. package/ios/TiRtcReactNativeMaps.h +21 -0
  31. package/ios/TiRtcReactNativeMaps.mm +249 -0
  32. package/ios/TiRtcReactNativeNative.h +8 -0
  33. package/ios/TiRtcReactNativeOptions.h +11 -0
  34. package/ios/TiRtcReactNativeOptions.mm +123 -0
  35. package/ios/TiRtcReactNativeRegistry.h +20 -0
  36. package/ios/TiRtcReactNativeRegistry.mm +119 -0
  37. package/ios/TiRtcVideoInputPreview.h +5 -0
  38. package/ios/TiRtcVideoInputPreview.mm +149 -0
  39. package/ios/TiRtcVideoOutputView.h +5 -0
  40. package/ios/TiRtcVideoOutputView.mm +149 -0
  41. package/lib/module/audio_input.js +177 -0
  42. package/lib/module/audio_input.js.map +1 -0
  43. package/lib/module/audio_output.js +168 -0
  44. package/lib/module/audio_output.js.map +1 -0
  45. package/lib/module/conn.js +174 -0
  46. package/lib/module/conn.js.map +1 -0
  47. package/lib/module/errors.js +63 -0
  48. package/lib/module/errors.js.map +1 -0
  49. package/lib/module/events.js +37 -0
  50. package/lib/module/events.js.map +1 -0
  51. package/lib/module/index.js +9 -0
  52. package/lib/module/index.js.map +1 -0
  53. package/lib/module/logging.js +26 -0
  54. package/lib/module/logging.js.map +1 -0
  55. package/lib/module/native.js +5 -0
  56. package/lib/module/native.js.map +1 -0
  57. package/lib/module/native_object_id.js +47 -0
  58. package/lib/module/native_object_id.js.map +1 -0
  59. package/lib/module/runtime.js +27 -0
  60. package/lib/module/runtime.js.map +1 -0
  61. package/lib/module/specs/NativeTiRtc.js +3 -0
  62. package/lib/module/specs/NativeTiRtc.js.map +1 -0
  63. package/lib/module/specs/TiRtcVideoInputPreviewNativeComponent.js +24 -0
  64. package/lib/module/specs/TiRtcVideoOutputViewNativeComponent.js +24 -0
  65. package/lib/module/types.js +93 -0
  66. package/lib/module/types.js.map +1 -0
  67. package/lib/module/video_input.js +266 -0
  68. package/lib/module/video_input.js.map +1 -0
  69. package/lib/module/video_output.js +224 -0
  70. package/lib/module/video_output.js.map +1 -0
  71. package/lib/typescript/audio_input.d.ts +25 -0
  72. package/lib/typescript/audio_output.d.ts +25 -0
  73. package/lib/typescript/conn.d.ts +28 -0
  74. package/lib/typescript/errors.d.ts +19 -0
  75. package/lib/typescript/events.d.ts +6 -0
  76. package/lib/typescript/index.d.ts +9 -0
  77. package/lib/typescript/logging.d.ts +9 -0
  78. package/lib/typescript/native.d.ts +5 -0
  79. package/lib/typescript/native_object_id.d.ts +12 -0
  80. package/lib/typescript/runtime.d.ts +8 -0
  81. package/lib/typescript/specs/NativeTiRtc.d.ts +223 -0
  82. package/lib/typescript/specs/TiRtcVideoInputPreviewNativeComponent.d.ts +8 -0
  83. package/lib/typescript/specs/TiRtcVideoOutputViewNativeComponent.d.ts +8 -0
  84. package/lib/typescript/types.d.ts +281 -0
  85. package/lib/typescript/video_input.d.ts +50 -0
  86. package/lib/typescript/video_output.d.ts +46 -0
  87. package/package.json +80 -0
  88. package/react-native.config.js +10 -0
  89. package/src/audio_input.ts +205 -0
  90. package/src/audio_output.ts +202 -0
  91. package/src/conn.ts +222 -0
  92. package/src/errors.ts +81 -0
  93. package/src/events.ts +44 -0
  94. package/src/index.ts +61 -0
  95. package/src/logging.ts +33 -0
  96. package/src/native.ts +6 -0
  97. package/src/native_object_id.ts +63 -0
  98. package/src/runtime.ts +32 -0
  99. package/src/specs/NativeTiRtc.ts +256 -0
  100. package/src/specs/TiRtcVideoInputPreviewNativeComponent.ts +14 -0
  101. package/src/specs/TiRtcVideoOutputViewNativeComponent.ts +12 -0
  102. package/src/types.ts +340 -0
  103. package/src/video_input.ts +314 -0
  104. package/src/video_output.ts +273 -0
package/src/types.ts ADDED
@@ -0,0 +1,340 @@
1
+ import type {ViewProps} from 'react-native';
2
+ import type {TiRtcConn} from './conn';
3
+ import type {TiRtcVideoInput} from './video_input';
4
+ import type {TiRtcVideoOutput} from './video_output';
5
+
6
+ export type TiRtcSize = Readonly<{
7
+ width: number;
8
+ height: number;
9
+ }>;
10
+
11
+ export const TiRtcOutputBufferStrategy = {
12
+ automatic: 'automatic',
13
+ noBuffer: 'noBuffer',
14
+ } as const;
15
+ export type TiRtcOutputBufferStrategy =
16
+ (typeof TiRtcOutputBufferStrategy)[keyof typeof TiRtcOutputBufferStrategy];
17
+
18
+ export const TiRtcConnState = {
19
+ idle: 'idle',
20
+ connecting: 'connecting',
21
+ connected: 'connected',
22
+ disconnected: 'disconnected',
23
+ } as const;
24
+ export type TiRtcConnState = (typeof TiRtcConnState)[keyof typeof TiRtcConnState];
25
+
26
+ export const TiRtcInputState = {
27
+ idle: 'idle',
28
+ running: 'running',
29
+ stopped: 'stopped',
30
+ failed: 'failed',
31
+ } as const;
32
+ export type TiRtcInputState = (typeof TiRtcInputState)[keyof typeof TiRtcInputState];
33
+
34
+ export const TiRtcAudioCodec = {
35
+ g711a: 'g711a',
36
+ aac: 'aac',
37
+ pcm: 'pcm',
38
+ opus: 'opus',
39
+ amr: 'amr',
40
+ } as const;
41
+ export type TiRtcAudioCodec = (typeof TiRtcAudioCodec)[keyof typeof TiRtcAudioCodec];
42
+
43
+ export const TiRtcAudioSampleRate = {
44
+ rate8k: 'rate8k',
45
+ rate16k: 'rate16k',
46
+ } as const;
47
+ export type TiRtcAudioSampleRate =
48
+ (typeof TiRtcAudioSampleRate)[keyof typeof TiRtcAudioSampleRate];
49
+
50
+ export const TiRtcAudioChannelCount = {
51
+ mono: 'mono',
52
+ } as const;
53
+ export type TiRtcAudioChannelCount =
54
+ (typeof TiRtcAudioChannelCount)[keyof typeof TiRtcAudioChannelCount];
55
+
56
+ export const TiRtcAudioAecMode = {
57
+ disabled: 'disabled',
58
+ enabled: 'enabled',
59
+ } as const;
60
+ export type TiRtcAudioAecMode = (typeof TiRtcAudioAecMode)[keyof typeof TiRtcAudioAecMode];
61
+
62
+ export const TiRtcAudioAgcLevel = {
63
+ disabled: 'disabled',
64
+ low: 'low',
65
+ medium: 'medium',
66
+ high: 'high',
67
+ } as const;
68
+ export type TiRtcAudioAgcLevel =
69
+ (typeof TiRtcAudioAgcLevel)[keyof typeof TiRtcAudioAgcLevel];
70
+
71
+ export const TiRtcAudioAnsLevel = {
72
+ disabled: 'disabled',
73
+ low: 'low',
74
+ medium: 'medium',
75
+ high: 'high',
76
+ } as const;
77
+ export type TiRtcAudioAnsLevel =
78
+ (typeof TiRtcAudioAnsLevel)[keyof typeof TiRtcAudioAnsLevel];
79
+
80
+ export const TiRtcVideoCodec = {
81
+ h264: 'h264',
82
+ h265: 'h265',
83
+ mjpeg: 'mjpeg',
84
+ } as const;
85
+ export type TiRtcVideoCodec = (typeof TiRtcVideoCodec)[keyof typeof TiRtcVideoCodec];
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
+ export const TiRtcVideoFrameRate = {
95
+ fps10: 'fps10',
96
+ fps15: 'fps15',
97
+ fps30: 'fps30',
98
+ } as const;
99
+ export type TiRtcVideoFrameRate =
100
+ (typeof TiRtcVideoFrameRate)[keyof typeof TiRtcVideoFrameRate];
101
+
102
+ export const TiRtcCameraFacing = {
103
+ front: 'front',
104
+ back: 'back',
105
+ } as const;
106
+ export type TiRtcCameraFacing = (typeof TiRtcCameraFacing)[keyof typeof TiRtcCameraFacing];
107
+
108
+ export const TiRtcVideoDecoderPreference = {
109
+ auto: 'auto',
110
+ software: 'software',
111
+ hardware: 'hardware',
112
+ } as const;
113
+ export type TiRtcVideoDecoderPreference =
114
+ (typeof TiRtcVideoDecoderPreference)[keyof typeof TiRtcVideoDecoderPreference];
115
+
116
+ export const TiRtcVideoDecoderBackend = {
117
+ unknown: 'unknown',
118
+ software: 'software',
119
+ hardware: 'hardware',
120
+ } as const;
121
+ export type TiRtcVideoDecoderBackend =
122
+ (typeof TiRtcVideoDecoderBackend)[keyof typeof TiRtcVideoDecoderBackend];
123
+
124
+ export const TiRtcVideoBitstreamFormat = {
125
+ unknown: 'unknown',
126
+ h264AnnexB: 'h264AnnexB',
127
+ h265AnnexB: 'h265AnnexB',
128
+ mjpegJfif: 'mjpegJfif',
129
+ } as const;
130
+ export type TiRtcVideoBitstreamFormat =
131
+ (typeof TiRtcVideoBitstreamFormat)[keyof typeof TiRtcVideoBitstreamFormat];
132
+
133
+ export const TiRtcAudioOutputState = {
134
+ idle: 'idle',
135
+ buffering: 'buffering',
136
+ playing: 'playing',
137
+ failed: 'failed',
138
+ } as const;
139
+ export type TiRtcAudioOutputState =
140
+ (typeof TiRtcAudioOutputState)[keyof typeof TiRtcAudioOutputState];
141
+
142
+ export const TiRtcVideoOutputState = {
143
+ idle: 'idle',
144
+ buffering: 'buffering',
145
+ rendering: 'rendering',
146
+ failed: 'failed',
147
+ } as const;
148
+ export type TiRtcVideoOutputState =
149
+ (typeof TiRtcVideoOutputState)[keyof typeof TiRtcVideoOutputState];
150
+
151
+ export type TiRtcInitOptions = Readonly<{
152
+ appId?: string;
153
+ endpoint?: string;
154
+ consoleLogEnabled?: boolean;
155
+ }>;
156
+
157
+ export type TiRtcAudioOutputOptions = Readonly<{
158
+ agcLevel?: TiRtcAudioAgcLevel;
159
+ ansLevel?: TiRtcAudioAnsLevel;
160
+ bufferStrategy?: TiRtcOutputBufferStrategy;
161
+ maxBufferWatermarkMs?: number;
162
+ }>;
163
+
164
+ export type TiRtcVideoOutputOptions = Readonly<{
165
+ decoderPreference?: TiRtcVideoDecoderPreference;
166
+ bufferStrategy?: TiRtcOutputBufferStrategy;
167
+ maxBufferWatermarkMs?: number;
168
+ }>;
169
+
170
+ /**
171
+ * Props for the public remote video Fabric surface.
172
+ *
173
+ * Pass the owning `TiRtcVideoOutput` instance, not a native id. The component
174
+ * renders an inert React Native `View` until the output has a live native
175
+ * identity, and returns to that placeholder after the owner is disposed.
176
+ */
177
+ export type TiRtcVideoOutputViewProps = ViewProps &
178
+ Readonly<{
179
+ /** Video output owner whose attached stream should render in this surface. */
180
+ output: TiRtcVideoOutput;
181
+ /** Surface scaling mode. Defaults to `contain`. */
182
+ resizeMode?: 'contain' | 'cover' | 'stretch';
183
+ }>;
184
+
185
+ export type TiRtcAudioInputOptions = Readonly<{
186
+ codec?: TiRtcAudioCodec;
187
+ sampleRate?: TiRtcAudioSampleRate;
188
+ channels?: TiRtcAudioChannelCount;
189
+ aecMode?: TiRtcAudioAecMode;
190
+ agcLevel?: TiRtcAudioAgcLevel;
191
+ ansLevel?: TiRtcAudioAnsLevel;
192
+ }>;
193
+
194
+ export type TiRtcVideoInputOptions = Readonly<{
195
+ codec?: TiRtcVideoEncoderCodec;
196
+ width?: number;
197
+ height?: number;
198
+ frameRate?: TiRtcVideoFrameRate;
199
+ bitrateKbps?: number;
200
+ cameraFacing?: TiRtcCameraFacing;
201
+ }>;
202
+
203
+ /**
204
+ * Props for the public local camera preview Fabric surface.
205
+ *
206
+ * Pass the owning `TiRtcVideoInput` instance, not a native id. The component
207
+ * renders an inert React Native `View` until the input has a live native
208
+ * identity, and returns to that placeholder after the owner is disposed.
209
+ */
210
+ export type TiRtcVideoInputPreviewProps = ViewProps &
211
+ Readonly<{
212
+ /** Video input owner whose local capture preview should render here. */
213
+ input: TiRtcVideoInput;
214
+ /** Surface scaling mode. Defaults to `contain`. */
215
+ resizeMode?: 'contain' | 'cover' | 'stretch';
216
+ }>;
217
+
218
+ export type TiRtcOnConnStateChanged = (state: TiRtcConnState, errorCode: number) => void;
219
+ export type TiRtcOnConnCommand = (commandId: number, data: Uint8Array) => void;
220
+ export type TiRtcOnConnStreamMessage = (
221
+ streamId: number,
222
+ timestampMs: number,
223
+ data: Uint8Array,
224
+ ) => void;
225
+ export type TiRtcOnAudioOutputStateChanged = (state: TiRtcAudioOutputState) => void;
226
+ export type TiRtcOnAudioOutputError = (code: number) => void;
227
+ export type TiRtcOnVideoOutputStateChanged = (state: TiRtcVideoOutputState) => void;
228
+ export type TiRtcOnVideoOutputRenderSizeChanged = (size: TiRtcSize) => void;
229
+ export type TiRtcOnVideoOutputError = (code: number) => void;
230
+ export type TiRtcOnInputStateChanged = (state: TiRtcInputState) => void;
231
+ export type TiRtcOnInputError = (code: number, message: string | null) => void;
232
+ export type TiRtcOnVideoInputActualConfigChanged = (size: TiRtcSize, fps: number) => void;
233
+
234
+ export type TiRtcConnMetricsResult = Readonly<{
235
+ code: number;
236
+ snapshot: TiRtcConnMetricsSnapshot | null;
237
+ }>;
238
+
239
+ export type TiRtcConnMetricsSnapshot = Readonly<{
240
+ hasConnectStart: boolean;
241
+ hasConnected: boolean;
242
+ connectStartMonotonicMs: number;
243
+ connectedMonotonicMs: number;
244
+ isReady: boolean;
245
+ connectDurationMs: number | null;
246
+ }>;
247
+
248
+ export type TiRtcOutputStartupMetrics = Readonly<{
249
+ hasConnectStart: boolean;
250
+ hasConnected: boolean;
251
+ hasFirstPacket: boolean;
252
+ hasFirstOutput: boolean;
253
+ connectStartMonotonicMs: number;
254
+ connectedMonotonicMs: number;
255
+ firstPacketMonotonicMs: number;
256
+ firstOutputMonotonicMs: number;
257
+ firstPacketAfterConnectedMs: number;
258
+ firstOutputAfterConnectedMs: number;
259
+ timeToFirstOutputMs: number;
260
+ }>;
261
+
262
+ export type TiRtcOutputStutterMetrics = Readonly<{
263
+ stutterThresholdMs: number;
264
+ outputDurationMs: number;
265
+ stutterTotalMs: number;
266
+ stutterCount: number;
267
+ stutterPeakMs: number;
268
+ stutterAverageMs: number;
269
+ stutterRatePercent: number;
270
+ }>;
271
+
272
+ export type TiRtcAudioOutputMetricsResult = Readonly<{
273
+ code: number;
274
+ snapshot: TiRtcAudioOutputMetricsSnapshot | null;
275
+ }>;
276
+
277
+ export type TiRtcAudioOutputMetricsSnapshot = Readonly<{
278
+ startup: TiRtcOutputStartupMetrics;
279
+ stutter: TiRtcOutputStutterMetrics;
280
+ estimatedOutputLatencyMs: number;
281
+ audioCodec: TiRtcAudioCodec | null;
282
+ audioSampleRateHz: number;
283
+ audioChannels: number;
284
+ audioInputBitrateKbps: number;
285
+ audioInputPacketRate: number;
286
+ audioRenderCallbackRate: number;
287
+ statsRefreshIntervalMs: number;
288
+ statsUpdatedAtMonotonicMs: number;
289
+ }>;
290
+
291
+ export type TiRtcVideoOutputMetricsResult = Readonly<{
292
+ code: number;
293
+ snapshot: TiRtcVideoOutputMetricsSnapshot | null;
294
+ }>;
295
+
296
+ export type TiRtcVideoOutputMetricsSnapshot = Readonly<{
297
+ startup: TiRtcOutputStartupMetrics;
298
+ stutter: TiRtcOutputStutterMetrics;
299
+ estimatedOutputLatencyMs: number;
300
+ videoCodec: TiRtcVideoCodec | null;
301
+ videoWidth: number;
302
+ videoHeight: number;
303
+ decoderBackend: TiRtcVideoDecoderBackend;
304
+ videoInputBitrateKbps: number;
305
+ videoInputFps: number;
306
+ videoDecodedFps: number;
307
+ videoRenderFps: number;
308
+ statsRefreshIntervalMs: number;
309
+ statsUpdatedAtMonotonicMs: number;
310
+ }>;
311
+
312
+ export type TiRtcAudioOutputDebugSnapshotResult = Readonly<{
313
+ code: number;
314
+ snapshot: TiRtcAudioOutputDebugSnapshot | null;
315
+ }>;
316
+
317
+ export type TiRtcAudioOutputDebugSnapshot = Readonly<{
318
+ codec: TiRtcAudioCodec | null;
319
+ sampleRateHz: number;
320
+ channels: number;
321
+ }>;
322
+
323
+ export type TiRtcVideoOutputDebugSnapshotResult = Readonly<{
324
+ code: number;
325
+ snapshot: TiRtcVideoOutputDebugSnapshot | null;
326
+ }>;
327
+
328
+ export type TiRtcVideoOutputDebugSnapshot = Readonly<{
329
+ codec: TiRtcVideoCodec | null;
330
+ bitstreamFormat: TiRtcVideoBitstreamFormat;
331
+ width: number;
332
+ height: number;
333
+ decoderPreference: TiRtcVideoDecoderPreference;
334
+ resolvedDecoderBackend: TiRtcVideoDecoderBackend;
335
+ }>;
336
+
337
+ export type TiRtcLoggingUploadResult = Readonly<{
338
+ code: number;
339
+ logId: string | null;
340
+ }>;
@@ -0,0 +1,314 @@
1
+ import * as React from 'react';
2
+ import {View} from 'react-native';
3
+
4
+ import {
5
+ isPlainObject,
6
+ isPositiveInt32Size,
7
+ isUInt8,
8
+ normalizeCreateResult,
9
+ returnReleasedIfDisposed,
10
+ TIRTC_ERROR_INVALID_ARGUMENT,
11
+ TIRTC_ERROR_OK,
12
+ TIRTC_HOST_ERROR_OBJECT_RELEASED,
13
+ } from './errors';
14
+ import {registerNativeEventHandler, unregisterNativeEventHandler} from './events';
15
+ import {
16
+ clearNativeIdentity,
17
+ readNativeIdentity,
18
+ setNativeIdentity,
19
+ subscribeNativeIdentity,
20
+ type NativeIdentity,
21
+ } from './native_object_id';
22
+ import {NativeTiRtc, NativeTiRtcVideoInputPreview, type NativeTiRtcEvent} from './native';
23
+ import {TiRtcConn} from './conn';
24
+ import type {
25
+ TiRtcInputState,
26
+ TiRtcOnInputError,
27
+ TiRtcOnInputStateChanged,
28
+ TiRtcOnVideoInputActualConfigChanged,
29
+ TiRtcSize,
30
+ TiRtcVideoInputOptions,
31
+ TiRtcVideoInputPreviewProps,
32
+ } from './types';
33
+
34
+ type VideoInputPreviewPropsWithoutOwner = Omit<TiRtcVideoInputPreviewProps, 'input'>;
35
+
36
+ export class TiRtcVideoInput {
37
+ private identity: NativeIdentity | null = null;
38
+ private disposed = false;
39
+ private createCode: number = TIRTC_ERROR_OK;
40
+ private queue: Promise<unknown> = Promise.resolve();
41
+ private currentState: TiRtcInputState = 'idle';
42
+ private currentOutputSize: TiRtcSize | null = null;
43
+ private currentOutputFps: number | null = null;
44
+ private options: Required<TiRtcVideoInputOptions> = {
45
+ codec: 'h264',
46
+ width: 640,
47
+ height: 480,
48
+ frameRate: 'fps15',
49
+ bitrateKbps: 0,
50
+ cameraFacing: 'front',
51
+ };
52
+
53
+ onStateChanged: TiRtcOnInputStateChanged | null = null;
54
+ onActualConfigChanged: TiRtcOnVideoInputActualConfigChanged | null = null;
55
+ onError: TiRtcOnInputError | null = null;
56
+
57
+ constructor() {
58
+ const result = normalizeCreateResult(NativeTiRtc.createVideoInput());
59
+ this.createCode = result.code;
60
+ if (result.code === TIRTC_ERROR_OK) {
61
+ this.adoptIdentity({objectId: result.objectId, generation: result.generation});
62
+ }
63
+ }
64
+
65
+ get state(): TiRtcInputState {
66
+ if (this.identity !== null && !this.disposed) {
67
+ const result = NativeTiRtc.videoInputGetState(this.identity.objectId);
68
+ if (result.state !== null) {
69
+ this.currentState = result.state as TiRtcInputState;
70
+ }
71
+ }
72
+ return this.currentState;
73
+ }
74
+
75
+ get outputSize(): TiRtcSize | null {
76
+ this.refreshActualConfig();
77
+ return this.currentOutputSize;
78
+ }
79
+
80
+ get outputFps(): number | null {
81
+ this.refreshActualConfig();
82
+ return this.currentOutputFps;
83
+ }
84
+
85
+ setOptions(options: TiRtcVideoInputOptions): Promise<number> {
86
+ if (!isPlainObject(options)) {
87
+ return Promise.resolve(TIRTC_ERROR_INVALID_ARGUMENT);
88
+ }
89
+ const normalized = {
90
+ codec: options.codec ?? 'h264',
91
+ width: options.width ?? 640,
92
+ height: options.height ?? 480,
93
+ frameRate: options.frameRate ?? 'fps15',
94
+ bitrateKbps: options.bitrateKbps ?? 0,
95
+ cameraFacing: options.cameraFacing ?? 'front',
96
+ };
97
+ if (!isPositiveInt32Size(normalized.width)
98
+ || !isPositiveInt32Size(normalized.height)
99
+ || !Number.isSafeInteger(normalized.bitrateKbps)
100
+ || normalized.bitrateKbps < 0
101
+ || normalized.bitrateKbps > 4294967295) {
102
+ return Promise.resolve(TIRTC_ERROR_INVALID_ARGUMENT);
103
+ }
104
+ return this.enqueue(async () => {
105
+ const identity = this.ensureIdentity();
106
+ if (typeof identity === 'number') {
107
+ return identity;
108
+ }
109
+ const code = await NativeTiRtc.videoInputSetOptions(identity.objectId, normalized);
110
+ if (code === TIRTC_ERROR_OK) {
111
+ this.options = normalized;
112
+ }
113
+ return code;
114
+ });
115
+ }
116
+
117
+ start(): Promise<number> {
118
+ return this.enqueue(async () => {
119
+ const identity = this.ensureIdentity();
120
+ if (typeof identity === 'number') {
121
+ return identity;
122
+ }
123
+ return NativeTiRtc.videoInputStart(identity.objectId);
124
+ });
125
+ }
126
+
127
+ attach(connection: TiRtcConn, streamId: number): Promise<number> {
128
+ const connIdentity = readNativeIdentity(connection);
129
+ if (!isUInt8(streamId)) {
130
+ return Promise.resolve(TIRTC_ERROR_INVALID_ARGUMENT);
131
+ }
132
+ if (connIdentity === null) {
133
+ return Promise.resolve(TIRTC_ERROR_INVALID_ARGUMENT);
134
+ }
135
+ return this.enqueue(async () => {
136
+ const identity = this.ensureIdentity();
137
+ if (typeof identity === 'number') {
138
+ return identity;
139
+ }
140
+ return NativeTiRtc.videoInputAttach(identity.objectId, connIdentity.objectId, streamId);
141
+ });
142
+ }
143
+
144
+ detach(connection: TiRtcConn): Promise<number> {
145
+ const connIdentity = readNativeIdentity(connection);
146
+ if (connIdentity === null) {
147
+ return Promise.resolve(TIRTC_ERROR_INVALID_ARGUMENT);
148
+ }
149
+ return this.enqueue(async () => {
150
+ if (this.identity === null) {
151
+ return TIRTC_ERROR_OK;
152
+ }
153
+ return NativeTiRtc.videoInputDetach(this.identity.objectId, connIdentity.objectId);
154
+ });
155
+ }
156
+
157
+ stop(): Promise<number> {
158
+ return this.enqueue(async () => {
159
+ if (this.identity === null) {
160
+ return TIRTC_ERROR_OK;
161
+ }
162
+ return NativeTiRtc.videoInputStop(this.identity.objectId);
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Creates a React element bound to this input owner.
168
+ *
169
+ * The returned element renders a placeholder until this input has a live
170
+ * native identity. Configure and start the input before expecting camera
171
+ * frames, and dispose the input when capture is no longer needed.
172
+ */
173
+ preview(props: VideoInputPreviewPropsWithoutOwner = {}): React.ReactElement {
174
+ return React.createElement(TiRtcVideoInputPreview, {
175
+ ...props,
176
+ input: this,
177
+ });
178
+ }
179
+
180
+ dispose(): Promise<number> {
181
+ if (this.disposed) {
182
+ return Promise.resolve(TIRTC_ERROR_OK);
183
+ }
184
+ return this.enqueue(async () => {
185
+ if (this.identity === null) {
186
+ this.disposed = true;
187
+ this.clearCallbacks();
188
+ this.currentState = 'stopped';
189
+ this.currentOutputSize = null;
190
+ this.currentOutputFps = null;
191
+ return TIRTC_ERROR_OK;
192
+ }
193
+ const identity = this.identity;
194
+ const code = await NativeTiRtc.videoInputDispose(identity.objectId);
195
+ if (code === TIRTC_ERROR_OK) {
196
+ this.disposed = true;
197
+ this.identity = null;
198
+ unregisterNativeEventHandler(identity);
199
+ clearNativeIdentity(this);
200
+ this.clearCallbacks();
201
+ this.currentState = 'stopped';
202
+ this.currentOutputSize = null;
203
+ this.currentOutputFps = null;
204
+ }
205
+ return code;
206
+ }, true);
207
+ }
208
+
209
+ private enqueue(operation: () => Promise<number>, allowDisposed = false): Promise<number> {
210
+ const released = returnReleasedIfDisposed(this.disposed);
211
+ if (released !== null && !allowDisposed) {
212
+ return Promise.resolve(released);
213
+ }
214
+ const next = this.queue.then(async () => {
215
+ if (this.disposed && !allowDisposed) {
216
+ return TIRTC_HOST_ERROR_OBJECT_RELEASED;
217
+ }
218
+ return operation();
219
+ });
220
+ this.queue = next.catch(() => undefined);
221
+ return next;
222
+ }
223
+
224
+ private ensureIdentity(): NativeIdentity | number {
225
+ const released = returnReleasedIfDisposed(this.disposed);
226
+ if (released !== null) {
227
+ return released;
228
+ }
229
+ if (this.identity !== null) {
230
+ return this.identity;
231
+ }
232
+ return this.createCode;
233
+ }
234
+
235
+ private adoptIdentity(identity: NativeIdentity): void {
236
+ this.identity = identity;
237
+ setNativeIdentity(this, identity);
238
+ registerNativeEventHandler(identity, (event) => this.handleEvent(event));
239
+ void NativeTiRtc.videoInputSetOptions(identity.objectId, this.options);
240
+ }
241
+
242
+ private refreshActualConfig(): void {
243
+ if (this.disposed || this.identity === null) {
244
+ return;
245
+ }
246
+ const result = NativeTiRtc.videoInputGetActualConfig(this.identity.objectId);
247
+ this.currentOutputSize = result.size;
248
+ this.currentOutputFps = result.fps;
249
+ }
250
+
251
+ private clearCallbacks(): void {
252
+ this.onStateChanged = null;
253
+ this.onActualConfigChanged = null;
254
+ this.onError = null;
255
+ }
256
+
257
+ private handleEvent(event: NativeTiRtcEvent): void {
258
+ if (this.disposed) {
259
+ return;
260
+ }
261
+ switch (event.type) {
262
+ case 'videoInputStateChanged':
263
+ if (event.state !== undefined) {
264
+ this.currentState = event.state as TiRtcInputState;
265
+ this.onStateChanged?.(this.currentState);
266
+ }
267
+ break;
268
+ case 'videoInputActualConfigChanged':
269
+ if (event.size !== undefined && event.fps !== undefined) {
270
+ this.currentOutputSize = event.size;
271
+ this.currentOutputFps = event.fps;
272
+ this.onActualConfigChanged?.(event.size, event.fps);
273
+ }
274
+ break;
275
+ case 'videoInputError':
276
+ this.onError?.(event.code ?? TIRTC_ERROR_INVALID_ARGUMENT, event.message ?? null);
277
+ break;
278
+ default:
279
+ break;
280
+ }
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Renders the local preview surface for a `TiRtcVideoInput`.
286
+ *
287
+ * The component observes the owner identity and never accepts raw native ids.
288
+ * If the owner is not created, has been disposed, or is not a `TiRtcVideoInput`,
289
+ * it renders a regular `View` with the remaining props.
290
+ */
291
+ export function TiRtcVideoInputPreview(props: TiRtcVideoInputPreviewProps): React.ReactElement {
292
+ const {input, resizeMode = 'contain', ...viewProps} = props as TiRtcVideoInputPreviewProps;
293
+ const identity = useOwnerIdentity(input instanceof TiRtcVideoInput ? input : null);
294
+ if (identity === null) {
295
+ return React.createElement(View, viewProps);
296
+ }
297
+ return React.createElement(NativeTiRtcVideoInputPreview, {
298
+ ...viewProps,
299
+ nativeObjectId: identity.objectId,
300
+ generation: identity.generation,
301
+ resizeMode,
302
+ });
303
+ }
304
+
305
+ function useOwnerIdentity(owner: object | null): NativeIdentity | null {
306
+ const [, forceUpdate] = React.useReducer((value: number) => value + 1, 0);
307
+ React.useEffect(() => {
308
+ if (owner === null) {
309
+ return undefined;
310
+ }
311
+ return subscribeNativeIdentity(owner, forceUpdate);
312
+ }, [owner]);
313
+ return owner === null ? null : readNativeIdentity(owner);
314
+ }