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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package com.tange.ai.tirtc.reactnative
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import com.tange.ai.tirtc.TiRtc
|
|
6
|
+
import com.tange.ai.tirtc.TiRtcAudioInput
|
|
7
|
+
import com.tange.ai.tirtc.TiRtcAudioOutput
|
|
8
|
+
import com.tange.ai.tirtc.TiRtcConn
|
|
9
|
+
import com.tange.ai.tirtc.TiRtcLogging
|
|
10
|
+
import com.tange.ai.tirtc.TiRtcRecordingFile
|
|
11
|
+
import com.tange.ai.tirtc.TiRtcRecordingTask
|
|
12
|
+
import com.tange.ai.tirtc.TiRtcSnapshotFile
|
|
13
|
+
import com.tange.ai.tirtc.TiRtcVideoInput
|
|
14
|
+
import com.tange.ai.tirtc.TiRtcVideoOutput
|
|
15
|
+
import com.tange.ai.tirtc.TiCloudStorage
|
|
16
|
+
import com.tange.ai.tirtc.TiCloudStorageAudioOutput
|
|
17
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingFile
|
|
18
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingTask
|
|
19
|
+
import com.tange.ai.tirtc.TiCloudStorageReplay
|
|
20
|
+
import com.tange.ai.tirtc.TiCloudStorageSnapshotFile
|
|
21
|
+
import com.tange.ai.tirtc.TiCloudStorageVideoOutput
|
|
22
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
23
|
+
|
|
24
|
+
internal object TiRtcReactNativeCleanup {
|
|
25
|
+
private const val TAG = "TIRTC_REACT_NATIVE"
|
|
26
|
+
|
|
27
|
+
fun release(objects: List<Any>, files: List<Any>) {
|
|
28
|
+
val pending = AtomicInteger(1)
|
|
29
|
+
fun retain() {
|
|
30
|
+
pending.incrementAndGet()
|
|
31
|
+
}
|
|
32
|
+
fun finish() {
|
|
33
|
+
if (pending.decrementAndGet() == 0) {
|
|
34
|
+
Handler(Looper.getMainLooper()).post { releaseOwners(objects) }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
fun delete(file: Any?) {
|
|
38
|
+
if (file == null) return
|
|
39
|
+
retain()
|
|
40
|
+
val completed: (Int) -> Unit = { code ->
|
|
41
|
+
if (code != 0) logFailure("media_delete", code)
|
|
42
|
+
finish()
|
|
43
|
+
}
|
|
44
|
+
when (file) {
|
|
45
|
+
is TiRtcRecordingFile -> file.delete(completed)
|
|
46
|
+
is TiRtcSnapshotFile -> file.delete(completed)
|
|
47
|
+
is TiCloudStorageRecordingFile -> file.delete(completed)
|
|
48
|
+
is TiCloudStorageSnapshotFile -> file.delete(completed)
|
|
49
|
+
else -> finish()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
files.forEach(::delete)
|
|
54
|
+
objects.forEach { value ->
|
|
55
|
+
when (value) {
|
|
56
|
+
is TiRtcRecordingTask -> {
|
|
57
|
+
retain()
|
|
58
|
+
value.stop { result ->
|
|
59
|
+
delete(result.file)
|
|
60
|
+
finish()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
is TiCloudStorageRecordingTask -> {
|
|
64
|
+
retain()
|
|
65
|
+
value.stop { result ->
|
|
66
|
+
delete(result.file)
|
|
67
|
+
finish()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
is CloudStorageExportBridge -> {
|
|
71
|
+
retain()
|
|
72
|
+
value.invalidate { result ->
|
|
73
|
+
delete(result?.file)
|
|
74
|
+
finish()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
finish()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private fun releaseOwners(objects: List<Any>) {
|
|
83
|
+
objects.filterIsInstance<TiCloudStorageAudioOutput>().forEach { output ->
|
|
84
|
+
logFailure("ti-cloud-storage-audio-detach", output.detach())
|
|
85
|
+
logFailure("ti-cloud-storage-audio-dispose", output.dispose())
|
|
86
|
+
}
|
|
87
|
+
objects.filterIsInstance<TiCloudStorageVideoOutput>().forEach { output ->
|
|
88
|
+
logFailure("ti-cloud-storage-video-detach", output.detach())
|
|
89
|
+
logFailure("ti-cloud-storage-video-view-detach", output.detachView())
|
|
90
|
+
logFailure("ti-cloud-storage-video-dispose", output.dispose())
|
|
91
|
+
}
|
|
92
|
+
objects.filterIsInstance<TiRtcAudioOutput>().forEach { logFailure("audio_output_dispose", it.dispose()) }
|
|
93
|
+
objects.filterIsInstance<TiRtcVideoOutput>().forEach { logFailure("video_output_dispose", it.dispose()) }
|
|
94
|
+
objects.filterIsInstance<TiRtcAudioInput>().forEach { logFailure("audio_input_dispose", it.dispose()) }
|
|
95
|
+
objects.filterIsInstance<TiRtcVideoInput>().forEach { logFailure("video_input_dispose", it.dispose()) }
|
|
96
|
+
objects.filterIsInstance<TiCloudStorageReplay>().forEach { replay ->
|
|
97
|
+
logFailure("ti-cloud-storage-replay-stop", replay.stop())
|
|
98
|
+
logFailure("ti-cloud-storage-replay-dispose", replay.dispose())
|
|
99
|
+
}
|
|
100
|
+
objects.filterIsInstance<TiRtcConn>().forEach { conn ->
|
|
101
|
+
logFailure("conn_disconnect", conn.disconnect())
|
|
102
|
+
logFailure("conn_dispose", conn.dispose())
|
|
103
|
+
}
|
|
104
|
+
objects.filterIsInstance<TiCloudStorage>().forEach { logFailure("ti-cloud-storage-dispose", it.dispose()) }
|
|
105
|
+
logFailure("ti-cloud-storage-shutdown", TiCloudStorage.shutdown())
|
|
106
|
+
logFailure("runtime_shutdown", TiRtc.shutdown())
|
|
107
|
+
TiRtcLogging.i(TAG, "[module_invalidate] native owners released")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private fun logFailure(operation: String, code: Int) {
|
|
111
|
+
if (code != 0 && code != TiRtcReactNativeErrors.OBJECT_RELEASED) {
|
|
112
|
+
TiRtcLogging.w(TAG, "[module_invalidate] operation=$operation error_code=$code")
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.tange.ai.tirtc.reactnative
|
|
2
2
|
|
|
3
|
-
import android.util.Log
|
|
4
3
|
import com.tange.ai.tirtc.TiRtcAudioInput
|
|
5
4
|
import com.tange.ai.tirtc.TiRtcAudioOutput
|
|
6
5
|
import com.tange.ai.tirtc.TiRtcAudioOutputErrorListener
|
|
@@ -17,16 +16,47 @@ import com.tange.ai.tirtc.TiRtcVideoOutput
|
|
|
17
16
|
import com.tange.ai.tirtc.TiRtcVideoOutputErrorListener
|
|
18
17
|
import com.tange.ai.tirtc.TiRtcVideoOutputRenderSizeListener
|
|
19
18
|
import com.tange.ai.tirtc.TiRtcVideoOutputStateListener
|
|
19
|
+
import com.tange.ai.tirtc.TiCloudStorage
|
|
20
|
+
import com.tange.ai.tirtc.TiCloudStorageAudioOutput
|
|
21
|
+
import com.tange.ai.tirtc.TiCloudStorageAudioOutputStateListener
|
|
22
|
+
import com.tange.ai.tirtc.TiCloudStorageOutputErrorListener
|
|
23
|
+
import com.tange.ai.tirtc.TiCloudStorageReplay
|
|
24
|
+
import com.tange.ai.tirtc.TiCloudStorageReplayCompletedListener
|
|
25
|
+
import com.tange.ai.tirtc.TiCloudStorageReplayErrorListener
|
|
26
|
+
import com.tange.ai.tirtc.TiCloudStorageTimeChangedListener
|
|
27
|
+
import com.tange.ai.tirtc.TiCloudStorageVideoOutput
|
|
28
|
+
import com.tange.ai.tirtc.TiCloudStorageVideoOutputStateListener
|
|
20
29
|
|
|
21
30
|
internal class TiRtcReactNativeEventBinder(
|
|
22
31
|
private val events: TiRtcReactNativeEventSink,
|
|
23
32
|
) {
|
|
33
|
+
fun wireCloudStorage(id: Int, cloudStorage: TiCloudStorage) = Unit
|
|
34
|
+
|
|
35
|
+
fun wireCloudStorageReplay(id: Int, replay: TiCloudStorageReplay) {
|
|
36
|
+
replay.onTimeChanged = TiCloudStorageTimeChangedListener { events.tiCloudStorageReplayTimeChanged(id, it) }
|
|
37
|
+
replay.onCompleted = TiCloudStorageReplayCompletedListener { events.tiCloudStorageReplayCompleted(id) }
|
|
38
|
+
replay.onError = TiCloudStorageReplayErrorListener { events.tiCloudStorageReplayError(id, it) }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fun wireCloudStorageAudioOutput(id: Int, output: TiCloudStorageAudioOutput) {
|
|
42
|
+
output.onStateChanged = TiCloudStorageAudioOutputStateListener {
|
|
43
|
+
events.audioOutputStateChanged(id, it.name.lowercase())
|
|
44
|
+
}
|
|
45
|
+
output.onError = TiCloudStorageOutputErrorListener { events.audioOutputError(id, it) }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun wireCloudStorageVideoOutput(id: Int, output: TiCloudStorageVideoOutput) {
|
|
49
|
+
output.onStateChanged = TiCloudStorageVideoOutputStateListener {
|
|
50
|
+
events.videoOutputStateChanged(id, it.name.lowercase())
|
|
51
|
+
}
|
|
52
|
+
output.onError = TiCloudStorageOutputErrorListener { events.videoOutputError(id, it) }
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
fun wireConn(
|
|
25
56
|
id: Int,
|
|
26
57
|
conn: TiRtcConn,
|
|
27
58
|
) {
|
|
28
59
|
conn.onStateChanged = TiRtcConnStateListener { state, errorCode ->
|
|
29
|
-
Log.i(TAG, "connStateChanged objectId=$id state=${state.name.lowercase()} code=$errorCode")
|
|
30
60
|
events.connStateChanged(id, state.name.lowercase(), errorCode)
|
|
31
61
|
}
|
|
32
62
|
conn.onCommand = TiRtcConnCommandListener { command, data ->
|
|
@@ -69,7 +99,6 @@ internal class TiRtcReactNativeEventBinder(
|
|
|
69
99
|
input: TiRtcAudioInput,
|
|
70
100
|
) {
|
|
71
101
|
input.onStateChanged = TiRtcInputStateListener { state ->
|
|
72
|
-
Log.i(TAG, "audioInputStateChanged objectId=$id state=${state.name.lowercase()}")
|
|
73
102
|
events.audioInputStateChanged(id, state.name.lowercase())
|
|
74
103
|
}
|
|
75
104
|
input.onError = TiRtcInputErrorListener { code, message ->
|
|
@@ -82,7 +111,6 @@ internal class TiRtcReactNativeEventBinder(
|
|
|
82
111
|
input: TiRtcVideoInput,
|
|
83
112
|
) {
|
|
84
113
|
input.onStateChanged = TiRtcInputStateListener { state ->
|
|
85
|
-
Log.i(TAG, "videoInputStateChanged objectId=$id state=${state.name.lowercase()}")
|
|
86
114
|
events.videoInputStateChanged(id, state.name.lowercase())
|
|
87
115
|
}
|
|
88
116
|
input.onActualConfigChanged = TiRtcVideoInputActualConfigListener { size, fps ->
|
|
@@ -92,8 +120,4 @@ internal class TiRtcReactNativeEventBinder(
|
|
|
92
120
|
events.videoInputError(id, code, message)
|
|
93
121
|
}
|
|
94
122
|
}
|
|
95
|
-
|
|
96
|
-
private companion object {
|
|
97
|
-
const val TAG = "TiRtcReactNative"
|
|
98
|
-
}
|
|
99
123
|
}
|
|
@@ -44,6 +44,22 @@ internal class TiRtcReactNativeEventSink(
|
|
|
44
44
|
})
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
fun tiCloudStorageReplayTimeChanged(objectId: Int, timestampMs: Long) {
|
|
48
|
+
emit(base("tiCloudStorageReplayTimeChanged", objectId).apply {
|
|
49
|
+
putDouble("timestampMs", timestampMs.toDouble())
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fun tiCloudStorageReplayCompleted(objectId: Int) {
|
|
54
|
+
emit(base("tiCloudStorageReplayCompleted", objectId))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fun tiCloudStorageReplayError(objectId: Int, code: Int) {
|
|
58
|
+
emit(base("tiCloudStorageReplayError", objectId).apply {
|
|
59
|
+
putInt("code", TiRtcReactNativeErrors.normalizeAndroid(code))
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
fun audioOutputStateChanged(
|
|
48
64
|
objectId: Int,
|
|
49
65
|
state: String,
|
|
@@ -2,6 +2,68 @@ package com.tange.ai.tirtc.reactnative
|
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
4
|
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingRange
|
|
6
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingDay
|
|
7
|
+
|
|
8
|
+
internal fun recordingStartResult(code: Int, taskId: Int?): WritableMap =
|
|
9
|
+
Arguments.createMap().apply {
|
|
10
|
+
putInt("code", TiRtcReactNativeErrors.normalizeAndroid(code))
|
|
11
|
+
if (taskId == null) putNull("taskId") else putInt("taskId", taskId)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
internal fun tiCloudStorageRecordingsResult(code: Int, ranges: List<TiCloudStorageRecordingRange>): WritableMap =
|
|
15
|
+
Arguments.createMap().apply {
|
|
16
|
+
putInt("code", code)
|
|
17
|
+
putArray("recordings", Arguments.createArray().apply {
|
|
18
|
+
ranges.forEach { range ->
|
|
19
|
+
pushMap(Arguments.createMap().apply {
|
|
20
|
+
putDouble("startTimeMs", range.startTimeMs.toDouble())
|
|
21
|
+
putDouble("endTimeMs", range.endTimeMs.toDouble())
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
internal fun tiCloudStorageRecordingDaysResult(code: Int, days: List<TiCloudStorageRecordingDay>): WritableMap =
|
|
28
|
+
Arguments.createMap().apply {
|
|
29
|
+
putInt("code", code)
|
|
30
|
+
putArray("days", Arguments.createArray().apply {
|
|
31
|
+
days.forEach { day ->
|
|
32
|
+
pushMap(Arguments.createMap().apply {
|
|
33
|
+
putString("date", day.date)
|
|
34
|
+
putBoolean("hasRecording", day.hasRecording)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
internal fun tiCloudStorageReplayInfoResult(code: Int, speed: Int, currentTimeMs: Long?): WritableMap =
|
|
41
|
+
Arguments.createMap().apply {
|
|
42
|
+
putInt("code", code)
|
|
43
|
+
putInt("speed", speed)
|
|
44
|
+
if (currentTimeMs == null) putNull("currentTimeMs") else putDouble("currentTimeMs", currentTimeMs.toDouble())
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
internal fun mp4Result(code: Int, fileId: Int?, filePath: String?, durationMs: Long?): WritableMap =
|
|
48
|
+
Arguments.createMap().apply {
|
|
49
|
+
putInt("code", TiRtcReactNativeErrors.normalizeAndroid(code))
|
|
50
|
+
if (fileId == null) putNull("fileId") else putInt("fileId", fileId)
|
|
51
|
+
if (filePath == null) putNull("filePath") else putString("filePath", filePath)
|
|
52
|
+
if (durationMs == null) putNull("durationMs") else putDouble("durationMs", durationMs.toDouble())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
internal fun mediaSnapshotResult(code: Int, fileId: Int?, filePath: String?): WritableMap =
|
|
56
|
+
Arguments.createMap().apply {
|
|
57
|
+
putInt("code", TiRtcReactNativeErrors.normalizeAndroid(code))
|
|
58
|
+
if (fileId == null) putNull("fileId") else putInt("fileId", fileId)
|
|
59
|
+
if (filePath == null) putNull("filePath") else putString("filePath", filePath)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
internal fun galleryResult(code: Int, uri: String?): WritableMap =
|
|
63
|
+
Arguments.createMap().apply {
|
|
64
|
+
putInt("code", TiRtcReactNativeErrors.normalizeAndroid(code))
|
|
65
|
+
if (uri == null) putNull("uri") else putString("uri", uri)
|
|
66
|
+
}
|
|
5
67
|
|
|
6
68
|
internal fun createResult(
|
|
7
69
|
code: Int,
|
package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeModuleSupport.kt
CHANGED
|
@@ -2,7 +2,6 @@ package com.tange.ai.tirtc.reactnative
|
|
|
2
2
|
|
|
3
3
|
import android.os.Handler
|
|
4
4
|
import android.os.Looper
|
|
5
|
-
import android.util.Log
|
|
6
5
|
import com.facebook.react.bridge.Arguments
|
|
7
6
|
import com.facebook.react.bridge.Promise
|
|
8
7
|
import com.facebook.react.bridge.ReadableArray
|
|
@@ -11,18 +10,21 @@ import com.tange.ai.tirtc.TiRtcLogging
|
|
|
11
10
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
12
11
|
|
|
13
12
|
private const val LOG_UPLOAD_TIMEOUT_MS = 30_000L
|
|
13
|
+
private const val TAG = "TiRtcReactNative"
|
|
14
14
|
|
|
15
15
|
internal fun uploadLogsToPromise(
|
|
16
16
|
promise: Promise,
|
|
17
|
-
tag: String,
|
|
18
17
|
) {
|
|
19
|
-
Log.i(tag, "uploadLogs start")
|
|
20
18
|
val settled = AtomicBoolean(false)
|
|
21
19
|
val timeoutHandler = Handler(Looper.getMainLooper())
|
|
22
20
|
val timeout =
|
|
23
21
|
Runnable {
|
|
24
22
|
if (settled.compareAndSet(false, true)) {
|
|
25
|
-
|
|
23
|
+
TiRtcLogging.e(
|
|
24
|
+
TAG,
|
|
25
|
+
"[react_native_log_upload] failed: native upload did not settle the Promise before timeout; " +
|
|
26
|
+
"error_code=1001 reason=promise_timeout",
|
|
27
|
+
)
|
|
26
28
|
promise.resolve(
|
|
27
29
|
Arguments.createMap().apply {
|
|
28
30
|
putInt("code", TiRtcReactNativeErrors.PLATFORM_INTERNAL)
|
|
@@ -37,7 +39,6 @@ internal fun uploadLogsToPromise(
|
|
|
37
39
|
return@upload
|
|
38
40
|
}
|
|
39
41
|
timeoutHandler.removeCallbacks(timeout)
|
|
40
|
-
Log.i(tag, "uploadLogs completed code=$code logIdPresent=${!logId.isNullOrEmpty()}")
|
|
41
42
|
promise.resolve(
|
|
42
43
|
Arguments.createMap().apply {
|
|
43
44
|
putInt("code", TiRtcReactNativeErrors.normalizeAndroid(code))
|
|
@@ -50,7 +51,6 @@ internal fun uploadLogsToPromise(
|
|
|
50
51
|
return
|
|
51
52
|
}
|
|
52
53
|
timeoutHandler.removeCallbacks(timeout)
|
|
53
|
-
Log.w(tag, "uploadLogs failed_to_start code=$startCode")
|
|
54
54
|
promise.resolve(
|
|
55
55
|
Arguments.createMap().apply {
|
|
56
56
|
putInt("code", TiRtcReactNativeErrors.normalizeAndroid(startCode))
|
|
@@ -5,6 +5,12 @@ import com.tange.ai.tirtc.TiRtcAudioOutput
|
|
|
5
5
|
import com.tange.ai.tirtc.TiRtcConn
|
|
6
6
|
import com.tange.ai.tirtc.TiRtcVideoInput
|
|
7
7
|
import com.tange.ai.tirtc.TiRtcVideoOutput
|
|
8
|
+
import com.tange.ai.tirtc.TiRtcRecordingTask
|
|
9
|
+
import com.tange.ai.tirtc.TiCloudStorage
|
|
10
|
+
import com.tange.ai.tirtc.TiCloudStorageAudioOutput
|
|
11
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingTask
|
|
12
|
+
import com.tange.ai.tirtc.TiCloudStorageReplay
|
|
13
|
+
import com.tange.ai.tirtc.TiCloudStorageVideoOutput
|
|
8
14
|
import java.util.concurrent.ConcurrentHashMap
|
|
9
15
|
import java.util.concurrent.atomic.AtomicInteger
|
|
10
16
|
|
|
@@ -32,6 +38,13 @@ internal object TiRtcReactNativeRegistry {
|
|
|
32
38
|
events = eventSink
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
fun drain(eventSink: TiRtcReactNativeEventSink): List<Any> {
|
|
42
|
+
val values = entries.values.map(Entry::value)
|
|
43
|
+
entries.clear()
|
|
44
|
+
if (events === eventSink) events = null
|
|
45
|
+
return values
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
fun release(id: Int) {
|
|
36
49
|
entries.remove(id)
|
|
37
50
|
}
|
|
@@ -48,6 +61,20 @@ internal object TiRtcReactNativeRegistry {
|
|
|
48
61
|
|
|
49
62
|
fun videoInput(id: Int): TiRtcVideoInput? = entries[id]?.value as? TiRtcVideoInput
|
|
50
63
|
|
|
64
|
+
fun recordingTask(id: Int): TiRtcRecordingTask? = entries[id]?.value as? TiRtcRecordingTask
|
|
65
|
+
|
|
66
|
+
fun cloudStorage(id: Int): TiCloudStorage? = entries[id]?.value as? TiCloudStorage
|
|
67
|
+
|
|
68
|
+
fun tiCloudStorageReplay(id: Int): TiCloudStorageReplay? = entries[id]?.value as? TiCloudStorageReplay
|
|
69
|
+
|
|
70
|
+
fun tiCloudStorageRecordingTask(id: Int): TiCloudStorageRecordingTask? = entries[id]?.value as? TiCloudStorageRecordingTask
|
|
71
|
+
|
|
72
|
+
fun tiCloudStorageExportTask(id: Int): CloudStorageExportBridge? = entries[id]?.value as? CloudStorageExportBridge
|
|
73
|
+
|
|
74
|
+
fun tiCloudStorageAudioOutput(id: Int): TiCloudStorageAudioOutput? = entries[id]?.value as? TiCloudStorageAudioOutput
|
|
75
|
+
|
|
76
|
+
fun tiCloudStorageVideoOutput(id: Int): TiCloudStorageVideoOutput? = entries[id]?.value as? TiCloudStorageVideoOutput
|
|
77
|
+
|
|
51
78
|
fun reportVideoOutputError(
|
|
52
79
|
id: Int,
|
|
53
80
|
code: Int,
|
package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeStorageSupport.kt
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package com.tange.ai.tirtc.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.tange.ai.tirtc.TiCloudStorageExportTask
|
|
5
|
+
import com.tange.ai.tirtc.TiCloudStorageRecordingResult
|
|
6
|
+
|
|
7
|
+
internal class CloudStorageExportBridge(
|
|
8
|
+
private val resolve: (Promise, TiCloudStorageRecordingResult) -> Unit,
|
|
9
|
+
) {
|
|
10
|
+
@Volatile var task: TiCloudStorageExportTask? = null
|
|
11
|
+
@Volatile var onDrained: (() -> Unit)? = null
|
|
12
|
+
private var result: TiCloudStorageRecordingResult? = null
|
|
13
|
+
private val waiters = mutableListOf<Promise>()
|
|
14
|
+
private var invalidated = false
|
|
15
|
+
private var invalidationCompletion: ((TiCloudStorageRecordingResult?) -> Unit)? = null
|
|
16
|
+
|
|
17
|
+
fun complete(value: TiCloudStorageRecordingResult) {
|
|
18
|
+
val completed = synchronized(this) {
|
|
19
|
+
if (result != null) return
|
|
20
|
+
result = value
|
|
21
|
+
val pending = if (invalidated) emptyList() else waiters.toList()
|
|
22
|
+
waiters.clear()
|
|
23
|
+
pending to invalidationCompletion.also { invalidationCompletion = null }
|
|
24
|
+
}
|
|
25
|
+
completed.first.forEach { resolve(it, value) }
|
|
26
|
+
if (completed.first.isNotEmpty()) onDrained?.invoke()
|
|
27
|
+
completed.second?.invoke(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fun await(promise: Promise) {
|
|
31
|
+
val completed = synchronized(this) {
|
|
32
|
+
result?.also { return@synchronized it } ?: run {
|
|
33
|
+
waiters += promise
|
|
34
|
+
null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (completed != null) {
|
|
38
|
+
resolve(promise, completed)
|
|
39
|
+
onDrained?.invoke()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun invalidate(onTerminal: (TiCloudStorageRecordingResult?) -> Unit) {
|
|
44
|
+
val completed = synchronized(this) {
|
|
45
|
+
invalidated = true
|
|
46
|
+
waiters.clear()
|
|
47
|
+
result?.also { return@synchronized it } ?: run {
|
|
48
|
+
invalidationCompletion = onTerminal
|
|
49
|
+
null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (completed != null) {
|
|
53
|
+
onTerminal(completed)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
val code = task?.stop() ?: TiRtcReactNativeErrors.OBJECT_RELEASED
|
|
57
|
+
if (code != 0) {
|
|
58
|
+
synchronized(this) {
|
|
59
|
+
invalidationCompletion = null
|
|
60
|
+
}
|
|
61
|
+
onTerminal(null)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -22,7 +22,7 @@ class TiRtcVideoOutputViewManager :
|
|
|
22
22
|
|
|
23
23
|
override fun onDropViewInstance(view: FrameLayout) {
|
|
24
24
|
(view.getTag(ATTACHED_ID_TAG) as? Int)?.let { id ->
|
|
25
|
-
|
|
25
|
+
detachView(id)
|
|
26
26
|
}
|
|
27
27
|
super.onDropViewInstance(view)
|
|
28
28
|
}
|
|
@@ -63,7 +63,7 @@ class TiRtcVideoOutputViewManager :
|
|
|
63
63
|
val attachedId = view.getTag(ATTACHED_ID_TAG) as? Int
|
|
64
64
|
val attachedGeneration = view.getTag(ATTACHED_GENERATION_TAG) as? Int
|
|
65
65
|
if (attachedId != null && (attachedId != nativeObjectId || attachedGeneration != generation)) {
|
|
66
|
-
|
|
66
|
+
detachView(attachedId)
|
|
67
67
|
view.setTag(ATTACHED_ID_TAG, null)
|
|
68
68
|
view.setTag(ATTACHED_GENERATION_TAG, null)
|
|
69
69
|
}
|
|
@@ -86,7 +86,22 @@ class TiRtcVideoOutputViewManager :
|
|
|
86
86
|
}
|
|
87
87
|
view.setTag(ATTACHED_ID_TAG, nativeObjectId)
|
|
88
88
|
view.setTag(ATTACHED_GENERATION_TAG, generation)
|
|
89
|
+
return
|
|
89
90
|
}
|
|
91
|
+
TiRtcReactNativeRegistry.tiCloudStorageVideoOutput(nativeObjectId)?.let { output ->
|
|
92
|
+
val code = output.attachView(view)
|
|
93
|
+
if (TiRtcReactNativeErrors.normalizeAndroid(code) != TiRtcReactNativeErrors.OK) {
|
|
94
|
+
TiRtcReactNativeRegistry.reportVideoOutputError(nativeObjectId, code)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
view.setTag(ATTACHED_ID_TAG, nativeObjectId)
|
|
98
|
+
view.setTag(ATTACHED_GENERATION_TAG, generation)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun detachView(id: Int) {
|
|
103
|
+
TiRtcReactNativeRegistry.videoOutput(id)?.detachView()
|
|
104
|
+
?: TiRtcReactNativeRegistry.tiCloudStorageVideoOutput(id)?.detachView()
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
private fun resizeModeForView(view: FrameLayout): TiRtcVideoViewResizeMode =
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
#import "TiRtcReactNative+Private.h"
|
|
2
2
|
#import "TiRtcReactNativeErrors.h"
|
|
3
3
|
#import "TiRtcReactNativeMaps.h"
|
|
4
|
+
#import <Photos/Photos.h>
|
|
5
|
+
|
|
6
|
+
static NSString* TiRtcReactNativeGalleryFileName(NSString* fileName, BOOL video) {
|
|
7
|
+
NSString* extension = video ? @".mp4" : @".jpg";
|
|
8
|
+
if (fileName == nil) {
|
|
9
|
+
return [NSString stringWithFormat:@"%lld%@",
|
|
10
|
+
(long long)(NSDate.date.timeIntervalSince1970 * 1000),
|
|
11
|
+
extension];
|
|
12
|
+
}
|
|
13
|
+
unichar nul = 0;
|
|
14
|
+
NSString* nulString = [NSString stringWithCharacters:&nul length:1];
|
|
15
|
+
if (fileName.length == 0 ||
|
|
16
|
+
[[fileName stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]
|
|
17
|
+
length] == 0 ||
|
|
18
|
+
[fileName isEqualToString:@"."] || [fileName isEqualToString:@".."] ||
|
|
19
|
+
[fileName containsString:@"/"] || [fileName containsString:@"\\"] ||
|
|
20
|
+
[fileName containsString:nulString]) {
|
|
21
|
+
return nil;
|
|
22
|
+
}
|
|
23
|
+
NSString* stem = nil;
|
|
24
|
+
NSString* normalized = nil;
|
|
25
|
+
if ([fileName.lowercaseString hasSuffix:extension]) {
|
|
26
|
+
stem = [fileName substringToIndex:fileName.length - extension.length];
|
|
27
|
+
normalized = fileName;
|
|
28
|
+
} else if ([fileName containsString:@"."]) {
|
|
29
|
+
return nil;
|
|
30
|
+
} else {
|
|
31
|
+
stem = fileName;
|
|
32
|
+
normalized = [fileName stringByAppendingString:extension];
|
|
33
|
+
}
|
|
34
|
+
NSUInteger stemBytes = [stem lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
35
|
+
return stemBytes >= 1 && stemBytes <= 200 ? normalized : nil;
|
|
36
|
+
}
|
|
4
37
|
|
|
5
38
|
#pragma clang diagnostic push
|
|
6
39
|
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
|
|
@@ -96,6 +129,138 @@
|
|
|
96
129
|
}];
|
|
97
130
|
}
|
|
98
131
|
|
|
132
|
+
- (NSDictionary*)connStartRecording:(double)objectId
|
|
133
|
+
options:(JS::NativeTiRtc::NativeRecordingOptions&)options {
|
|
134
|
+
TiRtcConn* conn = [self conn:objectId];
|
|
135
|
+
if (conn == nil) {
|
|
136
|
+
return @{@"code" : @(TiRtcReactNativeErrorObjectReleased), @"taskId" : (id)kCFNull};
|
|
137
|
+
}
|
|
138
|
+
NSNumber* audioStreamId =
|
|
139
|
+
options.audioStreamId().has_value() ? @(options.audioStreamId().value()) : nil;
|
|
140
|
+
TiRtcRecordingTaskStartResult* result =
|
|
141
|
+
[conn startRecordingWithVideoStreamId:(int32_t)options.videoStreamId()
|
|
142
|
+
audioStreamId:audioStreamId];
|
|
143
|
+
if (result.code != 0 || result.task == nil) {
|
|
144
|
+
return @{@"code" : @(TiRtcReactNativeNormalizeDarwin(result.code)), @"taskId" : (id)kCFNull};
|
|
145
|
+
}
|
|
146
|
+
NSInteger taskId = [[self registry] addObject:result.task];
|
|
147
|
+
return @{@"code" : @(TiRtcReactNativeErrorOK), @"taskId" : @(taskId)};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
- (void)recordingTaskStop:(double)objectId
|
|
151
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
152
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
153
|
+
TiRtcRecordingTask* task = [[self registry] objectForId:(NSInteger)objectId];
|
|
154
|
+
if (![task isKindOfClass:[TiRtcRecordingTask class]]) {
|
|
155
|
+
resolve(@{
|
|
156
|
+
@"code" : @(TiRtcReactNativeErrorObjectReleased),
|
|
157
|
+
@"fileId" : (id)kCFNull,
|
|
158
|
+
@"filePath" : (id)kCFNull,
|
|
159
|
+
@"durationMs" : (id)kCFNull
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
[task stopWithCompletion:^(TiRtcRecordingResult* result) {
|
|
164
|
+
[[self registry] releaseObject:(NSInteger)objectId];
|
|
165
|
+
NSInteger fileId = result.file == nil ? 0 : [[self registry] addTransientObject:result.file];
|
|
166
|
+
resolve(@{
|
|
167
|
+
@"code" : @(TiRtcReactNativeNormalizeDarwin(result.code)),
|
|
168
|
+
@"fileId" : fileId == 0 ? (id)kCFNull : @(fileId),
|
|
169
|
+
@"filePath" : result.file.path ?: (id)kCFNull,
|
|
170
|
+
@"durationMs" : result.file != nil ? @(result.file.durationMs) : (id)kCFNull
|
|
171
|
+
});
|
|
172
|
+
}];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
- (void)mediaFileMoveToGallery:(double)objectId
|
|
176
|
+
fileName:(NSString*)fileName
|
|
177
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
178
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
179
|
+
id file = [[self registry] transientObjectForId:(NSInteger)objectId];
|
|
180
|
+
if (![file isKindOfClass:[TiRtcRecordingFile class]] &&
|
|
181
|
+
![file isKindOfClass:[TiRtcSnapshotFile class]] &&
|
|
182
|
+
![file isKindOfClass:[TiCloudStorageRecordingFile class]] &&
|
|
183
|
+
![file isKindOfClass:[TiCloudStorageSnapshotFile class]]) {
|
|
184
|
+
resolve(@{@"code" : @(TiRtcReactNativeErrorObjectReleased), @"uri" : (id)kCFNull});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
NSString* path = [file path];
|
|
188
|
+
BOOL video = [file isKindOfClass:[TiRtcRecordingFile class]] ||
|
|
189
|
+
[file isKindOfClass:[TiCloudStorageRecordingFile class]];
|
|
190
|
+
NSString* normalizedName = TiRtcReactNativeGalleryFileName(fileName, video);
|
|
191
|
+
if (normalizedName == nil) {
|
|
192
|
+
resolve(@{@"code" : @6000, @"uri" : (id)kCFNull});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) {
|
|
196
|
+
resolve(@{@"code" : @6045, @"uri" : (id)kCFNull});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
PHAuthorizationStatus status =
|
|
200
|
+
[PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelAddOnly];
|
|
201
|
+
if (status != PHAuthorizationStatusAuthorized && status != PHAuthorizationStatusLimited) {
|
|
202
|
+
resolve(@{@"code" : @6024, @"uri" : (id)kCFNull});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
__block NSString* localIdentifier = nil;
|
|
206
|
+
[[PHPhotoLibrary sharedPhotoLibrary]
|
|
207
|
+
performChanges:^{
|
|
208
|
+
PHAssetCreationRequest* request = [PHAssetCreationRequest creationRequestForAsset];
|
|
209
|
+
PHAssetResourceCreationOptions* options = [[PHAssetResourceCreationOptions alloc] init];
|
|
210
|
+
options.originalFilename = normalizedName;
|
|
211
|
+
[request addResourceWithType:video ? PHAssetResourceTypeVideo : PHAssetResourceTypePhoto
|
|
212
|
+
fileURL:[NSURL fileURLWithPath:path]
|
|
213
|
+
options:options];
|
|
214
|
+
localIdentifier = request.placeholderForCreatedAsset.localIdentifier;
|
|
215
|
+
}
|
|
216
|
+
completionHandler:^(BOOL success, __unused NSError* error) {
|
|
217
|
+
if (!success || localIdentifier.length == 0) {
|
|
218
|
+
resolve(@{@"code" : @6046, @"uri" : (id)kCFNull});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
void (^deleted)(int32_t) = ^(int32_t code) {
|
|
222
|
+
if (code == 0) {
|
|
223
|
+
[[self registry] releaseTransientObject:(NSInteger)objectId];
|
|
224
|
+
resolve(@{@"code" : @0,
|
|
225
|
+
@"uri" : [@"ph://" stringByAppendingString:localIdentifier]});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
PHFetchResult<PHAsset*>* assets =
|
|
229
|
+
[PHAsset fetchAssetsWithLocalIdentifiers:@[ localIdentifier ] options:nil];
|
|
230
|
+
[[PHPhotoLibrary sharedPhotoLibrary]
|
|
231
|
+
performChanges:^{ [PHAssetChangeRequest deleteAssets:assets]; }
|
|
232
|
+
completionHandler:^(__unused BOOL rolledBack, __unused NSError* rollbackError) {
|
|
233
|
+
resolve(@{@"code" : @(TiRtcReactNativeNormalizeDarwin(code)),
|
|
234
|
+
@"uri" : (id)kCFNull});
|
|
235
|
+
}];
|
|
236
|
+
};
|
|
237
|
+
[file deleteWithCompletion:deleted];
|
|
238
|
+
}];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
- (void)mediaFileDelete:(double)objectId
|
|
242
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
243
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
244
|
+
id file = [[self registry] transientObjectForId:(NSInteger)objectId];
|
|
245
|
+
if (![file isKindOfClass:[TiRtcRecordingFile class]] &&
|
|
246
|
+
![file isKindOfClass:[TiRtcSnapshotFile class]] &&
|
|
247
|
+
![file isKindOfClass:[TiCloudStorageRecordingFile class]] &&
|
|
248
|
+
![file isKindOfClass:[TiCloudStorageSnapshotFile class]]) {
|
|
249
|
+
resolve(@(TiRtcReactNativeErrorObjectReleased));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
[file deleteWithCompletion:^(int32_t code) {
|
|
253
|
+
if (code == 0) {
|
|
254
|
+
[[self registry] releaseTransientObject:(NSInteger)objectId];
|
|
255
|
+
}
|
|
256
|
+
resolve(@(TiRtcReactNativeNormalizeDarwin(code)));
|
|
257
|
+
}];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
- (void)mediaFileRelease:(double)objectId {
|
|
261
|
+
[[self registry] releaseTransientObject:(NSInteger)objectId];
|
|
262
|
+
}
|
|
263
|
+
|
|
99
264
|
- (NSDictionary*)connGetMetricsSnapshot:(double)objectId {
|
|
100
265
|
TiRtcConn* conn = [self conn:objectId];
|
|
101
266
|
return conn == nil ? TiRtcReactNativeReleasedSnapshotResult()
|
|
@@ -104,8 +269,14 @@
|
|
|
104
269
|
|
|
105
270
|
- (NSNumber*)connDispose:(double)objectId {
|
|
106
271
|
TiRtcConn* conn = [self conn:objectId];
|
|
107
|
-
|
|
108
|
-
|
|
272
|
+
if (conn == nil) {
|
|
273
|
+
return @(TiRtcReactNativeErrorObjectReleased);
|
|
274
|
+
}
|
|
275
|
+
NSInteger code = TiRtcReactNativeNormalizeDarwin([conn dispose]);
|
|
276
|
+
if (code == TiRtcReactNativeErrorOK) {
|
|
277
|
+
[self releaseRegisteredObject:(NSInteger)objectId object:conn];
|
|
278
|
+
}
|
|
279
|
+
return @(code);
|
|
109
280
|
}
|
|
110
281
|
|
|
111
282
|
@end
|