tirtc-react-native 2.3.1 → 2.4.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +23 -7
  2. package/TiRtcReactNative.podspec +1 -1
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/AndroidManifest.xml +2 -1
  5. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcModule.kt +585 -28
  6. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeCleanup.kt +115 -0
  7. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventBinder.kt +35 -8
  8. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeEventSink.kt +20 -0
  9. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeMaps.kt +62 -0
  10. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeModuleSupport.kt +6 -6
  11. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeRegistry.kt +27 -0
  12. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcReactNativeStoreSupport.kt +64 -0
  13. package/android/src/main/java/com/tange/ai/tirtc/reactnative/TiRtcVideoOutputViewManager.kt +17 -2
  14. package/ios/TiRtcReactNative+Conn.mm +173 -2
  15. package/ios/TiRtcReactNative+Input.mm +18 -4
  16. package/ios/TiRtcReactNative+Output.mm +41 -4
  17. package/ios/TiRtcReactNative+Private.h +4 -0
  18. package/ios/TiRtcReactNative+Store.mm +496 -0
  19. package/ios/TiRtcReactNative.h +2 -1
  20. package/ios/TiRtcReactNative.mm +109 -1
  21. package/ios/TiRtcReactNativeEventBinder.h +8 -0
  22. package/ios/TiRtcReactNativeEventBinder.mm +104 -0
  23. package/ios/TiRtcReactNativeEventSink.h +4 -0
  24. package/ios/TiRtcReactNativeEventSink.mm +20 -0
  25. package/ios/TiRtcReactNativeRegistry.h +5 -0
  26. package/ios/TiRtcReactNativeRegistry.mm +43 -0
  27. package/ios/TiRtcVideoOutputView.mm +11 -0
  28. package/lib/module/audio_input.js +33 -17
  29. package/lib/module/audio_input.js.map +1 -1
  30. package/lib/module/audio_output.js +27 -10
  31. package/lib/module/audio_output.js.map +1 -1
  32. package/lib/module/conn.js +31 -7
  33. package/lib/module/conn.js.map +1 -1
  34. package/lib/module/errors.js +4 -3
  35. package/lib/module/errors.js.map +1 -1
  36. package/lib/module/index.js +2 -0
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/module/logging.js +9 -1
  39. package/lib/module/logging.js.map +1 -1
  40. package/lib/module/logging_internal.js +27 -0
  41. package/lib/module/logging_internal.js.map +1 -0
  42. package/lib/module/recording.js +210 -0
  43. package/lib/module/recording.js.map +1 -0
  44. package/lib/module/runtime.js +13 -5
  45. package/lib/module/runtime.js.map +1 -1
  46. package/lib/module/specs/NativeTiRtc.js.map +1 -1
  47. package/lib/module/store.js +698 -0
  48. package/lib/module/store.js.map +1 -0
  49. package/lib/module/video_input.js +40 -22
  50. package/lib/module/video_input.js.map +1 -1
  51. package/lib/module/video_output.js +51 -10
  52. package/lib/module/video_output.js.map +1 -1
  53. package/lib/typescript/audio_input.d.ts +1 -0
  54. package/lib/typescript/conn.d.ts +2 -0
  55. package/lib/typescript/errors.d.ts +3 -2
  56. package/lib/typescript/index.d.ts +4 -0
  57. package/lib/typescript/logging_internal.d.ts +2 -0
  58. package/lib/typescript/recording.d.ts +53 -0
  59. package/lib/typescript/runtime.d.ts +2 -0
  60. package/lib/typescript/specs/NativeTiRtc.d.ts +88 -0
  61. package/lib/typescript/store.d.ts +202 -0
  62. package/lib/typescript/video_input.d.ts +1 -0
  63. package/lib/typescript/video_output.d.ts +3 -0
  64. package/package.json +1 -1
  65. package/src/audio_input.ts +85 -59
  66. package/src/audio_output.ts +30 -17
  67. package/src/conn.ts +46 -28
  68. package/src/errors.ts +4 -3
  69. package/src/index.ts +54 -0
  70. package/src/logging.ts +7 -1
  71. package/src/logging_internal.ts +23 -0
  72. package/src/recording.ts +271 -0
  73. package/src/runtime.ts +14 -5
  74. package/src/specs/NativeTiRtc.ts +123 -0
  75. package/src/store.ts +760 -0
  76. package/src/video_input.ts +97 -62
  77. 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.TiStore
