tirtc-react-native 2.3.1 → 2.4.0-alpha.4
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.
- package/README.md +23 -7
- package/TiRtcReactNative.podspec +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/AndroidManifest.xml +2 -1
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcModule.kt +622 -28
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeCleanup.kt +115 -0
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventBinder.kt +32 -8
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventSink.kt +16 -0
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeMaps.kt +62 -0
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeModuleSupport.kt +6 -6
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeRegistry.kt +27 -0
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeStorageSupport.kt +64 -0
- package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcVideoOutputViewManager.kt +17 -2
- package/ios/TiRtcReactNative+Conn.mm +173 -2
- package/ios/TiRtcReactNative+Input.mm +18 -4
- package/ios/TiRtcReactNative+Output.mm +41 -4
- package/ios/TiRtcReactNative+Private.h +4 -0
- package/ios/TiRtcReactNative+Storage.mm +513 -0
- package/ios/TiRtcReactNative.h +2 -1
- package/ios/TiRtcReactNative.mm +109 -1
- package/ios/TiRtcReactNativeEventBinder.h +8 -0
- package/ios/TiRtcReactNativeEventBinder.mm +102 -0
- package/ios/TiRtcReactNativeEventSink.h +3 -0
- package/ios/TiRtcReactNativeEventSink.mm +16 -0
- package/ios/TiRtcReactNativeRegistry.h +5 -0
- package/ios/TiRtcReactNativeRegistry.mm +43 -0
- package/ios/TiRtcVideoOutputView.mm +11 -0
- package/lib/module/audio_input.js +33 -17
- package/lib/module/audio_input.js.map +1 -1
- package/lib/module/audio_output.js +27 -10
- package/lib/module/audio_output.js.map +1 -1
- package/lib/module/conn.js +31 -7
- package/lib/module/conn.js.map +1 -1
- package/lib/module/errors.js +4 -3
- package/lib/module/errors.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/logging.js +9 -1
- package/lib/module/logging.js.map +1 -1
- package/lib/module/logging_internal.js +27 -0
- package/lib/module/logging_internal.js.map +1 -0
- package/lib/module/recording.js +210 -0
- package/lib/module/recording.js.map +1 -0
- package/lib/module/runtime.js +13 -5
- package/lib/module/runtime.js.map +1 -1
- package/lib/module/specs/NativeTiRtc.js.map +1 -1
- package/lib/module/storage.js +706 -0
- package/lib/module/storage.js.map +1 -0
- package/lib/module/video_input.js +40 -22
- package/lib/module/video_input.js.map +1 -1
- package/lib/module/video_output.js +51 -10
- package/lib/module/video_output.js.map +1 -1
- package/lib/typescript/audio_input.d.ts +1 -0
- package/lib/typescript/conn.d.ts +2 -0
- package/lib/typescript/errors.d.ts +3 -2
- package/lib/typescript/index.d.ts +4 -0
- package/lib/typescript/logging_internal.d.ts +2 -0
- package/lib/typescript/recording.d.ts +53 -0
- package/lib/typescript/runtime.d.ts +2 -0
- package/lib/typescript/specs/NativeTiRtc.d.ts +88 -0
- package/lib/typescript/storage.d.ts +204 -0
- package/lib/typescript/video_input.d.ts +1 -0
- package/lib/typescript/video_output.d.ts +3 -0
- package/package.json +1 -1
- package/src/audio_input.ts +85 -59
- package/src/audio_output.ts +30 -17
- package/src/conn.ts +46 -28
- package/src/errors.ts +4 -3
- package/src/index.ts +54 -0
- package/src/logging.ts +7 -1
- package/src/logging_internal.ts +23 -0
- package/src/recording.ts +271 -0
- package/src/runtime.ts +14 -5
- package/src/specs/NativeTiRtc.ts +123 -0
- package/src/storage.ts +769 -0
- package/src/video_input.ts +97 -62
- package/src/video_output.ts +54 -16
package/src/audio_output.ts
CHANGED
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
isPositiveInt32,
|
|
4
4
|
isUInt8,
|
|
5
5
|
normalizeCreateResult,
|
|
6
|
-
|
|
6
|
+
returnNotInitializedIfDisposed,
|
|
7
7
|
TIRTC_ERROR_INVALID_ARGUMENT,
|
|
8
8
|
TIRTC_ERROR_OK,
|
|
9
9
|
} from './errors';
|
|
10
10
|
import {registerNativeEventHandler, unregisterNativeEventHandler} from './events';
|
|
11
11
|
import {readNativeIdentity, clearNativeIdentity, setNativeIdentity, type NativeIdentity} from './native_object_id';
|
|
12
12
|
import {NativeTiRtc, type NativeTiRtcEvent} from './native';
|
|
13
|
+
import {logRtcOutcome, logRtcState} from './logging_internal';
|
|
13
14
|
import {TiRtcConn} from './conn';
|
|
14
15
|
import type {
|
|
15
16
|
TiRtcAudioOutputDebugSnapshotResult,
|
|
@@ -27,11 +28,11 @@ export class TiRtcAudioOutput {
|
|
|
27
28
|
private currentState: TiRtcAudioOutputState = 'idle';
|
|
28
29
|
private options: Required<Omit<TiRtcAudioOutputOptions, 'maxBufferWatermarkMs'>> &
|
|
29
30
|
Pick<TiRtcAudioOutputOptions, 'maxBufferWatermarkMs'> = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
agcLevel: 'disabled',
|
|
32
|
+
ansLevel: 'disabled',
|
|
33
|
+
bufferStrategy: 'automatic',
|
|
34
|
+
maxBufferWatermarkMs: undefined,
|
|
35
|
+
};
|
|
35
36
|
|
|
36
37
|
onStateChanged: TiRtcOnAudioOutputStateChanged | null = null;
|
|
37
38
|
onError: TiRtcOnAudioOutputError | null = null;
|
|
@@ -40,8 +41,12 @@ export class TiRtcAudioOutput {
|
|
|
40
41
|
const result = normalizeCreateResult(NativeTiRtc.createAudioOutput());
|
|
41
42
|
this.createCode = result.code;
|
|
42
43
|
if (result.code === TIRTC_ERROR_OK) {
|
|
43
|
-
this.adoptIdentity({
|
|
44
|
+
this.adoptIdentity({
|
|
45
|
+
objectId: result.objectId,
|
|
46
|
+
generation: result.generation,
|
|
47
|
+
});
|
|
44
48
|
}
|
|
49
|
+
logRtcOutcome('audio_output_create', result.code);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
get state(): TiRtcAudioOutputState {
|
|
@@ -61,10 +66,7 @@ export class TiRtcAudioOutput {
|
|
|
61
66
|
bufferStrategy: options.bufferStrategy ?? 'automatic',
|
|
62
67
|
maxBufferWatermarkMs: options.maxBufferWatermarkMs,
|
|
63
68
|
};
|
|
64
|
-
if (
|
|
65
|
-
normalized.maxBufferWatermarkMs !== undefined
|
|
66
|
-
&& !isPositiveInt32(normalized.maxBufferWatermarkMs)
|
|
67
|
-
) {
|
|
69
|
+
if (normalized.maxBufferWatermarkMs !== undefined && !isPositiveInt32(normalized.maxBufferWatermarkMs)) {
|
|
68
70
|
return TIRTC_ERROR_INVALID_ARGUMENT;
|
|
69
71
|
}
|
|
70
72
|
const identity = this.ensureIdentity();
|
|
@@ -93,15 +95,20 @@ export class TiRtcAudioOutput {
|
|
|
93
95
|
if (typeof identity === 'number') {
|
|
94
96
|
return identity;
|
|
95
97
|
}
|
|
96
|
-
|
|
98
|
+
const code = NativeTiRtc.audioOutputAttach(identity.objectId, connIdentity.objectId, streamId);
|
|
99
|
+
logRtcOutcome('audio_output_attach', code);
|
|
100
|
+
return code;
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
detach(): number {
|
|
100
|
-
const released =
|
|
104
|
+
const released = returnNotInitializedIfDisposed(this.disposed);
|
|
101
105
|
if (released !== null) {
|
|
102
106
|
return released;
|
|
103
107
|
}
|
|
104
|
-
|
|
108
|
+
const code = this.identity === null ? TIRTC_ERROR_OK : NativeTiRtc.audioOutputDetach(this.identity.objectId);
|
|
109
|
+
if (code === TIRTC_ERROR_OK) this.currentState = 'idle';
|
|
110
|
+
logRtcOutcome('audio_output_detach', code);
|
|
111
|
+
return code;
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
setVolume(volumePercent: number): number {
|
|
@@ -159,11 +166,12 @@ export class TiRtcAudioOutput {
|
|
|
159
166
|
this.clearCallbacks();
|
|
160
167
|
this.currentState = 'idle';
|
|
161
168
|
}
|
|
169
|
+
logRtcOutcome('audio_output_dispose', code);
|
|
162
170
|
return code;
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
private ensureIdentity(): NativeIdentity | number {
|
|
166
|
-
const released =
|
|
174
|
+
const released = returnNotInitializedIfDisposed(this.disposed);
|
|
167
175
|
if (released !== null) {
|
|
168
176
|
return released;
|
|
169
177
|
}
|
|
@@ -193,10 +201,15 @@ export class TiRtcAudioOutput {
|
|
|
193
201
|
return;
|
|
194
202
|
}
|
|
195
203
|
if (event.type === 'audioOutputStateChanged' && event.state !== undefined) {
|
|
196
|
-
|
|
204
|
+
const nextState = event.state as TiRtcAudioOutputState;
|
|
205
|
+
const changed = this.currentState !== nextState;
|
|
206
|
+
this.currentState = nextState;
|
|
207
|
+
if (changed) logRtcState('audio_output_state', nextState);
|
|
197
208
|
this.onStateChanged?.(this.currentState);
|
|
198
209
|
} else if (event.type === 'audioOutputError') {
|
|
199
|
-
|
|
210
|
+
const code = event.code ?? TIRTC_ERROR_INVALID_ARGUMENT;
|
|
211
|
+
logRtcOutcome('audio_output', code);
|
|
212
|
+
this.onError?.(code);
|
|
200
213
|
}
|
|
201
214
|
}
|
|
202
215
|
}
|
package/src/conn.ts
CHANGED
|
@@ -3,14 +3,23 @@ import {
|
|
|
3
3
|
isUInt8,
|
|
4
4
|
isUInt32,
|
|
5
5
|
normalizeCreateResult,
|
|
6
|
-
|
|
6
|
+
returnNotInitializedIfDisposed,
|
|
7
7
|
TIRTC_ERROR_INVALID_ARGUMENT,
|
|
8
8
|
TIRTC_ERROR_OK,
|
|
9
9
|
} from './errors';
|
|
10
10
|
import {registerNativeEventHandler, unregisterNativeEventHandler} from './events';
|
|
11
11
|
import {clearNativeIdentity, setNativeIdentity, type NativeIdentity} from './native_object_id';
|
|
12
12
|
import {NativeTiRtc, type NativeTiRtcEvent} from './native';
|
|
13
|
+
import {logRtcOutcome, logRtcState} from './logging_internal';
|
|
13
14
|
import {toNativeBytes} from './errors';
|
|
15
|
+
import {
|
|
16
|
+
recordingFailure,
|
|
17
|
+
startRecording,
|
|
18
|
+
validateStartRecordingOptions,
|
|
19
|
+
type Resp,
|
|
20
|
+
type TiRtcRecordingTask,
|
|
21
|
+
type TiRtcStartRecordingOptions,
|
|
22
|
+
} from './recording';
|
|
14
23
|
import type {
|
|
15
24
|
TiRtcConnMetricsResult,
|
|
16
25
|
TiRtcConnState,
|
|
@@ -29,14 +38,27 @@ export class TiRtcConn {
|
|
|
29
38
|
const result = normalizeCreateResult(NativeTiRtc.createConn());
|
|
30
39
|
this.createCode = result.code;
|
|
31
40
|
if (result.code === TIRTC_ERROR_OK) {
|
|
32
|
-
this.adoptIdentity({
|
|
41
|
+
this.adoptIdentity({
|
|
42
|
+
objectId: result.objectId,
|
|
43
|
+
generation: result.generation,
|
|
44
|
+
});
|
|
33
45
|
}
|
|
46
|
+
logRtcOutcome('connection_create', result.code);
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
onStateChanged: TiRtcOnConnStateChanged | null = null;
|
|
37
50
|
onCommand: TiRtcOnConnCommand | null = null;
|
|
38
51
|
onStreamMessage: TiRtcOnConnStreamMessage | null = null;
|
|
39
52
|
|
|
53
|
+
startRecording(options: TiRtcStartRecordingOptions): Resp<TiRtcRecordingTask> {
|
|
54
|
+
const validationCode = validateStartRecordingOptions(options);
|
|
55
|
+
if (validationCode !== TIRTC_ERROR_OK) {
|
|
56
|
+
return recordingFailure(validationCode);
|
|
57
|
+
}
|
|
58
|
+
const identity = this.ensureIdentity();
|
|
59
|
+
return typeof identity === 'number' ? recordingFailure(identity) : startRecording(identity.objectId, options);
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
get state(): TiRtcConnState {
|
|
41
63
|
if (this.identity !== null && !this.disposed) {
|
|
42
64
|
const result = NativeTiRtc.connGetState(this.identity.objectId);
|
|
@@ -55,15 +77,19 @@ export class TiRtcConn {
|
|
|
55
77
|
if (typeof identity === 'number') {
|
|
56
78
|
return identity;
|
|
57
79
|
}
|
|
58
|
-
|
|
80
|
+
const code = NativeTiRtc.connConnect(identity.objectId, remoteId, token);
|
|
81
|
+
logRtcOutcome('connection_connect', code);
|
|
82
|
+
return code;
|
|
59
83
|
}
|
|
60
84
|
|
|
61
85
|
disconnect(): number {
|
|
62
|
-
const released =
|
|
86
|
+
const released = returnNotInitializedIfDisposed(this.disposed);
|
|
63
87
|
if (released !== null) {
|
|
64
88
|
return released;
|
|
65
89
|
}
|
|
66
|
-
|
|
90
|
+
const code = this.identity === null ? TIRTC_ERROR_OK : NativeTiRtc.connDisconnect(this.identity.objectId);
|
|
91
|
+
logRtcOutcome('connection_disconnect', code);
|
|
92
|
+
return code;
|
|
67
93
|
}
|
|
68
94
|
|
|
69
95
|
sendCommand(commandId: number, data: Uint8Array): number {
|
|
@@ -93,37 +119,27 @@ export class TiRtcConn {
|
|
|
93
119
|
if (typeof identity === 'number') {
|
|
94
120
|
return identity;
|
|
95
121
|
}
|
|
96
|
-
return NativeTiRtc.connSendStreamMessage(
|
|
97
|
-
identity.objectId,
|
|
98
|
-
streamId,
|
|
99
|
-
timestampMs,
|
|
100
|
-
nativeBytes,
|
|
101
|
-
);
|
|
122
|
+
return NativeTiRtc.connSendStreamMessage(identity.objectId, streamId, timestampMs, nativeBytes);
|
|
102
123
|
}
|
|
103
124
|
|
|
104
125
|
subscribeVideo(streamId: number): number {
|
|
105
|
-
return this.callWithStream(streamId, (identity) =>
|
|
106
|
-
NativeTiRtc.connSubscribeVideo(identity.objectId, streamId));
|
|
126
|
+
return this.callWithStream(streamId, (identity) => NativeTiRtc.connSubscribeVideo(identity.objectId, streamId));
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
unsubscribeVideo(streamId: number): number {
|
|
110
|
-
return this.callWithStream(streamId, (identity) =>
|
|
111
|
-
NativeTiRtc.connUnsubscribeVideo(identity.objectId, streamId));
|
|
130
|
+
return this.callWithStream(streamId, (identity) => NativeTiRtc.connUnsubscribeVideo(identity.objectId, streamId));
|
|
112
131
|
}
|
|
113
132
|
|
|
114
133
|
subscribeAudio(streamId: number): number {
|
|
115
|
-
return this.callWithStream(streamId, (identity) =>
|
|
116
|
-
NativeTiRtc.connSubscribeAudio(identity.objectId, streamId));
|
|
134
|
+
return this.callWithStream(streamId, (identity) => NativeTiRtc.connSubscribeAudio(identity.objectId, streamId));
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
unsubscribeAudio(streamId: number): number {
|
|
120
|
-
return this.callWithStream(streamId, (identity) =>
|
|
121
|
-
NativeTiRtc.connUnsubscribeAudio(identity.objectId, streamId));
|
|
138
|
+
return this.callWithStream(streamId, (identity) => NativeTiRtc.connUnsubscribeAudio(identity.objectId, streamId));
|
|
122
139
|
}
|
|
123
140
|
|
|
124
141
|
requestKeyFrame(streamId: number): number {
|
|
125
|
-
return this.callWithStream(streamId, (identity) =>
|
|
126
|
-
NativeTiRtc.connRequestKeyFrame(identity.objectId, streamId));
|
|
142
|
+
return this.callWithStream(streamId, (identity) => NativeTiRtc.connRequestKeyFrame(identity.objectId, streamId));
|
|
127
143
|
}
|
|
128
144
|
|
|
129
145
|
getMetricsSnapshot(): TiRtcConnMetricsResult {
|
|
@@ -153,6 +169,7 @@ export class TiRtcConn {
|
|
|
153
169
|
this.clearCallbacks();
|
|
154
170
|
this.currentState = 'disconnected';
|
|
155
171
|
}
|
|
172
|
+
logRtcOutcome('connection_dispose', code);
|
|
156
173
|
return code;
|
|
157
174
|
}
|
|
158
175
|
|
|
@@ -168,7 +185,7 @@ export class TiRtcConn {
|
|
|
168
185
|
}
|
|
169
186
|
|
|
170
187
|
private ensureIdentity(): NativeIdentity | number {
|
|
171
|
-
const released =
|
|
188
|
+
const released = returnNotInitializedIfDisposed(this.disposed);
|
|
172
189
|
if (released !== null) {
|
|
173
190
|
return released;
|
|
174
191
|
}
|
|
@@ -197,7 +214,12 @@ export class TiRtcConn {
|
|
|
197
214
|
switch (event.type) {
|
|
198
215
|
case 'connStateChanged':
|
|
199
216
|
if (event.state !== undefined && event.errorCode !== undefined) {
|
|
200
|
-
|
|
217
|
+
const nextState = event.state as TiRtcConnState;
|
|
218
|
+
const changed = this.currentState !== nextState;
|
|
219
|
+
this.currentState = nextState;
|
|
220
|
+
if (changed || event.errorCode !== 0) {
|
|
221
|
+
logRtcState('connection_state', nextState, event.errorCode);
|
|
222
|
+
}
|
|
201
223
|
this.onStateChanged?.(this.currentState, event.errorCode);
|
|
202
224
|
}
|
|
203
225
|
break;
|
|
@@ -208,11 +230,7 @@ export class TiRtcConn {
|
|
|
208
230
|
break;
|
|
209
231
|
case 'connStreamMessage':
|
|
210
232
|
if (event.streamId !== undefined && event.timestampMs !== undefined) {
|
|
211
|
-
this.onStreamMessage?.(
|
|
212
|
-
event.streamId,
|
|
213
|
-
event.timestampMs,
|
|
214
|
-
fromNativeBytes(event.data),
|
|
215
|
-
);
|
|
233
|
+
this.onStreamMessage?.(event.streamId, event.timestampMs, fromNativeBytes(event.data));
|
|
216
234
|
}
|
|
217
235
|
break;
|
|
218
236
|
default:
|
package/src/errors.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const TIRTC_ERROR_OK = 0;
|
|
2
2
|
export const TIRTC_ERROR_INVALID_ARGUMENT = 6000;
|
|
3
|
-
export const
|
|
3
|
+
export const TIRTC_ERROR_NOT_INITIALIZED = 6001;
|
|
4
|
+
export const TIRTC_ERROR_IN_USE = 6026;
|
|
4
5
|
|
|
5
6
|
export type NativeCreateResultLike = Readonly<{
|
|
6
7
|
code: number;
|
|
@@ -9,8 +10,8 @@ export type NativeCreateResultLike = Readonly<{
|
|
|
9
10
|
message: string | null;
|
|
10
11
|
}>;
|
|
11
12
|
|
|
12
|
-
export function
|
|
13
|
-
return disposed ?
|
|
13
|
+
export function returnNotInitializedIfDisposed(disposed: boolean): number | null {
|
|
14
|
+
return disposed ? TIRTC_ERROR_NOT_INITIALIZED : null;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function normalizeCreateResult(result: NativeCreateResultLike): NativeCreateResultLike {
|
package/src/index.ts
CHANGED
|
@@ -59,3 +59,57 @@ export {TiRtcVideoOutput, TiRtcVideoOutputView} from './video_output';
|
|
|
59
59
|
export {TiRtcAudioInput} from './audio_input';
|
|
60
60
|
export {TiRtcVideoInput, TiRtcVideoInputPreview} from './video_input';
|
|
61
61
|
export {TiRtcLogging} from './logging';
|
|
62
|
+
export {TiRtcRecordingTask} from './recording';
|
|
63
|
+
export type {
|
|
64
|
+
Resp,
|
|
65
|
+
TiRtcGalleryAsset,
|
|
66
|
+
TiRtcRecordingFile,
|
|
67
|
+
TiRtcSnapshotFile,
|
|
68
|
+
TiRtcStartRecordingOptions,
|
|
69
|
+
} from './recording';
|
|
70
|
+
export {
|
|
71
|
+
TI_CLOUD_STORAGE_ERROR_OK,
|
|
72
|
+
TI_CLOUD_STORAGE_ERROR_INVALID_ARGUMENT,
|
|
73
|
+
TI_CLOUD_STORAGE_ERROR_NOT_INITIALIZED,
|
|
74
|
+
TI_CLOUD_STORAGE_ERROR_TOKEN_EXPIRED,
|
|
75
|
+
TI_CLOUD_STORAGE_ERROR_ALREADY_INITIALIZED,
|
|
76
|
+
TI_CLOUD_STORAGE_ERROR_PERMISSION_DENIED,
|
|
77
|
+
TI_CLOUD_STORAGE_ERROR_IN_USE,
|
|
78
|
+
TI_CLOUD_STORAGE_ERROR_NOT_STARTED,
|
|
79
|
+
TI_CLOUD_STORAGE_ERROR_NOT_BOUND,
|
|
80
|
+
TI_CLOUD_STORAGE_ERROR_NOT_CONFIGURED,
|
|
81
|
+
TI_CLOUD_STORAGE_ERROR_RESOURCE_EXHAUSTED,
|
|
82
|
+
TI_CLOUD_STORAGE_ERROR_FILE_WRITE_FAILED,
|
|
83
|
+
TI_CLOUD_STORAGE_ERROR_UNSUPPORTED_FORMAT,
|
|
84
|
+
TI_CLOUD_STORAGE_ERROR_IO_FAILED,
|
|
85
|
+
TI_CLOUD_STORAGE_ERROR_CANCELLED,
|
|
86
|
+
TI_CLOUD_STORAGE_ERROR_RANGE_TOO_LARGE,
|
|
87
|
+
TI_CLOUD_STORAGE_ERROR_NO_FRAME,
|
|
88
|
+
TI_CLOUD_STORAGE_ERROR_NO_RECORDABLE_MEDIA,
|
|
89
|
+
TI_CLOUD_STORAGE_ERROR_RECORDING_OVERRUN,
|
|
90
|
+
TI_CLOUD_STORAGE_ERROR_RECORDING_UNREADABLE,
|
|
91
|
+
TI_CLOUD_STORAGE_ERROR_UNAVAILABLE,
|
|
92
|
+
TI_CLOUD_STORAGE_ERROR_STOPPED,
|
|
93
|
+
TiCloudStorageReplaySpeed,
|
|
94
|
+
TiCloudStorageAudioOutputState,
|
|
95
|
+
TiCloudStorageVideoOutputState,
|
|
96
|
+
TiCloudStorage,
|
|
97
|
+
TiCloudStorageReplay,
|
|
98
|
+
TiCloudStorageAudioOutput,
|
|
99
|
+
TiCloudStorageVideoOutput,
|
|
100
|
+
TiCloudStorageVideoOutputView,
|
|
101
|
+
TiCloudStorageRecordingTask,
|
|
102
|
+
TiCloudStorageExportTask,
|
|
103
|
+
} from './storage';
|
|
104
|
+
export type {
|
|
105
|
+
TiCloudStorageRecordingRange,
|
|
106
|
+
TiCloudStorageRecordingRangesResult,
|
|
107
|
+
TiCloudStorageRecordingDay,
|
|
108
|
+
TiCloudStorageRecordingDaysResult,
|
|
109
|
+
TiCloudStorageStartRecordingOptions,
|
|
110
|
+
TiCloudStorageExportOptions,
|
|
111
|
+
TiCloudStorageGalleryAsset,
|
|
112
|
+
TiCloudStorageRecordingFile,
|
|
113
|
+
TiCloudStorageSnapshotFile,
|
|
114
|
+
TiCloudStorageVideoOutputViewProps,
|
|
115
|
+
} from './storage';
|
package/src/logging.ts
CHANGED
|
@@ -28,6 +28,12 @@ export class TiRtcLogging {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
static upload(): Promise<TiRtcLoggingUploadResult> {
|
|
31
|
-
|
|
31
|
+
TiRtcLogging.i('TIRTC_REACT_NATIVE', '[log_upload] started');
|
|
32
|
+
return NativeTiRtc.uploadLogs().then((result) => {
|
|
33
|
+
const message = `[log_upload] completed code=${result.code}`;
|
|
34
|
+
if (result.code === 0) TiRtcLogging.i('TIRTC_REACT_NATIVE', message);
|
|
35
|
+
else TiRtcLogging.w('TIRTC_REACT_NATIVE', message);
|
|
36
|
+
return result;
|
|
37
|
+
});
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {TiRtcLogging} from './logging';
|
|
2
|
+
|
|
3
|
+
const rtcLogTag = 'TIRTC_REACT_NATIVE';
|
|
4
|
+
|
|
5
|
+
export function logRtcOutcome(operation: string, code: number, details = ''): void {
|
|
6
|
+
const message = `[${operation}] completed code=${code}${details}`;
|
|
7
|
+
try {
|
|
8
|
+
if (code === 0) TiRtcLogging.i(rtcLogTag, message);
|
|
9
|
+
else TiRtcLogging.w(rtcLogTag, message);
|
|
10
|
+
} catch {
|
|
11
|
+
// Diagnostics must never change the SDK operation result.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function logRtcState(operation: string, state: string, code = 0): void {
|
|
16
|
+
const message = `[${operation}] state=${state} code=${code}`;
|
|
17
|
+
try {
|
|
18
|
+
if (code === 0) TiRtcLogging.i(rtcLogTag, message);
|
|
19
|
+
else TiRtcLogging.w(rtcLogTag, message);
|
|
20
|
+
} catch {
|
|
21
|
+
// Diagnostics must never change callback delivery.
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/recording.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import {isUInt8, TIRTC_ERROR_INVALID_ARGUMENT} from './errors';
|
|
2
|
+
import {TiRtcLogging} from './logging';
|
|
3
|
+
import {logRtcOutcome} from './logging_internal';
|
|
4
|
+
import {NativeTiRtc} from './native';
|
|
5
|
+
|
|
6
|
+
export type Resp<T> = Readonly<{
|
|
7
|
+
success: boolean;
|
|
8
|
+
code: number | null;
|
|
9
|
+
message: string | null;
|
|
10
|
+
data: T | null;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
export type TiRtcStartRecordingOptions = Readonly<{
|
|
14
|
+
videoStreamId: number;
|
|
15
|
+
audioStreamId?: number;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export type TiRtcGalleryAsset = Readonly<{
|
|
19
|
+
uri: string;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
function success<T>(data: T): Resp<T> {
|
|
23
|
+
return {success: true, code: null, message: null, data};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function recordingFailure<T>(code: number, message: string | null = null): Resp<T> {
|
|
27
|
+
return {success: false, code, message, data: null};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mediaFileFinalizer =
|
|
31
|
+
typeof FinalizationRegistry === 'undefined'
|
|
32
|
+
? null
|
|
33
|
+
: new FinalizationRegistry<number>((nativeFileId) => NativeTiRtc.mediaFileRelease(nativeFileId));
|
|
34
|
+
|
|
35
|
+
function logMediaOutcome(operation: string, code: number): void {
|
|
36
|
+
const message = `[${operation}] outcome=${code === 0 ? 'ok' : 'failed'} error_code=${code}`;
|
|
37
|
+
if (code === 0) TiRtcLogging.i('TIRTC_REACT_NATIVE_MEDIA', message);
|
|
38
|
+
else TiRtcLogging.e('TIRTC_REACT_NATIVE_MEDIA', message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TiRtcMediaFile {
|
|
42
|
+
readonly path: string;
|
|
43
|
+
moveToGallery(fileName?: string): Promise<Resp<TiRtcGalleryAsset>>;
|
|
44
|
+
delete(): Promise<number>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TiRtcRecordingFile extends TiRtcMediaFile {
|
|
48
|
+
readonly durationMs: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TiRtcSnapshotFile extends TiRtcMediaFile {}
|
|
52
|
+
|
|
53
|
+
export abstract class NativeMediaFileBase implements TiRtcMediaFile {
|
|
54
|
+
private movePromise: Promise<Resp<TiRtcGalleryAsset>> | null = null;
|
|
55
|
+
private moveFileName: string | null = null;
|
|
56
|
+
private deletePromise: Promise<number> | null = null;
|
|
57
|
+
private deleted = false;
|
|
58
|
+
private galleryAsset: TiRtcGalleryAsset | null = null;
|
|
59
|
+
|
|
60
|
+
protected constructor(
|
|
61
|
+
readonly path: string,
|
|
62
|
+
private readonly nativeFileId: number,
|
|
63
|
+
private readonly mediaKind: 'video' | 'image',
|
|
64
|
+
) {
|
|
65
|
+
mediaFileFinalizer?.register(this, nativeFileId, this);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
moveToGallery(fileName?: string): Promise<Resp<TiRtcGalleryAsset>> {
|
|
69
|
+
const normalized = normalizeGalleryFileName(fileName, this.mediaKind);
|
|
70
|
+
if (normalized.code !== 0) {
|
|
71
|
+
return Promise.resolve(recordingFailure<TiRtcGalleryAsset>(normalized.code));
|
|
72
|
+
}
|
|
73
|
+
if (this.galleryAsset !== null) {
|
|
74
|
+
return Promise.resolve(success(this.galleryAsset));
|
|
75
|
+
}
|
|
76
|
+
if (this.deleted) {
|
|
77
|
+
return Promise.resolve(recordingFailure<TiRtcGalleryAsset>(6045));
|
|
78
|
+
}
|
|
79
|
+
if (this.movePromise !== null) {
|
|
80
|
+
return normalized.fileName === this.moveFileName
|
|
81
|
+
? this.movePromise
|
|
82
|
+
: Promise.resolve(recordingFailure<TiRtcGalleryAsset>(6026));
|
|
83
|
+
}
|
|
84
|
+
if (this.deletePromise !== null) {
|
|
85
|
+
return Promise.resolve(recordingFailure<TiRtcGalleryAsset>(6026));
|
|
86
|
+
}
|
|
87
|
+
this.moveFileName = normalized.fileName;
|
|
88
|
+
this.movePromise = NativeTiRtc.mediaFileMoveToGallery(this.nativeFileId, normalized.fileName)
|
|
89
|
+
.then((result) => {
|
|
90
|
+
if (
|
|
91
|
+
result.code === 0
|
|
92
|
+
&& (result.uri === null || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(result.uri))
|
|
93
|
+
) {
|
|
94
|
+
logMediaOutcome('gallery_move', 1001);
|
|
95
|
+
this.deleted = true;
|
|
96
|
+
NativeTiRtc.mediaFileRelease(this.nativeFileId);
|
|
97
|
+
mediaFileFinalizer?.unregister(this);
|
|
98
|
+
return recordingFailure<TiRtcGalleryAsset>(1001);
|
|
99
|
+
}
|
|
100
|
+
logMediaOutcome('gallery_move', result.code);
|
|
101
|
+
if (result.code !== 0 || result.uri === null) {
|
|
102
|
+
return recordingFailure<TiRtcGalleryAsset>(result.code);
|
|
103
|
+
}
|
|
104
|
+
this.galleryAsset = {uri: result.uri};
|
|
105
|
+
this.deleted = true;
|
|
106
|
+
NativeTiRtc.mediaFileRelease(this.nativeFileId);
|
|
107
|
+
mediaFileFinalizer?.unregister(this);
|
|
108
|
+
return success(this.galleryAsset);
|
|
109
|
+
})
|
|
110
|
+
.finally(() => {
|
|
111
|
+
this.movePromise = null;
|
|
112
|
+
this.moveFileName = null;
|
|
113
|
+
});
|
|
114
|
+
return this.movePromise;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
delete(): Promise<number> {
|
|
118
|
+
if (this.deleted || this.galleryAsset !== null) {
|
|
119
|
+
return Promise.resolve(0);
|
|
120
|
+
}
|
|
121
|
+
if (this.deletePromise !== null) {
|
|
122
|
+
return this.deletePromise;
|
|
123
|
+
}
|
|
124
|
+
if (this.movePromise !== null) {
|
|
125
|
+
return Promise.resolve(6026);
|
|
126
|
+
}
|
|
127
|
+
this.deletePromise = this.deleteOnce().finally(() => {
|
|
128
|
+
this.deletePromise = null;
|
|
129
|
+
});
|
|
130
|
+
return this.deletePromise;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async deleteOnce(): Promise<number> {
|
|
134
|
+
const code = await NativeTiRtc.mediaFileDelete(this.nativeFileId);
|
|
135
|
+
logMediaOutcome('media_delete', code);
|
|
136
|
+
if (code === 0) {
|
|
137
|
+
this.deleted = true;
|
|
138
|
+
NativeTiRtc.mediaFileRelease(this.nativeFileId);
|
|
139
|
+
mediaFileFinalizer?.unregister(this);
|
|
140
|
+
}
|
|
141
|
+
return code;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type NormalizedGalleryFileName = Readonly<{
|
|
146
|
+
code: number;
|
|
147
|
+
fileName: string | null;
|
|
148
|
+
}>;
|
|
149
|
+
|
|
150
|
+
export function normalizeGalleryFileName(
|
|
151
|
+
fileName: string | undefined,
|
|
152
|
+
mediaType: 'video' | 'image',
|
|
153
|
+
): NormalizedGalleryFileName {
|
|
154
|
+
if (fileName === undefined) {
|
|
155
|
+
return {code: 0, fileName: null};
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
typeof fileName !== 'string'
|
|
159
|
+
|| fileName.length === 0
|
|
160
|
+
|| fileName.trim().length === 0
|
|
161
|
+
|| fileName === '.'
|
|
162
|
+
|| fileName === '..'
|
|
163
|
+
|| fileName.includes('/')
|
|
164
|
+
|| fileName.includes('\\')
|
|
165
|
+
|| fileName.includes('\0')
|
|
166
|
+
) {
|
|
167
|
+
return {code: TIRTC_ERROR_INVALID_ARGUMENT, fileName: null};
|
|
168
|
+
}
|
|
169
|
+
const extension = mediaType === 'video' ? '.mp4' : '.jpg';
|
|
170
|
+
const lowerName = fileName.toLowerCase();
|
|
171
|
+
let stem: string;
|
|
172
|
+
let normalizedName: string;
|
|
173
|
+
if (lowerName.endsWith(extension)) {
|
|
174
|
+
stem = fileName.slice(0, -extension.length);
|
|
175
|
+
normalizedName = fileName;
|
|
176
|
+
} else if (fileName.includes('.')) {
|
|
177
|
+
return {code: TIRTC_ERROR_INVALID_ARGUMENT, fileName: null};
|
|
178
|
+
} else {
|
|
179
|
+
stem = fileName;
|
|
180
|
+
normalizedName = `${fileName}${extension}`;
|
|
181
|
+
}
|
|
182
|
+
const stemBytes = utf8ByteLength(stem);
|
|
183
|
+
return stemBytes >= 1 && stemBytes <= 200
|
|
184
|
+
? {code: 0, fileName: normalizedName}
|
|
185
|
+
: {code: TIRTC_ERROR_INVALID_ARGUMENT, fileName: null};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function utf8ByteLength(value: string): number {
|
|
189
|
+
let bytes = 0;
|
|
190
|
+
for (const character of value) {
|
|
191
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
192
|
+
bytes += codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
|
|
193
|
+
}
|
|
194
|
+
return bytes;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
class TiRtcRecordingFileImpl extends NativeMediaFileBase implements TiRtcRecordingFile {
|
|
198
|
+
constructor(
|
|
199
|
+
path: string,
|
|
200
|
+
readonly durationMs: number,
|
|
201
|
+
nativeFileId: number,
|
|
202
|
+
) {
|
|
203
|
+
super(path, nativeFileId, 'video');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class TiRtcSnapshotFileImpl extends NativeMediaFileBase implements TiRtcSnapshotFile {
|
|
208
|
+
constructor(path: string, nativeFileId: number) {
|
|
209
|
+
super(path, nativeFileId, 'image');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function validateStartRecordingOptions(options: TiRtcStartRecordingOptions): number {
|
|
214
|
+
return options != null &&
|
|
215
|
+
isUInt8(options.videoStreamId) &&
|
|
216
|
+
options.videoStreamId <= 15 &&
|
|
217
|
+
(options.audioStreamId === undefined || (isUInt8(options.audioStreamId) && options.audioStreamId <= 15)) &&
|
|
218
|
+
options.audioStreamId !== options.videoStreamId
|
|
219
|
+
? 0
|
|
220
|
+
: TIRTC_ERROR_INVALID_ARGUMENT;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export class TiRtcRecordingTask {
|
|
224
|
+
private stopPromise: Promise<Resp<TiRtcRecordingFile>> | null = null;
|
|
225
|
+
|
|
226
|
+
protected constructor(private readonly nativeTaskId: number) {}
|
|
227
|
+
|
|
228
|
+
stop(): Promise<Resp<TiRtcRecordingFile>> {
|
|
229
|
+
if (this.stopPromise === null) {
|
|
230
|
+
this.stopPromise = NativeTiRtc.recordingTaskStop(this.nativeTaskId).then((result) => {
|
|
231
|
+
logRtcOutcome('recording_stop', result.code, ` duration_ms=${result.durationMs ?? 0}`);
|
|
232
|
+
if (result.code !== 0 || result.fileId === null || result.filePath === null || result.durationMs === null) {
|
|
233
|
+
return recordingFailure<TiRtcRecordingFile>(result.code);
|
|
234
|
+
}
|
|
235
|
+
return success(new TiRtcRecordingFileImpl(result.filePath, result.durationMs, result.fileId));
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return this.stopPromise;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
class NativeTiRtcRecordingTask extends TiRtcRecordingTask {
|
|
243
|
+
constructor(nativeTaskId: number) {
|
|
244
|
+
super(nativeTaskId);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function startRecording(
|
|
249
|
+
nativeConnectionId: number,
|
|
250
|
+
options: TiRtcStartRecordingOptions,
|
|
251
|
+
): Resp<TiRtcRecordingTask> {
|
|
252
|
+
const validationCode = validateStartRecordingOptions(options);
|
|
253
|
+
if (validationCode !== 0) {
|
|
254
|
+
return recordingFailure(validationCode);
|
|
255
|
+
}
|
|
256
|
+
const result = NativeTiRtc.connStartRecording(nativeConnectionId, {
|
|
257
|
+
videoStreamId: options.videoStreamId,
|
|
258
|
+
audioStreamId: options.audioStreamId ?? null,
|
|
259
|
+
});
|
|
260
|
+
logRtcOutcome('recording_start', result.code);
|
|
261
|
+
return result.code === 0 && result.taskId !== null
|
|
262
|
+
? success(new NativeTiRtcRecordingTask(result.taskId))
|
|
263
|
+
: recordingFailure(result.code);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export async function takeSnapshot(nativeOutputId: number): Promise<Resp<TiRtcSnapshotFile>> {
|
|
267
|
+
const result = await NativeTiRtc.videoOutputTakeSnapshot(nativeOutputId);
|
|
268
|
+
return result.code === 0 && result.fileId !== null && result.filePath !== null
|
|
269
|
+
? success(new TiRtcSnapshotFileImpl(result.filePath, result.fileId))
|
|
270
|
+
: recordingFailure(result.code);
|
|
271
|
+
}
|