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
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
package com.tange.ai.tirtc.reactnative
|
|
2
2
|
|
|
3
|
-
import android.
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.ContentValues
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.os.Environment
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
10
|
+
import android.provider.MediaStore
|
|
4
11
|
import com.facebook.react.bridge.Promise
|
|
5
12
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
13
|
import com.facebook.react.bridge.ReactMethod
|
|
@@ -13,21 +20,46 @@ import com.tange.ai.tirtc.TiRtcAudioInput
|
|
|
13
20
|
import com.tange.ai.tirtc.TiRtcAudioOutput
|
|
14
21
|
import com.tange.ai.tirtc.TiRtcConn
|
|
15
22
|
import com.tange.ai.tirtc.TiRtcInitOptions
|
|
23
|
+
import com.tange.ai.tirtc.TiRtcLogging
|
|
24
|
+
import com.tange.ai.tirtc.TiRtcRecordingFile
|
|
25
|
+
import com.tange.ai.tirtc.TiRtcSnapshotFile
|
|
16
26
|
import com.tange.ai.tirtc.TiRtcVideoInput
|
|
17
27
|
import com.tange.ai.tirtc.TiRtcVideoOutput
|
|
28
|
+
import com.tange.ai.tirtc.TiCloudStorage
|
|
29
|
+
import com.tange.ai.tirtc.TiCloudStorageAudioOutput
|
|
30
|
+
import com.tange.ai.tirtc.TiCloudStorageExportRequest
|
|
31
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingFile
|
|
32
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingResult
|
|
33
|
+
import com.tange.ai.tirtc.TiCloudStorageReplaySpeed
|
|
34
|
+
import com.tange.ai.tirtc.TiCloudStorageSnapshotFile
|
|
35
|
+
import com.tange.ai.tirtc.TiCloudStorageVideoOutput
|
|
36
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
37
|
+
import java.util.concurrent.ArrayBlockingQueue
|
|
38
|
+
import java.util.concurrent.CountDownLatch
|
|
39
|
+
import java.util.concurrent.RejectedExecutionException
|
|
40
|
+
import java.util.concurrent.ThreadPoolExecutor
|
|
41
|
+
import java.util.concurrent.TimeUnit
|
|
42
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
43
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
18
44
|
|
|
19
45
|
@ReactModule(name = TiRtcModule.NAME)
|
|
20
46
|
class TiRtcModule(
|
|
21
47
|
private val reactContext: ReactApplicationContext,
|
|
22
48
|
) : NativeTiRtcSpec(reactContext) {
|
|
23
49
|
private val registry = TiRtcReactNativeRegistry
|
|
50
|
+
private val invalidated = AtomicBoolean(false)
|
|
24
51
|
private val events =
|
|
25
52
|
TiRtcReactNativeEventSink(registry) { event ->
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
if (!invalidated.get()) {
|
|
54
|
+
reactContext.runOnJSQueueThread {
|
|
55
|
+
if (!invalidated.get()) emitOnTiRtcEvent(event)
|
|
56
|
+
}
|
|
28
57
|
}
|
|
29
58
|
}
|
|
30
59
|
private val eventBinder = TiRtcReactNativeEventBinder(events)
|
|
60
|
+
private val nextMediaFileId = AtomicInteger(1)
|
|
61
|
+
private val mediaFiles = ConcurrentHashMap<Int, Any>()
|
|
62
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
31
63
|
|
|
32
64
|
init {
|
|
33
65
|
registry.bindEventSink(events)
|
|
@@ -35,13 +67,22 @@ class TiRtcModule(
|
|
|
35
67
|
|
|
36
68
|
override fun getName(): String = NAME
|
|
37
69
|
|
|
70
|
+
override fun invalidate() {
|
|
71
|
+
if (!invalidated.compareAndSet(false, true)) return
|
|
72
|
+
val objects = registry.drain(events)
|
|
73
|
+
val files = mediaFiles.values.toList()
|
|
74
|
+
mediaFiles.clear()
|
|
75
|
+
TiRtcReactNativeCleanup.release(objects, files)
|
|
76
|
+
super.invalidate()
|
|
77
|
+
}
|
|
78
|
+
|
|
38
79
|
@ReactMethod
|
|
39
80
|
override fun initialize(
|
|
40
81
|
options: ReadableMap,
|
|
41
82
|
promise: Promise,
|
|
42
83
|
) {
|
|
43
84
|
val code =
|
|
44
|
-
TiRtc.
|
|
85
|
+
TiRtc.init(
|
|
45
86
|
reactContext,
|
|
46
87
|
TiRtcInitOptions(
|
|
47
88
|
appId = options.getString("appId") ?: "",
|
|
@@ -54,6 +95,11 @@ class TiRtcModule(
|
|
|
54
95
|
|
|
55
96
|
override fun shutdown(): Double {
|
|
56
97
|
if (registry.hasLiveObjects()) {
|
|
98
|
+
TiRtcLogging.w(
|
|
99
|
+
TAG,
|
|
100
|
+
"[react_native_shutdown] rejected: dispose all React Native objects before shutdown; " +
|
|
101
|
+
"error_code=1007 reason=objects_live",
|
|
102
|
+
)
|
|
57
103
|
return TiRtcReactNativeErrors.RUNTIME_OBJECTS_LIVE.toDouble()
|
|
58
104
|
}
|
|
59
105
|
return TiRtcReactNativeErrors.normalizeAndroid(TiRtc.shutdown()).toDouble()
|
|
@@ -67,16 +113,322 @@ class TiRtcModule(
|
|
|
67
113
|
message: String,
|
|
68
114
|
) {
|
|
69
115
|
when (level.toInt()) {
|
|
70
|
-
0 ->
|
|
71
|
-
1 ->
|
|
72
|
-
2 ->
|
|
73
|
-
3 ->
|
|
116
|
+
0 -> TiRtcLogging.d(tag, message)
|
|
117
|
+
1 -> TiRtcLogging.i(tag, message)
|
|
118
|
+
2 -> TiRtcLogging.w(tag, message)
|
|
119
|
+
3 -> TiRtcLogging.e(tag, message)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@ReactMethod
|
|
124
|
+
override fun tiCloudStorageInitialize(options: ReadableMap, promise: Promise) {
|
|
125
|
+
val code = TiCloudStorage.init(
|
|
126
|
+
reactContext,
|
|
127
|
+
options.getString("appId") ?: "",
|
|
128
|
+
options.getString("endpoint") ?: "",
|
|
129
|
+
options.getBoolean("consoleLogEnabled"),
|
|
130
|
+
)
|
|
131
|
+
promise.resolve(code.toDouble())
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
override fun tiCloudStorageShutdown(): Double = TiCloudStorage.shutdown().toDouble()
|
|
135
|
+
|
|
136
|
+
override fun tiCloudStorageCreate(token: String): WritableMap =
|
|
137
|
+
registerReactNativeObject(
|
|
138
|
+
registry,
|
|
139
|
+
factory = { TiCloudStorage(token) },
|
|
140
|
+
onRegistered = { entry, value -> eventBinder.wireCloudStorage(entry.id, value as TiCloudStorage) },
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
override fun tiCloudStorageUpdateToken(id: Double, token: String): Double =
|
|
144
|
+
registry.cloudStorage(id.toInt())?.updateToken(token).normalized()
|
|
145
|
+
|
|
146
|
+
@ReactMethod
|
|
147
|
+
override fun tiCloudStorageListRecordings(id: Double, startTimeMs: Double, endTimeMs: Double, promise: Promise) {
|
|
148
|
+
val cloudStorage = registry.cloudStorage(id.toInt())
|
|
149
|
+
if (cloudStorage == null) {
|
|
150
|
+
promise.resolve(tiCloudStorageRecordingsResult(TiRtcReactNativeErrors.OBJECT_RELEASED, emptyList()))
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
cloudStorage.listRecordings(startTimeMs.toLong(), endTimeMs.toLong()) { result ->
|
|
154
|
+
reactContext.runOnJSQueueThread {
|
|
155
|
+
promise.resolve(tiCloudStorageRecordingsResult(result.code, result.recordings))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@ReactMethod
|
|
161
|
+
override fun tiCloudStorageListRecordingDays(
|
|
162
|
+
id: Double,
|
|
163
|
+
startDate: String,
|
|
164
|
+
endDate: String,
|
|
165
|
+
timeZoneId: String,
|
|
166
|
+
promise: Promise,
|
|
167
|
+
) {
|
|
168
|
+
val cloudStorage = registry.cloudStorage(id.toInt())
|
|
169
|
+
if (cloudStorage == null) {
|
|
170
|
+
promise.resolve(tiCloudStorageRecordingDaysResult(TiRtcReactNativeErrors.OBJECT_RELEASED, emptyList()))
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
cloudStorage.listRecordingDays(startDate, endDate, timeZoneId) { result ->
|
|
174
|
+
reactContext.runOnJSQueueThread {
|
|
175
|
+
promise.resolve(tiCloudStorageRecordingDaysResult(result.code, result.days))
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override fun tiCloudStorageCreateReplay(id: Double): WritableMap {
|
|
181
|
+
val cloudStorage = registry.cloudStorage(id.toInt())
|
|
182
|
+
?: return createResult(TiRtcReactNativeErrors.OBJECT_RELEASED, 0, 0)
|
|
183
|
+
return registerReactNativeObject(
|
|
184
|
+
registry,
|
|
185
|
+
factory = { cloudStorage.createReplay() },
|
|
186
|
+
onRegistered = { entry, value -> eventBinder.wireCloudStorageReplay(entry.id, value as com.tange.ai.tirtc.TiCloudStorageReplay) },
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
override fun tiCloudStorageExportRecording(
|
|
191
|
+
id: Double,
|
|
192
|
+
startTimeMs: Double,
|
|
193
|
+
endTimeMs: Double,
|
|
194
|
+
videoChannelId: Double,
|
|
195
|
+
audioChannelId: Double?,
|
|
196
|
+
): WritableMap {
|
|
197
|
+
val cloudStorage = registry.cloudStorage(id.toInt())
|
|
198
|
+
?: return recordingStartResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null)
|
|
199
|
+
val bridge = CloudStorageExportBridge(::resolveCloudStorageRecording)
|
|
200
|
+
val started = cloudStorage.exportRecording(
|
|
201
|
+
TiCloudStorageExportRequest(
|
|
202
|
+
startTimeMs.toLong(),
|
|
203
|
+
endTimeMs.toLong(),
|
|
204
|
+
videoChannelId.toInt(),
|
|
205
|
+
audioChannelId?.toInt(),
|
|
206
|
+
),
|
|
207
|
+
null,
|
|
208
|
+
) { result -> bridge.complete(result) }
|
|
209
|
+
val task = started.task ?: return recordingStartResult(started.code, null)
|
|
210
|
+
bridge.task = task
|
|
211
|
+
val entry = registry.add(bridge)
|
|
212
|
+
bridge.onDrained = { registry.release(entry.id) }
|
|
213
|
+
return recordingStartResult(started.code, entry.id)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
override fun tiCloudStorageExportProgress(id: Double): Double =
|
|
217
|
+
registry.tiCloudStorageExportTask(id.toInt())?.task?.progress ?: 0.0
|
|
218
|
+
|
|
219
|
+
@ReactMethod
|
|
220
|
+
override fun tiCloudStorageExportResult(id: Double, promise: Promise) {
|
|
221
|
+
registry.tiCloudStorageExportTask(id.toInt())?.await(promise)
|
|
222
|
+
?: promise.resolve(mp4Result(TiRtcReactNativeErrors.OBJECT_RELEASED, null, null, null))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
override fun tiCloudStorageExportStop(id: Double): Double =
|
|
226
|
+
registry.tiCloudStorageExportTask(id.toInt())?.task?.stop().normalized()
|
|
227
|
+
|
|
228
|
+
override fun tiCloudStorageDispose(id: Double): Double {
|
|
229
|
+
val objectId = id.toInt()
|
|
230
|
+
val code = registry.cloudStorage(objectId)?.dispose().normalized()
|
|
231
|
+
if (code.toInt() == TiRtcReactNativeErrors.OK) registry.release(objectId)
|
|
232
|
+
return code
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
override fun tiCloudStorageReplayGetInfo(id: Double): WritableMap {
|
|
236
|
+
val replay = registry.tiCloudStorageReplay(id.toInt())
|
|
237
|
+
?: return tiCloudStorageReplayInfoResult(TiRtcReactNativeErrors.OBJECT_RELEASED, 0, null)
|
|
238
|
+
val speed = when (replay.speed) {
|
|
239
|
+
TiCloudStorageReplaySpeed.X0_125 -> 6
|
|
240
|
+
TiCloudStorageReplaySpeed.X0_25 -> 5
|
|
241
|
+
TiCloudStorageReplaySpeed.X0_5 -> 4
|
|
242
|
+
TiCloudStorageReplaySpeed.X1 -> 0
|
|
243
|
+
TiCloudStorageReplaySpeed.X2 -> 1
|
|
244
|
+
TiCloudStorageReplaySpeed.X4 -> 2
|
|
245
|
+
TiCloudStorageReplaySpeed.X8 -> 3
|
|
246
|
+
}
|
|
247
|
+
return tiCloudStorageReplayInfoResult(0, speed, replay.currentTimeMs)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
override fun tiCloudStorageReplayPlay(
|
|
251
|
+
id: Double,
|
|
252
|
+
startTimeMs: Double,
|
|
253
|
+
endTimeMs: Double,
|
|
254
|
+
initialTimeMs: Double?,
|
|
255
|
+
): Double {
|
|
256
|
+
val replay = registry.tiCloudStorageReplay(id.toInt()) ?: return TiRtcReactNativeErrors.OBJECT_RELEASED.toDouble()
|
|
257
|
+
return replay.play(startTimeMs.toLong(), endTimeMs.toLong(), initialTimeMs?.toLong()).toDouble()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
override fun tiCloudStorageReplayPause(id: Double): Double = registry.tiCloudStorageReplay(id.toInt())?.pause().normalized()
|
|
261
|
+
|
|
262
|
+
override fun tiCloudStorageReplayResume(id: Double): Double = registry.tiCloudStorageReplay(id.toInt())?.resume().normalized()
|
|
263
|
+
|
|
264
|
+
override fun tiCloudStorageReplaySeek(id: Double, timeMs: Double): Double =
|
|
265
|
+
registry.tiCloudStorageReplay(id.toInt())?.seek(timeMs.toLong()).normalized()
|
|
266
|
+
|
|
267
|
+
override fun tiCloudStorageReplaySetSpeed(id: Double, speed: Double): Double {
|
|
268
|
+
val value = when (speed.toInt()) {
|
|
269
|
+
6 -> TiCloudStorageReplaySpeed.X0_125
|
|
270
|
+
5 -> TiCloudStorageReplaySpeed.X0_25
|
|
271
|
+
4 -> TiCloudStorageReplaySpeed.X0_5
|
|
272
|
+
0 -> TiCloudStorageReplaySpeed.X1
|
|
273
|
+
1 -> TiCloudStorageReplaySpeed.X2
|
|
274
|
+
2 -> TiCloudStorageReplaySpeed.X4
|
|
275
|
+
3 -> TiCloudStorageReplaySpeed.X8
|
|
276
|
+
else -> return TiRtcReactNativeErrors.INVALID_ARGUMENT.toDouble()
|
|
277
|
+
}
|
|
278
|
+
return registry.tiCloudStorageReplay(id.toInt())?.setSpeed(value).normalized()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
override fun tiCloudStorageReplayStartRecording(
|
|
282
|
+
id: Double,
|
|
283
|
+
videoChannelId: Double,
|
|
284
|
+
audioChannelId: Double?,
|
|
285
|
+
): WritableMap {
|
|
286
|
+
val replay = registry.tiCloudStorageReplay(id.toInt())
|
|
287
|
+
?: return recordingStartResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null)
|
|
288
|
+
val result = replay.startRecording(videoChannelId.toInt(), audioChannelId?.toInt())
|
|
289
|
+
val entry = result.task?.let(registry::add)
|
|
290
|
+
return recordingStartResult(result.code, entry?.id)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@ReactMethod
|
|
294
|
+
override fun tiCloudStorageRecordingTaskStop(id: Double, promise: Promise) {
|
|
295
|
+
val objectId = id.toInt()
|
|
296
|
+
val task = registry.tiCloudStorageRecordingTask(objectId)
|
|
297
|
+
if (task == null) {
|
|
298
|
+
promise.resolve(mp4Result(TiRtcReactNativeErrors.OBJECT_RELEASED, null, null, null))
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
task.stop { result ->
|
|
302
|
+
registry.release(objectId)
|
|
303
|
+
resolveCloudStorageRecording(promise, result)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
override fun tiCloudStorageReplayStop(id: Double): Double = registry.tiCloudStorageReplay(id.toInt())?.stop().normalized()
|
|
308
|
+
|
|
309
|
+
override fun tiCloudStorageReplayDispose(id: Double): Double {
|
|
310
|
+
val objectId = id.toInt()
|
|
311
|
+
val code = registry.tiCloudStorageReplay(objectId)?.dispose().normalized()
|
|
312
|
+
if (code.toInt() == TiRtcReactNativeErrors.OK) registry.release(objectId)
|
|
313
|
+
return code
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
override fun tiCloudStorageCreateAudioOutput(): WritableMap =
|
|
317
|
+
registerReactNativeObject(
|
|
318
|
+
registry,
|
|
319
|
+
factory = { TiCloudStorageAudioOutput() },
|
|
320
|
+
onRegistered = { entry, value -> eventBinder.wireCloudStorageAudioOutput(entry.id, value as TiCloudStorageAudioOutput) },
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
override fun tiCloudStorageAudioOutputGetState(id: Double): WritableMap {
|
|
324
|
+
val output = registry.tiCloudStorageAudioOutput(id.toInt())
|
|
325
|
+
?: return stateResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null)
|
|
326
|
+
return stateResult(0, output.state.name.lowercase())
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
override fun tiCloudStorageAudioOutputSetVolume(id: Double, volumePercent: Double): Double =
|
|
330
|
+
registry.tiCloudStorageAudioOutput(id.toInt())?.setVolume(volumePercent.toInt()).normalized()
|
|
331
|
+
|
|
332
|
+
override fun tiCloudStorageAudioOutputAttach(outputId: Double, replayId: Double, channelId: Double): Double {
|
|
333
|
+
val replay = registry.tiCloudStorageReplay(replayId.toInt())
|
|
334
|
+
?: return TiRtcReactNativeErrors.OBJECT_RELEASED.toDouble()
|
|
335
|
+
return registry.tiCloudStorageAudioOutput(outputId.toInt())?.attach(replay, channelId.toInt()).normalized()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
override fun tiCloudStorageAudioOutputDetach(outputId: Double): Double =
|
|
339
|
+
registry.tiCloudStorageAudioOutput(outputId.toInt())?.detach().normalized()
|
|
340
|
+
|
|
341
|
+
override fun tiCloudStorageAudioOutputDispose(id: Double): Double {
|
|
342
|
+
val objectId = id.toInt()
|
|
343
|
+
val code = registry.tiCloudStorageAudioOutput(objectId)?.dispose().normalized()
|
|
344
|
+
if (code.toInt() == TiRtcReactNativeErrors.OK) registry.release(objectId)
|
|
345
|
+
return code
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
override fun tiCloudStorageCreateVideoOutput(): WritableMap =
|
|
349
|
+
registerReactNativeObject(
|
|
350
|
+
registry,
|
|
351
|
+
factory = { TiCloudStorageVideoOutput() },
|
|
352
|
+
onRegistered = { entry, value -> eventBinder.wireCloudStorageVideoOutput(entry.id, value as TiCloudStorageVideoOutput) },
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
override fun tiCloudStorageVideoOutputGetState(id: Double): WritableMap {
|
|
356
|
+
val output = registry.tiCloudStorageVideoOutput(id.toInt())
|
|
357
|
+
?: return stateResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null)
|
|
358
|
+
return stateResult(0, output.state.name.lowercase())
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
override fun tiCloudStorageVideoOutputAttach(outputId: Double, replayId: Double, channelId: Double): Double {
|
|
362
|
+
val replay = registry.tiCloudStorageReplay(replayId.toInt())
|
|
363
|
+
?: return TiRtcReactNativeErrors.OBJECT_RELEASED.toDouble()
|
|
364
|
+
return registry.tiCloudStorageVideoOutput(outputId.toInt())?.attach(replay, channelId.toInt()).normalized()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
override fun tiCloudStorageVideoOutputDetach(outputId: Double): Double =
|
|
368
|
+
registry.tiCloudStorageVideoOutput(outputId.toInt())?.detach().normalized()
|
|
369
|
+
|
|
370
|
+
@ReactMethod
|
|
371
|
+
override fun tiCloudStorageVideoOutputTakeSnapshot(id: Double, promise: Promise) {
|
|
372
|
+
val output = registry.tiCloudStorageVideoOutput(id.toInt())
|
|
373
|
+
if (output == null) {
|
|
374
|
+
promise.resolve(mediaSnapshotResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null, null))
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
output.takeSnapshot { result ->
|
|
378
|
+
val fileId = result.file?.let { file ->
|
|
379
|
+
nextMediaFileId.getAndIncrement().also { mediaFiles[it] = file }
|
|
380
|
+
}
|
|
381
|
+
reactContext.runOnJSQueueThread {
|
|
382
|
+
promise.resolve(mediaSnapshotResult(result.code, fileId, result.file?.path))
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
override fun tiCloudStorageVideoOutputDispose(id: Double): Double {
|
|
388
|
+
val objectId = id.toInt()
|
|
389
|
+
val output = registry.tiCloudStorageVideoOutput(objectId) ?: return TiRtcReactNativeErrors.OBJECT_RELEASED.toDouble()
|
|
390
|
+
val detachCode = detachCloudStorageVideoView(output)
|
|
391
|
+
val nativeCode = if (detachCode == TiRtcReactNativeErrors.OK) output.dispose() else detachCode
|
|
392
|
+
val code = TiRtcReactNativeErrors.normalizeAndroid(nativeCode).toDouble()
|
|
393
|
+
if (code.toInt() == TiRtcReactNativeErrors.OK) registry.release(objectId)
|
|
394
|
+
return code
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private fun detachCloudStorageVideoView(output: TiCloudStorageVideoOutput): Int {
|
|
398
|
+
if (Looper.myLooper() == Looper.getMainLooper()) return output.detachView()
|
|
399
|
+
val result = AtomicInteger(TiRtcReactNativeErrors.PLATFORM_INTERNAL)
|
|
400
|
+
val completed = CountDownLatch(1)
|
|
401
|
+
if (!mainHandler.post {
|
|
402
|
+
result.set(output.detachView())
|
|
403
|
+
completed.countDown()
|
|
404
|
+
}
|
|
405
|
+
) {
|
|
406
|
+
return TiRtcReactNativeErrors.PLATFORM_INTERNAL
|
|
407
|
+
}
|
|
408
|
+
return try {
|
|
409
|
+
if (completed.await(VIEW_DETACH_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
|
410
|
+
result.get()
|
|
411
|
+
} else {
|
|
412
|
+
TiRtcReactNativeErrors.PLATFORM_INTERNAL
|
|
413
|
+
}
|
|
414
|
+
} catch (_: InterruptedException) {
|
|
415
|
+
Thread.currentThread().interrupt()
|
|
416
|
+
TiRtcReactNativeErrors.PLATFORM_INTERNAL
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private fun resolveCloudStorageRecording(promise: Promise, result: TiCloudStorageRecordingResult) {
|
|
421
|
+
val fileId = result.file?.let { file ->
|
|
422
|
+
nextMediaFileId.getAndIncrement().also { mediaFiles[it] = file }
|
|
423
|
+
}
|
|
424
|
+
reactContext.runOnJSQueueThread {
|
|
425
|
+
promise.resolve(mp4Result(result.code, fileId, result.file?.path, result.file?.durationMs))
|
|
74
426
|
}
|
|
75
427
|
}
|
|
76
428
|
|
|
77
429
|
@ReactMethod
|
|
78
430
|
override fun uploadLogs(promise: Promise) {
|
|
79
|
-
uploadLogsToPromise(promise
|
|
431
|
+
uploadLogsToPromise(promise)
|
|
80
432
|
}
|
|
81
433
|
|
|
82
434
|
override fun createConn(): WritableMap =
|
|
@@ -95,22 +447,9 @@ class TiRtcModule(
|
|
|
95
447
|
id: Double,
|
|
96
448
|
remoteId: String,
|
|
97
449
|
token: String,
|
|
98
|
-
): Double
|
|
99
|
-
val objectId = id.toInt()
|
|
100
|
-
val code = registry.conn(objectId)?.connect(remoteId, token).normalized()
|
|
101
|
-
Log.i(
|
|
102
|
-
TAG,
|
|
103
|
-
"connConnect objectId=$objectId remoteIdPresent=${remoteId.isNotEmpty()} tokenPresent=${token.isNotEmpty()} code=${code.toInt()}",
|
|
104
|
-
)
|
|
105
|
-
return code
|
|
106
|
-
}
|
|
450
|
+
): Double = registry.conn(id.toInt())?.connect(remoteId, token).normalized()
|
|
107
451
|
|
|
108
|
-
override fun connDisconnect(id: Double): Double
|
|
109
|
-
val objectId = id.toInt()
|
|
110
|
-
val code = registry.conn(objectId)?.disconnect().normalized()
|
|
111
|
-
Log.i(TAG, "connDisconnect objectId=$objectId code=${code.toInt()}")
|
|
112
|
-
return code
|
|
113
|
-
}
|
|
452
|
+
override fun connDisconnect(id: Double): Double = registry.conn(id.toInt())?.disconnect().normalized()
|
|
114
453
|
|
|
115
454
|
override fun connSendCommand(
|
|
116
455
|
id: Double,
|
|
@@ -143,6 +482,196 @@ class TiRtcModule(
|
|
|
143
482
|
override fun connRequestKeyFrame(id: Double, streamId: Double): Double =
|
|
144
483
|
registry.conn(id.toInt())?.requestKeyFrame(streamId.toInt()).normalized()
|
|
145
484
|
|
|
485
|
+
override fun connStartRecording(id: Double, options: ReadableMap): WritableMap {
|
|
486
|
+
val conn = registry.conn(id.toInt())
|
|
487
|
+
if (conn == null) {
|
|
488
|
+
return recordingStartResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null)
|
|
489
|
+
}
|
|
490
|
+
val result = conn.startRecording(
|
|
491
|
+
videoStreamId = options.getInt("videoStreamId"),
|
|
492
|
+
audioStreamId = if (options.isNull("audioStreamId")) null else options.getInt("audioStreamId"),
|
|
493
|
+
)
|
|
494
|
+
val entry = result.task?.let { registry.add(it) }
|
|
495
|
+
return recordingStartResult(result.code, entry?.id)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
@ReactMethod
|
|
499
|
+
override fun recordingTaskStop(id: Double, promise: Promise) {
|
|
500
|
+
val objectId = id.toInt()
|
|
501
|
+
val task = registry.recordingTask(objectId)
|
|
502
|
+
if (task == null) {
|
|
503
|
+
promise.resolve(mp4Result(TiRtcReactNativeErrors.OBJECT_RELEASED, null, null, null))
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
task.stop { result ->
|
|
507
|
+
registry.release(objectId)
|
|
508
|
+
val fileId = result.file?.let { file ->
|
|
509
|
+
nextMediaFileId.getAndIncrement().also { mediaFiles[it] = file }
|
|
510
|
+
}
|
|
511
|
+
reactContext.runOnJSQueueThread {
|
|
512
|
+
promise.resolve(mp4Result(result.code, fileId, result.file?.path, result.file?.durationMs))
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@ReactMethod
|
|
518
|
+
override fun mediaFileMoveToGallery(id: Double, fileName: String?, promise: Promise) {
|
|
519
|
+
val fileId = id.toInt()
|
|
520
|
+
val mediaFile = mediaFiles[fileId]
|
|
521
|
+
val path = when (mediaFile) {
|
|
522
|
+
is TiRtcRecordingFile -> mediaFile.path
|
|
523
|
+
is TiRtcSnapshotFile -> mediaFile.path
|
|
524
|
+
is TiCloudStorageRecordingFile -> mediaFile.path
|
|
525
|
+
is TiCloudStorageSnapshotFile -> mediaFile.path
|
|
526
|
+
else -> {
|
|
527
|
+
promise.resolve(galleryResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null))
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
val video = mediaFile is TiRtcRecordingFile || mediaFile is TiCloudStorageRecordingFile
|
|
532
|
+
val normalizedName = normalizeGalleryFileName(fileName, video)
|
|
533
|
+
if (normalizedName == null) {
|
|
534
|
+
promise.resolve(galleryResult(ERROR_INVALID_ARGUMENT, null))
|
|
535
|
+
return
|
|
536
|
+
}
|
|
537
|
+
if (
|
|
538
|
+
Build.VERSION.SDK_INT <= Build.VERSION_CODES.P &&
|
|
539
|
+
reactContext.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
|
540
|
+
) {
|
|
541
|
+
promise.resolve(galleryResult(ERROR_PERMISSION_DENIED, null))
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
mediaFileExecutor.execute {
|
|
546
|
+
publishMediaFile(
|
|
547
|
+
fileId,
|
|
548
|
+
mediaFile,
|
|
549
|
+
path,
|
|
550
|
+
video,
|
|
551
|
+
normalizedName,
|
|
552
|
+
promise,
|
|
553
|
+
)
|
|
554
|
+
}
|
|
555
|
+
} catch (_: RejectedExecutionException) {
|
|
556
|
+
promise.resolve(galleryResult(ERROR_RESOURCE_EXHAUSTED, null))
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
private fun publishMediaFile(
|
|
561
|
+
fileId: Int,
|
|
562
|
+
mediaFile: Any,
|
|
563
|
+
path: String,
|
|
564
|
+
video: Boolean,
|
|
565
|
+
fileName: String,
|
|
566
|
+
promise: Promise,
|
|
567
|
+
) {
|
|
568
|
+
val source = java.io.File(path)
|
|
569
|
+
if (!source.isFile) {
|
|
570
|
+
reactContext.runOnJSQueueThread { promise.resolve(galleryResult(ERROR_FILE_READ_FAILED, null)) }
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
val collection = if (video) MediaStore.Video.Media.EXTERNAL_CONTENT_URI else MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
|
574
|
+
val displayName = fileName
|
|
575
|
+
val legacyTarget = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
576
|
+
val directory = if (video) Environment.DIRECTORY_MOVIES else Environment.DIRECTORY_PICTURES
|
|
577
|
+
val targetDirectory = java.io.File(
|
|
578
|
+
Environment.getExternalStoragePublicDirectory(directory),
|
|
579
|
+
"TiRTC",
|
|
580
|
+
)
|
|
581
|
+
if (!targetDirectory.exists() && !targetDirectory.mkdirs()) {
|
|
582
|
+
reactContext.runOnJSQueueThread {
|
|
583
|
+
promise.resolve(galleryResult(ERROR_FILE_WRITE_FAILED, null))
|
|
584
|
+
}
|
|
585
|
+
return
|
|
586
|
+
}
|
|
587
|
+
java.io.File(targetDirectory, displayName)
|
|
588
|
+
} else {
|
|
589
|
+
null
|
|
590
|
+
}
|
|
591
|
+
val values = ContentValues().apply {
|
|
592
|
+
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
|
593
|
+
put(MediaStore.MediaColumns.MIME_TYPE, if (video) "video/mp4" else "image/jpeg")
|
|
594
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
595
|
+
put(MediaStore.MediaColumns.RELATIVE_PATH, "${if (video) "Movies" else "Pictures"}/TiRTC")
|
|
596
|
+
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
|
597
|
+
} else {
|
|
598
|
+
@Suppress("DEPRECATION")
|
|
599
|
+
put(MediaStore.MediaColumns.DATA, legacyTarget!!.absolutePath)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
val resolver = reactContext.contentResolver
|
|
603
|
+
val target = try {
|
|
604
|
+
resolver.insert(collection, values)
|
|
605
|
+
} catch (_: SecurityException) {
|
|
606
|
+
reactContext.runOnJSQueueThread { promise.resolve(galleryResult(ERROR_PERMISSION_DENIED, null)) }
|
|
607
|
+
return
|
|
608
|
+
} catch (_: Throwable) {
|
|
609
|
+
null
|
|
610
|
+
}
|
|
611
|
+
if (target == null) {
|
|
612
|
+
reactContext.runOnJSQueueThread { promise.resolve(galleryResult(ERROR_FILE_WRITE_FAILED, null)) }
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
resolver.openOutputStream(target)?.use { output ->
|
|
617
|
+
source.inputStream().use { input -> input.copyTo(output) }
|
|
618
|
+
} ?: error("unable to open media asset")
|
|
619
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
620
|
+
values.clear()
|
|
621
|
+
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
|
622
|
+
if (resolver.update(target, values, null, null) != 1) error("unable to publish media asset")
|
|
623
|
+
}
|
|
624
|
+
} catch (error: Throwable) {
|
|
625
|
+
try {
|
|
626
|
+
resolver.delete(target, null, null)
|
|
627
|
+
} catch (_: Throwable) {
|
|
628
|
+
}
|
|
629
|
+
val code = if (error is SecurityException) ERROR_PERMISSION_DENIED else ERROR_FILE_WRITE_FAILED
|
|
630
|
+
reactContext.runOnJSQueueThread { promise.resolve(galleryResult(code, null)) }
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
val onDelete: (Int) -> Unit = { code ->
|
|
634
|
+
if (code == 0) {
|
|
635
|
+
mediaFiles.remove(fileId, mediaFile)
|
|
636
|
+
} else {
|
|
637
|
+
try {
|
|
638
|
+
resolver.delete(target, null, null)
|
|
639
|
+
} catch (_: Throwable) {
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
reactContext.runOnJSQueueThread {
|
|
643
|
+
promise.resolve(galleryResult(code, if (code == 0) target.toString() else null))
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
when (mediaFile) {
|
|
647
|
+
is TiRtcRecordingFile -> mediaFile.delete(onDelete)
|
|
648
|
+
is TiRtcSnapshotFile -> mediaFile.delete(onDelete)
|
|
649
|
+
is TiCloudStorageRecordingFile -> mediaFile.delete(onDelete)
|
|
650
|
+
is TiCloudStorageSnapshotFile -> mediaFile.delete(onDelete)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
@ReactMethod
|
|
655
|
+
override fun mediaFileDelete(id: Double, promise: Promise) {
|
|
656
|
+
val fileId = id.toInt()
|
|
657
|
+
val mediaFile = mediaFiles[fileId]
|
|
658
|
+
val callback: (Int) -> Unit = { code ->
|
|
659
|
+
if (code == 0) mediaFiles.remove(fileId)
|
|
660
|
+
reactContext.runOnJSQueueThread { promise.resolve(code.toDouble()) }
|
|
661
|
+
}
|
|
662
|
+
when (mediaFile) {
|
|
663
|
+
is TiRtcRecordingFile -> mediaFile.delete(callback)
|
|
664
|
+
is TiRtcSnapshotFile -> mediaFile.delete(callback)
|
|
665
|
+
is TiCloudStorageRecordingFile -> mediaFile.delete(callback)
|
|
666
|
+
is TiCloudStorageSnapshotFile -> mediaFile.delete(callback)
|
|
667
|
+
else -> promise.resolve(TiRtcReactNativeErrors.OBJECT_RELEASED.toDouble())
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
override fun mediaFileRelease(id: Double) {
|
|
672
|
+
mediaFiles.remove(id.toInt())
|
|
673
|
+
}
|
|
674
|
+
|
|
146
675
|
override fun connGetMetricsSnapshot(id: Double): WritableMap {
|
|
147
676
|
val conn = registry.conn(id.toInt()) ?: return releasedSnapshotResult()
|
|
148
677
|
return connMetricsResult(conn.getMetricsSnapshot())
|
|
@@ -181,6 +710,11 @@ class TiRtcModule(
|
|
|
181
710
|
volumePercent < 0.0 ||
|
|
182
711
|
volumePercent > 100.0
|
|
183
712
|
) {
|
|
713
|
+
TiRtcLogging.w(
|
|
714
|
+
TAG,
|
|
715
|
+
"[react_native_audio_output_set_volume] rejected: volume must be an integer from 0 to 100; " +
|
|
716
|
+
"error_code=6000 reason=invalid_volume",
|
|
717
|
+
)
|
|
184
718
|
return TiRtcReactNativeErrors.INVALID_ARGUMENT.toDouble()
|
|
185
719
|
}
|
|
186
720
|
return registry.audioOutput(id.toInt())?.setVolume(volumePercent.toInt()).normalized()
|
|
@@ -259,6 +793,23 @@ class TiRtcModule(
|
|
|
259
793
|
return videoDebugResult(output.getDebugSnapshot())
|
|
260
794
|
}
|
|
261
795
|
|
|
796
|
+
@ReactMethod
|
|
797
|
+
override fun videoOutputTakeSnapshot(id: Double, promise: Promise) {
|
|
798
|
+
val output = registry.videoOutput(id.toInt())
|
|
799
|
+
if (output == null) {
|
|
800
|
+
promise.resolve(mediaSnapshotResult(TiRtcReactNativeErrors.OBJECT_RELEASED, null, null))
|
|
801
|
+
return
|
|
802
|
+
}
|
|
803
|
+
output.takeSnapshot { snapshotResult ->
|
|
804
|
+
val fileId = snapshotResult.file?.let { file ->
|
|
805
|
+
nextMediaFileId.getAndIncrement().also { mediaFiles[it] = file }
|
|
806
|
+
}
|
|
807
|
+
reactContext.runOnJSQueueThread {
|
|
808
|
+
promise.resolve(mediaSnapshotResult(snapshotResult.code, fileId, snapshotResult.file?.path))
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
262
813
|
override fun videoOutputDispose(id: Double): Double {
|
|
263
814
|
val objectId = id.toInt()
|
|
264
815
|
val code = registry.videoOutput(objectId)?.dispose().normalized()
|
|
@@ -310,7 +861,6 @@ class TiRtcModule(
|
|
|
310
861
|
?: TiRtcReactNativeErrors.OBJECT_RELEASED,
|
|
311
862
|
)
|
|
312
863
|
}
|
|
313
|
-
Log.i(TAG, "audioInputAttach objectId=${id.toInt()} connId=${connId.toInt()} streamId=${streamId.toInt()} code=$code")
|
|
314
864
|
promise.resolve(code.toDouble())
|
|
315
865
|
}
|
|
316
866
|
|
|
@@ -324,7 +874,6 @@ class TiRtcModule(
|
|
|
324
874
|
registry.audioInput(id.toInt())?.start()
|
|
325
875
|
?: TiRtcReactNativeErrors.OBJECT_RELEASED,
|
|
326
876
|
)
|
|
327
|
-
Log.i(TAG, "audioInputStart objectId=${id.toInt()} code=$code")
|
|
328
877
|
promise.resolve(code.toDouble())
|
|
329
878
|
}
|
|
330
879
|
|
|
@@ -420,7 +969,6 @@ class TiRtcModule(
|
|
|
420
969
|
?: TiRtcReactNativeErrors.OBJECT_RELEASED,
|
|
421
970
|
)
|
|
422
971
|
}
|
|
423
|
-
Log.i(TAG, "videoInputAttach objectId=${id.toInt()} connId=${connId.toInt()} streamId=${streamId.toInt()} code=$code")
|
|
424
972
|
promise.resolve(code.toDouble())
|
|
425
973
|
}
|
|
426
974
|
|
|
@@ -434,7 +982,6 @@ class TiRtcModule(
|
|
|
434
982
|
registry.videoInput(id.toInt())?.start()
|
|
435
983
|
?: TiRtcReactNativeErrors.OBJECT_RELEASED,
|
|
436
984
|
)
|
|
437
|
-
Log.i(TAG, "videoInputStart objectId=${id.toInt()} code=$code")
|
|
438
985
|
promise.resolve(code.toDouble())
|
|
439
986
|
}
|
|
440
987
|
|
|
@@ -478,5 +1025,52 @@ class TiRtcModule(
|
|
|
478
1025
|
companion object {
|
|
479
1026
|
const val NAME = "TiRtc"
|
|
480
1027
|
private const val TAG = "TiRtcReactNative"
|
|
1028
|
+
private const val VIEW_DETACH_TIMEOUT_SECONDS = 5L
|
|
1029
|
+
private const val ERROR_INVALID_ARGUMENT = 6000
|
|
1030
|
+
private const val ERROR_PERMISSION_DENIED = 6024
|
|
1031
|
+
private const val ERROR_RESOURCE_EXHAUSTED = 6043
|
|
1032
|
+
private const val ERROR_FILE_READ_FAILED = 6045
|
|
1033
|
+
private const val ERROR_FILE_WRITE_FAILED = 6046
|
|
1034
|
+
private val mediaFileExecutor =
|
|
1035
|
+
ThreadPoolExecutor(
|
|
1036
|
+
1,
|
|
1037
|
+
2,
|
|
1038
|
+
30L,
|
|
1039
|
+
TimeUnit.SECONDS,
|
|
1040
|
+
ArrayBlockingQueue(16),
|
|
1041
|
+
{ work -> Thread(work, "tirtc-rn-media-file").apply { isDaemon = true } },
|
|
1042
|
+
ThreadPoolExecutor.AbortPolicy(),
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
private fun normalizeGalleryFileName(fileName: String?, video: Boolean): String? {
|
|
1046
|
+
if (fileName == null) {
|
|
1047
|
+
return "${System.currentTimeMillis()}.${if (video) "mp4" else "jpg"}"
|
|
1048
|
+
}
|
|
1049
|
+
if (
|
|
1050
|
+
fileName.isEmpty() ||
|
|
1051
|
+
fileName.isBlank() ||
|
|
1052
|
+
fileName == "." ||
|
|
1053
|
+
fileName == ".." ||
|
|
1054
|
+
fileName.contains('/') ||
|
|
1055
|
+
fileName.contains('\\') ||
|
|
1056
|
+
fileName.contains('\u0000')
|
|
1057
|
+
) {
|
|
1058
|
+
return null
|
|
1059
|
+
}
|
|
1060
|
+
val extension = if (video) ".mp4" else ".jpg"
|
|
1061
|
+
val stem: String
|
|
1062
|
+
val normalized: String
|
|
1063
|
+
if (fileName.endsWith(extension, ignoreCase = true)) {
|
|
1064
|
+
stem = fileName.dropLast(extension.length)
|
|
1065
|
+
normalized = fileName
|
|
1066
|
+
} else if (fileName.contains('.')) {
|
|
1067
|
+
return null
|
|
1068
|
+
} else {
|
|
1069
|
+
stem = fileName
|
|
1070
|
+
normalized = fileName + extension
|
|
1071
|
+
}
|
|
1072
|
+
val stemBytes = stem.toByteArray(Charsets.UTF_8).size
|
|
1073
|
+
return normalized.takeIf { stemBytes in 1..200 }
|
|
1074
|
+
}
|
|
481
1075
|
}
|
|
482
1076
|
}
|