16
+ import com.tange.ai.tirtc.TiStoreAudioOutput
17
+ import com.tange.ai.tirtc.TiStoreRecordingFile
18
+ import com.tange.ai.tirtc.TiStoreRecordingTask
19
+ import com.tange.ai.tirtc.TiStoreReplay
20
+ import com.tange.ai.tirtc.TiStoreSnapshotFile
21
+ import com.tange.ai.tirtc.TiStoreVideoOutput
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 TiStoreRecordingFile -> file.delete(completed)
48
+ is TiStoreSnapshotFile -> 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 TiStoreRecordingTask -> {
64
+ retain()
65
+ value.stop { result ->
66
+ delete(result.file)
67
+ finish()
68
+ }
69
+ }
70
+ is StoreExportBridge -> {
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<TiStoreAudioOutput>().forEach { output ->
84
+ logFailure("store_audio_detach", output.detach())
85
+ logFailure("store_audio_dispose", output.dispose())
86
+ }
87
+ objects.filterIsInstance<TiStoreVideoOutput>().forEach { output ->
88
+ logFailure("store_video_detach", output.detach())
89
+ logFailure("store_video_view_detach", output.detachView())
90
+ logFailure("store_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<TiStoreReplay>().forEach { replay ->
97
+ logFailure("store_replay_stop", replay.stop())
98
+ logFailure("store_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<TiStore>().forEach { logFailure("store_dispose", it.dispose()) }
105
+ logFailure("store_shutdown", TiStore.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,50 @@ 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.TiStore
20
+ import com.tange.ai.tirtc.TiStoreAudioOutput
21
+ import com.tange.ai.tirtc.TiStoreAudioOutputStateListener
22
+ import com.tange.ai.tirtc.TiStoreOutputErrorListener
23
+ import com.tange.ai.tirtc.TiStoreReplay
24
+ import com.tange.ai.tirtc.TiStoreReplayCompletedListener
25
+ import com.tange.ai.tirtc.TiStoreReplayErrorListener
26
+ import com.tange.ai.tirtc.TiStoreTimeChangedListener
27
+ import com.tange.ai.tirtc.TiStoreTokenExpiredListener
28
+ import com.tange.ai.tirtc.TiStoreVideoOutput
29
+ import com.tange.ai.tirtc.TiStoreVideoOutputStateListener
20
30
 
21
31
  internal class TiRtcReactNativeEventBinder(
22
32
  private val events: TiRtcReactNativeEventSink,
23
33
  ) {
34
+ fun wireStore(id: Int, store: TiStore) {
35
+ store.onTokenExpired = TiStoreTokenExpiredListener { events.storeTokenExpired(id) }
36
+ }
37
+
38
+ fun wireStoreReplay(id: Int, replay: TiStoreReplay) {
39
+ replay.onTimeChanged = TiStoreTimeChangedListener { events.storeReplayTimeChanged(id, it) }
40
+ replay.onCompleted = TiStoreReplayCompletedListener { events.storeReplayCompleted(id) }
41
+ replay.onError = TiStoreReplayErrorListener { events.storeReplayError(id, it) }
42
+ }
43
+
44
+ fun wireStoreAudioOutput(id: Int, output: TiStoreAudioOutput) {
45
+ output.onStateChanged = TiStoreAudioOutputStateListener {
46
+ events.audioOutputStateChanged(id, it.name.lowercase())
47
+ }
48
+ output.onError = TiStoreOutputErrorListener { events.audioOutputError(id, it) }
49
+ }
50
+
51
+ fun wireStoreVideoOutput(id: Int, output: TiStoreVideoOutput) {
52
+ output.onStateChanged = TiStoreVideoOutputStateListener {
53
+ events.videoOutputStateChanged(id, it.name.lowercase())
54
+ }
55
+ output.onError = TiStoreOutputErrorListener { events.videoOutputError(id, it) }
56
+ }
57
+
24
58
  fun wireConn(
25
59
  id: Int,
26
60
  conn: TiRtcConn,
27
61
  ) {
28
62
  conn.onStateChanged = TiRtcConnStateListener { state, errorCode ->
29
- Log.i(TAG, "connStateChanged objectId=$id state=${state.name.lowercase()} code=$errorCode")
30
63
  events.connStateChanged(id, state.name.lowercase(), errorCode)
31
64
  }
32
65
  conn.onCommand = TiRtcConnCommandListener { command, data ->
@@ -69,7 +102,6 @@ internal class TiRtcReactNativeEventBinder(
69
102
  input: TiRtcAudioInput,
70
103
  ) {
71
104
  input.onStateChanged = TiRtcInputStateListener { state ->
72
- Log.i(TAG, "audioInputStateChanged objectId=$id state=${state.name.lowercase()}")
73
105
  events.audioInputStateChanged(id, state.name.lowercase())
74
106
  }
75
107
  input.onError = TiRtcInputErrorListener { code, message ->
@@ -82,7 +114,6 @@ internal class TiRtcReactNativeEventBinder(
82
114
  input: TiRtcVideoInput,
83
115
  ) {
84
116
  input.onStateChanged = TiRtcInputStateListener { state ->
85
- Log.i(TAG, "videoInputStateChanged objectId=$id state=${state.name.lowercase()}")
86
117
  events.videoInputStateChanged(id, state.name.lowercase())
87
118
  }
88
119
  input.onActualConfigChanged = TiRtcVideoInputActualConfigListener { size, fps ->
@@ -92,8 +123,4 @@ internal class TiRtcReactNativeEventBinder(
92
123
  events.videoInputError(id, code, message)
93
124
  }
94
125
  }
95
-
96
- private companion object {
97
- const val TAG = "TiRtcReactNative"
98
- }
99
126
  }
@@ -44,6 +44,26 @@ internal class TiRtcReactNativeEventSink(
44
44
  })
45
45
  }
46
46
 
47
+ fun storeTokenExpired(objectId: Int) {
48
+ emit(base("storeTokenExpired", objectId))
49
+ }
50
+
51
+ fun storeReplayTimeChanged(objectId: Int, timestampMs: Long) {
52
+ emit(base("storeReplayTimeChanged", objectId).apply {
53
+ putDouble("timestampMs", timestampMs.toDouble())
54
+ })
55
+ }
56
+
57
+ fun storeReplayCompleted(objectId: Int) {
58
+ emit(base("storeReplayCompleted", objectId))
59
+ }
60
+
61
+ fun storeReplayError(objectId: Int, code: Int) {
62
+ emit(base("storeReplayError", objectId).apply {
63
+ putInt("code", TiRtcReactNativeErrors.normalizeAndroid(code))
64
+ })
65
+ }
66
+
47
67
  fun audioOutputStateChanged(
48
68
  objectId: Int,
49
69
  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.TiStoreRecordingRange
6
+ import com.tange.ai.tirtc.TiStoreRecordingDay
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 storeRecordingsResult(code: Int, ranges: List<TiStoreRecordingRange>): 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 storeRecordingDaysResult(code: Int, days: List<TiStoreRecordingDay>): 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 storeReplayInfoResult(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,
@@ -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
- Log.w(tag, "uploadLogs timeout")
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.TiStore
10
+ import com.tange.ai.tirtc.TiStoreAudioOutput
11
+ import com.tange.ai.tirtc.TiStoreRecordingTask
12
+ import com.tange.ai.tirtc.TiStoreReplay
13
+ import com.tange.ai.tirtc.TiStoreVideoOutput
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 store(id: Int): TiStore? = entries[id]?.value as? TiStore
67
+
68
+ fun storeReplay(id: Int): TiStoreReplay? = entries[id]?.value as? TiStoreReplay
69
+
70
+ fun storeRecordingTask(id: Int): TiStoreRecordingTask? = entries[id]?.value as? TiStoreRecordingTask
71
+
72
+ fun storeExportTask(id: Int): StoreExportBridge? = entries[id]?.value as? StoreExportBridge
73
+
74
+ fun storeAudioOutput(id: Int): TiStoreAudioOutput? = entries[id]?.value as? TiStoreAudioOutput
75
+
76
+ fun storeVideoOutput(id: Int): TiStoreVideoOutput? = entries[id]?.value as? TiStoreVideoOutput
77
+
51
78
  fun reportVideoOutputError(
52
79
  id: Int,
53
80
  code: Int,
@@ -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.TiStoreExportTask
5
+ import com.tange.ai.tirtc.TiStoreRecordingResult
6
+
7
+ internal class StoreExportBridge(
8
+ private val resolve: (Promise, TiStoreRecordingResult) -> Unit,
9
+ ) {
10
+ @Volatile var task: TiStoreExportTask? = null
11
+ @Volatile var onDrained: (() -> Unit)? = null
12
+ private var result: TiStoreRecordingResult? = null
13
+ private val waiters = mutableListOf<Promise>()
14
+ private var invalidated = false
15
+ private var invalidationCompletion: ((TiStoreRecordingResult?) -> Unit)? = null
16
+
17
+ fun complete(value: TiStoreRecordingResult) {
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: (TiStoreRecordingResult?) -> 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
- TiRtcReactNativeRegistry.videoOutput(id)?.detachView()
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
- TiRtcReactNativeRegistry.videoOutput(attachedId)?.detachView()
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.storeVideoOutput(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.storeVideoOutput(id)?.detachView()
90
105
  }
91
106
 
92
107
  private fun resizeModeForView(view: FrameLayout): TiRtcVideoViewResizeMode =