rns-recplay 1.2.7

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 (124) hide show
  1. package/README.md +172 -0
  2. package/android/build/.transforms/cdad66fa505a583e6d3a40af343cb85b/results.bin +1 -0
  3. package/android/build/.transforms/cdad66fa505a583e6d3a40af343cb85b/transformed/classes/classes_dex/classes.dex +0 -0
  4. package/android/build/generated/source/buildConfig/debug/com/rnsrecplay/BuildConfig.java +10 -0
  5. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +7 -0
  6. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  7. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  8. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  9. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  10. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  11. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  12. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
  13. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  14. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  15. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  16. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -0
  17. package/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/rns-recplay_debug.kotlin_module +0 -0
  18. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/rnsrecplay/BuildConfig.class +0 -0
  19. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
  20. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +7 -0
  21. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +7 -0
  22. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  23. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  24. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  25. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  57. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  58. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  59. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  60. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
  61. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  62. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  63. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  64. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  65. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  66. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  67. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
  68. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  69. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
  70. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
  71. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
  72. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  73. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
  74. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
  75. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  76. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  77. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  78. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
  79. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  80. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  81. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
  82. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +2 -0
  83. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  84. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  85. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  86. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  87. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  88. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  89. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
  90. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  91. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  92. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  93. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  94. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  95. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  96. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  97. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  98. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  99. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  100. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  101. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  102. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  103. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
  104. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  105. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  106. package/android/build/outputs/logs/manifest-merger-debug-report.txt +16 -0
  107. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  108. package/android/build/tmp/kotlin-classes/debug/META-INF/rns-recplay_debug.kotlin_module +0 -0
  109. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule$playAudio$1$1.class +0 -0
  110. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule$playbackRunnable$1.class +0 -0
  111. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule$timerRunnable$1.class +0 -0
  112. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule.class +0 -0
  113. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayPackage.class +0 -0
  114. package/android/build.gradle +67 -0
  115. package/android/src/main/java/com/rnsrecplay/RecPlayModule.kt +291 -0
  116. package/android/src/main/java/com/rnsrecplay/RecPlayPackage.kt +15 -0
  117. package/app.plugin.js +1 -0
  118. package/index.d.ts +34 -0
  119. package/index.js +67 -0
  120. package/ios/RecPlayModule.m +29 -0
  121. package/ios/RecPlayModule.swift +177 -0
  122. package/package.json +52 -0
  123. package/react-native.config.js +12 -0
  124. package/rns-recplay.podspec +23 -0
@@ -0,0 +1,291 @@
1
+ package com.rnsrecplay
2
+
3
+ import android.Manifest
4
+ import android.content.pm.PackageManager
5
+ import android.media.MediaRecorder
6
+ import android.net.Uri
7
+ import android.os.Build
8
+ import android.os.Handler
9
+ import android.os.Looper
10
+ import android.util.Log
11
+ import androidx.core.content.ContextCompat
12
+ import com.facebook.react.bridge.*
13
+ import com.facebook.react.modules.core.DeviceEventManagerModule
14
+ import com.google.android.exoplayer2.ExoPlayer
15
+ import com.google.android.exoplayer2.MediaItem
16
+ import com.google.android.exoplayer2.Player // Add this import
17
+ import java.io.File
18
+
19
+ class RecPlayModule(
20
+ private val reactContext: ReactApplicationContext,
21
+ ) : ReactContextBaseJavaModule(reactContext) {
22
+ // Add this line to fix the compilation error
23
+ private val TAG = "RecPlayDebug"
24
+
25
+ private var recorder: MediaRecorder? = null
26
+ private var player: ExoPlayer? = null
27
+ private var isPaused = false
28
+ private var secondsElapsed = 0
29
+ private val handler = Handler(Looper.getMainLooper())
30
+ private var playbackHandler = Handler(Looper.getMainLooper())
31
+ private var currentFilePath: String? = null
32
+
33
+ override fun getName(): String = "RecPlayModule"
34
+
35
+ private val playbackRunnable =
36
+ object : Runnable {
37
+ override fun run() {
38
+ player?.let {
39
+ if (it.isPlaying) {
40
+ val params = Arguments.createMap()
41
+ params.putDouble("currentPosition", it.currentPosition.toDouble() / 1000.0)
42
+ params.putDouble("duration", it.duration.toDouble() / 1000.0)
43
+
44
+ reactContext
45
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
46
+ .emit("onPlaybackProgress", params)
47
+ }
48
+ playbackHandler.postDelayed(this, 500) // Update every 500ms
49
+ }
50
+ }
51
+ }
52
+
53
+ private val timerRunnable =
54
+ object : Runnable {
55
+ override fun run() {
56
+ if (recorder == null) return
57
+ if (!isPaused) {
58
+ val params = Arguments.createMap()
59
+ params.putInt("seconds", secondsElapsed)
60
+ reactContext
61
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
62
+ .emit("onTimerUpdate", params)
63
+ secondsElapsed++
64
+ }
65
+ handler.postDelayed(this, 1000)
66
+ }
67
+ }
68
+
69
+ // 1. Add this new explicit stop method
70
+ @ReactMethod
71
+ fun stopPlayback(promise: Promise?) {
72
+ handler.post {
73
+ try {
74
+ player?.let {
75
+ if (it.isPlaying) {
76
+ it.pause()
77
+ }
78
+ it.stop()
79
+ it.clearMediaItems()
80
+ }
81
+ promise?.resolve(true)
82
+ } catch (e: Exception) {
83
+ promise?.reject("STOP_PLAY_ERROR", e.message)
84
+ }
85
+ }
86
+ }
87
+
88
+ // 2. Update startRecording to include playback cleanup
89
+ @ReactMethod
90
+ fun startRecording(
91
+ fileName: String?,
92
+ shouldStopPlayback: Boolean, // New parameter
93
+ promise: Promise,
94
+ ) {
95
+ handler.post {
96
+ try {
97
+ // 1. Conditional Playback Cleanup
98
+ if (shouldStopPlayback) {
99
+ Log.d(TAG, "shouldStopPlayback is true: Stopping player...")
100
+ player?.let {
101
+ if (it.isPlaying) it.pause()
102
+ it.stop()
103
+ it.clearMediaItems()
104
+ }
105
+ }
106
+
107
+ // 2. Standard Recorder Cleanup
108
+ Log.d(TAG, "Cleaning up old recorder...")
109
+ handler.removeCallbacks(timerRunnable)
110
+ recorder?.release()
111
+ recorder = null
112
+
113
+ // 3. File Setup
114
+ val name = fileName ?: "rec_${System.currentTimeMillis()}"
115
+ val file = File(reactContext.cacheDir, "$name.m4a")
116
+ if (file.exists()) file.delete()
117
+ currentFilePath = file.absolutePath
118
+
119
+ // 4. Recorder Initialization
120
+ val context = currentActivity ?: reactContext
121
+ recorder =
122
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
123
+ MediaRecorder(context)
124
+ } else {
125
+ @Suppress("DEPRECATION")
126
+ MediaRecorder()
127
+ }.apply {
128
+ setAudioSource(MediaRecorder.AudioSource.MIC)
129
+ setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
130
+ setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
131
+ setAudioEncodingBitRate(128000)
132
+ setAudioSamplingRate(44100)
133
+ setOutputFile(currentFilePath)
134
+ prepare()
135
+ start()
136
+ }
137
+
138
+ secondsElapsed = 0
139
+ isPaused = false
140
+ handler.post(timerRunnable)
141
+ promise.resolve(name)
142
+ } catch (e: Exception) {
143
+ Log.e(TAG, "Start Error: ${e.message}")
144
+ recorder?.release()
145
+ recorder = null
146
+ promise.reject("REC_ERROR", e.message)
147
+ }
148
+ }
149
+ }
150
+
151
+ @ReactMethod
152
+ fun stopRecording(promise: Promise) {
153
+ Log.d(TAG, "Stop requested")
154
+ handler.removeCallbacks(timerRunnable)
155
+ try {
156
+ recorder?.apply {
157
+ stop()
158
+ release()
159
+ }
160
+ recorder = null
161
+ isPaused = false
162
+ secondsElapsed = 0
163
+ promise.resolve("file://$currentFilePath")
164
+ } catch (e: Exception) {
165
+ Log.e(TAG, "Stop Error: ${e.message}")
166
+ recorder?.release()
167
+ recorder = null
168
+ promise.reject("STOP_ERROR", e.message)
169
+ }
170
+ }
171
+
172
+ @ReactMethod
173
+ fun pauseRecording(promise: Promise) {
174
+ try {
175
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
176
+ recorder?.pause()
177
+ isPaused = true
178
+ }
179
+ promise.resolve(true)
180
+ } catch (e: Exception) {
181
+ promise.reject("PAUSE_ERROR", e.message)
182
+ }
183
+ }
184
+
185
+ @ReactMethod
186
+ fun resumeRecording(promise: Promise) {
187
+ try {
188
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
189
+ recorder?.resume()
190
+ isPaused = false
191
+ }
192
+ promise.resolve(true)
193
+ } catch (e: Exception) {
194
+ promise.reject("RESUME_ERROR", e.message)
195
+ }
196
+ }
197
+
198
+ @ReactMethod
199
+ fun playAudio(
200
+ uriString: String,
201
+ shouldStopPrevious: Boolean,
202
+ loop: Boolean, // Added parameter
203
+ ) {
204
+ handler.post {
205
+ try {
206
+ if (player == null) {
207
+ player = ExoPlayer.Builder(reactContext).build()
208
+ player?.addListener(
209
+ object : Player.Listener {
210
+ override fun onPlaybackStateChanged(state: Int) {
211
+ val params = Arguments.createMap()
212
+ val status =
213
+ when (state) {
214
+ Player.STATE_BUFFERING -> "BUFFERING"
215
+ Player.STATE_READY -> if (player?.isPlaying == true) "PLAYING" else "PAUSED"
216
+ Player.STATE_ENDED -> "ENDED"
217
+ else -> "IDLE"
218
+ }
219
+ params.putString("status", status)
220
+ reactContext
221
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
222
+ .emit("onPlaybackStatus", params)
223
+
224
+ if (state == Player.STATE_READY && player?.isPlaying == true) {
225
+ playbackHandler.post(playbackRunnable)
226
+ } else if (state == Player.STATE_ENDED) {
227
+ playbackHandler.removeCallbacks(playbackRunnable)
228
+ val finishParams = Arguments.createMap()
229
+ finishParams.putBoolean("finished", true)
230
+ reactContext
231
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
232
+ .emit("onPlaybackFinished", finishParams)
233
+ }
234
+ }
235
+
236
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
237
+ val params = Arguments.createMap()
238
+ params.putString("status", if (isPlaying) "PLAYING" else "PAUSED")
239
+ reactContext
240
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
241
+ .emit("onPlaybackStatus", params)
242
+
243
+ if (isPlaying) {
244
+ playbackHandler.post(playbackRunnable)
245
+ } else {
246
+ playbackHandler.removeCallbacks(playbackRunnable)
247
+ }
248
+ }
249
+ },
250
+ )
251
+ } else if (shouldStopPrevious) {
252
+ playbackHandler.removeCallbacks(playbackRunnable)
253
+ player?.stop()
254
+ player?.clearMediaItems()
255
+ }
256
+
257
+ val mediaItem = MediaItem.fromUri(Uri.parse(uriString))
258
+ player?.apply {
259
+ // This is the magic line for native looping
260
+ repeatMode = if (loop) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF
261
+
262
+ setMediaItem(mediaItem)
263
+ prepare()
264
+ play()
265
+ }
266
+ } catch (e: Exception) {
267
+ Log.e(TAG, "Playback Error: ${e.message}")
268
+ }
269
+ }
270
+ }
271
+
272
+ @ReactMethod
273
+ fun seekTo(seconds: Double) {
274
+ handler.post {
275
+ player?.seekTo((seconds * 1000).toLong())
276
+ }
277
+ }
278
+
279
+ @ReactMethod
280
+ fun togglePlayback() {
281
+ handler.post {
282
+ player?.let { if (it.isPlaying) it.pause() else it.play() }
283
+ }
284
+ }
285
+
286
+ override fun onCatalystInstanceDestroy() {
287
+ handler.removeCallbacks(timerRunnable)
288
+ recorder?.release()
289
+ player?.release()
290
+ }
291
+ }
@@ -0,0 +1,15 @@
1
+ package com.rnsrecplay
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+ import java.util.Collections
8
+
9
+ class RecPlayPackage : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = listOf(RecPlayModule(reactContext))
11
+
12
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
13
+ return emptyList() // We don't have any UI components, just logic
14
+ }
15
+ }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./withNativeRecPlay');
package/index.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ export type PlaybackStatus = "BUFFERING" | "PLAYING" | "PAUSED" | "ENDED" | "IDLE";
2
+
3
+ declare const Recplay: {
4
+ startRecording: (
5
+ fileName?: string | null,
6
+ shouldStopPlayback?: boolean,
7
+ onSecondsUpdate?: (seconds: number) => void
8
+ ) => Promise<string>;
9
+
10
+ stopRecording: () => Promise<string>;
11
+ pauseRecording: () => Promise<boolean>;
12
+ resumeRecording: () => Promise<boolean>;
13
+ seekTo: (seconds: number) => void;
14
+
15
+ /**
16
+ * Plays audio with optional status and progress tracking.
17
+ * Order: uri, shouldStopPrevious, callbacks
18
+ */
19
+ playAudio: (
20
+ uri: string,
21
+ shouldStopPrevious?: boolean,
22
+ loop?: boolean,
23
+ callbacks?: {
24
+ onPlaybackFinished?: () => void;
25
+ onStatus?: (status: PlaybackStatus) => void;
26
+ onProgress?: (currentPosition: number, duration: number) => void;
27
+ }
28
+ ) => void;
29
+
30
+ stopPlayback: () => Promise<boolean>;
31
+ togglePlayback: () => void;
32
+ };
33
+
34
+ export default Recplay;
package/index.js ADDED
@@ -0,0 +1,67 @@
1
+ import { NativeModules, NativeEventEmitter } from 'react-native';
2
+
3
+ const { RecPlayModule } = NativeModules;
4
+ const eventEmitter = new NativeEventEmitter(RecPlayModule);
5
+
6
+ let internalSub = null;
7
+ let internalPlaySub = null;
8
+ let internalStatusSub = null;
9
+ let internalProgressSub = null;
10
+
11
+ const Recplay = {
12
+ startRecording: async (fileName = null, shouldStopPlayback = true, onSecondsUpdate = null) => {
13
+ if (internalSub) internalSub.remove();
14
+ if (onSecondsUpdate) {
15
+ internalSub = eventEmitter.addListener('onTimerUpdate', (data) => onSecondsUpdate(data.seconds));
16
+ }
17
+ return RecPlayModule.startRecording(fileName, shouldStopPlayback);
18
+ },
19
+
20
+ stopRecording: async () => {
21
+ if (internalSub) { internalSub.remove(); internalSub = null; }
22
+ return RecPlayModule.stopRecording();
23
+ },
24
+
25
+ /**
26
+ * @param {string} uri
27
+ * @param {boolean} shouldStopPrevious
28
+ * @param {boolean} loop - New native loop flag
29
+ * @param {object} callbacks
30
+ */
31
+ playAudio: (uri, shouldStopPrevious = false, loop = false, callbacks = {}) => {
32
+ // Cleanup listeners
33
+ [internalPlaySub, internalStatusSub, internalProgressSub].forEach(s => s?.remove());
34
+
35
+ if (callbacks.onFinished) {
36
+ internalPlaySub = eventEmitter.addListener('onPlaybackFinished', () => {
37
+ callbacks.onFinished();
38
+ internalPlaySub?.remove();
39
+ });
40
+ }
41
+
42
+ if (callbacks.onStatus) {
43
+ internalStatusSub = eventEmitter.addListener('onPlaybackStatus', (data) => callbacks.onStatus(data.status));
44
+ }
45
+
46
+ if (callbacks.onProgress) {
47
+ internalProgressSub = eventEmitter.addListener('onPlaybackProgress', (data) =>
48
+ callbacks.onProgress(data.currentPosition, data.duration)
49
+ );
50
+ }
51
+
52
+ // Pass all three arguments to Native
53
+ RecPlayModule.playAudio(uri, shouldStopPrevious, loop);
54
+ },
55
+
56
+ stopPlayback: () => {
57
+ [internalPlaySub, internalStatusSub, internalProgressSub].forEach(s => s?.remove());
58
+ return RecPlayModule.stopPlayback();
59
+ },
60
+
61
+ seekTo: (seconds) => RecPlayModule.seekTo(seconds),
62
+ pauseRecording: () => RecPlayModule.pauseRecording(),
63
+ resumeRecording: () => RecPlayModule.resumeRecording(),
64
+ togglePlayback: () => RecPlayModule.togglePlayback()
65
+ };
66
+
67
+ export default Recplay;
@@ -0,0 +1,29 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE (RecPlayModule, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(startRecording : (NSString *)fileName shouldStopPlayback : (
7
+ BOOL)shouldStopPlayback resolver : (RCTPromiseResolveBlock)
8
+ resolve rejecter : (RCTPromiseRejectBlock)reject)
9
+
10
+ RCT_EXTERN_METHOD(stopRecording : (RCTPromiseResolveBlock)
11
+ resolve rejecter : (RCTPromiseRejectBlock)reject)
12
+
13
+ RCT_EXTERN_METHOD(pauseRecording : (RCTPromiseResolveBlock)
14
+ resolve rejecter : (RCTPromiseRejectBlock)reject)
15
+
16
+ RCT_EXTERN_METHOD(resumeRecording : (RCTPromiseResolveBlock)
17
+ resolve rejecter : (RCTPromiseRejectBlock)reject)
18
+
19
+ RCT_EXTERN_METHOD(playAudio : (NSString *)uri shouldStopPrevious : (BOOL)
20
+ shouldStopPrevious loop : (BOOL)loop)
21
+
22
+ RCT_EXTERN_METHOD(stopPlayback : (RCTPromiseResolveBlock)
23
+ resolve rejecter : (RCTPromiseRejectBlock)reject)
24
+
25
+ RCT_EXTERN_METHOD(seekTo : (double)seconds)
26
+
27
+ RCT_EXTERN_METHOD(togglePlayback)
28
+
29
+ @end
@@ -0,0 +1,177 @@
1
+ import Foundation
2
+ import AVFoundation
3
+ import React
4
+
5
+ @objc(RecPlayModule)
6
+ class RecPlayModule: RCTEventEmitter {
7
+
8
+ private var audioRecorder: AVAudioRecorder?
9
+ private var audioPlayer: AVPlayer?
10
+ private var playerItemContext = 0
11
+ private var timeObserverToken: Any?
12
+
13
+ private var recordingTimer: Timer?
14
+ private var secondsElapsed = 0
15
+ private var isPaused = false
16
+ private var isLooping = false
17
+
18
+ override static func requiresMainQueueSetup() -> Bool {
19
+ return true
20
+ }
21
+
22
+ override func supportedEvents() -> [String]! {
23
+ return ["onTimerUpdate", "onPlaybackStatus", "onPlaybackProgress", "onPlaybackFinished"]
24
+ }
25
+
26
+ // MARK: - Recording Logic
27
+
28
+ @objc(startRecording:shouldStopPlayback:resolver:rejecter:)
29
+ func startRecording(fileName: String?, shouldStopPlayback: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
30
+
31
+ if shouldStopPlayback {
32
+ stopPlaybackInternal()
33
+ }
34
+
35
+ let session = AVAudioSession.sharedInstance()
36
+ do {
37
+ // Set category for both recording and playback
38
+ try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth])
39
+ try session.setActive(true)
40
+
41
+ let name = fileName ?? "rec_\(Int(Date().timeIntervalSince1970))"
42
+ let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(name).m4a")
43
+
44
+ let settings: [String: Any] = [
45
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
46
+ AVSampleRateKey: 44100,
47
+ AVNumberOfChannelsKey: 1,
48
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
49
+ AVEncoderBitRateKey: 128000
50
+ ]
51
+
52
+ audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings)
53
+ audioRecorder?.prepareToRecord()
54
+ audioRecorder?.record()
55
+
56
+ self.secondsElapsed = 0
57
+ self.isPaused = false
58
+
59
+ DispatchQueue.main.async {
60
+ self.recordingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
61
+ if !self.isPaused {
62
+ self.sendEvent(withName: "onTimerUpdate", body: ["seconds": self.secondsElapsed])
63
+ self.secondsElapsed += 1
64
+ }
65
+ }
66
+ }
67
+
68
+ resolve(name)
69
+ } catch {
70
+ reject("REC_ERROR", "Failed to start recording", error)
71
+ }
72
+ }
73
+
74
+ @objc(stopRecording:rejecter:)
75
+ func stopRecording(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
76
+ recordingTimer?.invalidate()
77
+ recordingTimer = nil
78
+
79
+ audioRecorder?.stop()
80
+ let url = audioRecorder?.url
81
+ audioRecorder = nil
82
+
83
+ if let fileUrl = url {
84
+ resolve(fileUrl.absoluteString)
85
+ } else {
86
+ reject("STOP_ERROR", "No recording found", nil)
87
+ }
88
+ }
89
+
90
+ @objc(pauseRecording:rejecter:)
91
+ func pauseRecording(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
92
+ audioRecorder?.pause()
93
+ isPaused = true
94
+ resolve(true)
95
+ }
96
+
97
+ @objc(resumeRecording:rejecter:)
98
+ func resumeRecording(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
99
+ audioRecorder?.record()
100
+ isPaused = false
101
+ resolve(true)
102
+ }
103
+
104
+ // MARK: - Playback Logic
105
+
106
+ @objc(playAudio:shouldStopPrevious:loop:)
107
+ func playAudio(uri: String, shouldStopPrevious: Bool, loop: Bool) {
108
+ if shouldStopPrevious {
109
+ stopPlaybackInternal()
110
+ }
111
+
112
+ guard let url = URL(string: uri) else { return }
113
+ self.isLooping = loop
114
+
115
+ let playerItem = AVPlayerItem(url: url)
116
+ audioPlayer = AVPlayer(playerItem: playerItem)
117
+
118
+ // Add Observer for Progress
119
+ let interval = CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
120
+ timeObserverToken = audioPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
121
+ guard let self = self, let duration = self.audioPlayer?.currentItem?.duration.seconds, duration > 0 else { return }
122
+ self.sendEvent(withName: "onPlaybackProgress", body: [
123
+ "currentPosition": time.seconds,
124
+ "duration": duration
125
+ ])
126
+ }
127
+
128
+ // Notification for Finished
129
+ NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
130
+
131
+ audioPlayer?.play()
132
+ sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
133
+ }
134
+
135
+ @objc func playerDidFinishPlaying(note: NSNotification) {
136
+ if isLooping {
137
+ audioPlayer?.seek(to: .zero)
138
+ audioPlayer?.play()
139
+ } else {
140
+ sendEvent(withName: "onPlaybackFinished", body: ["finished": true])
141
+ sendEvent(withName: "onPlaybackStatus", body: ["status": "ENDED"])
142
+ }
143
+ }
144
+
145
+ @objc(stopPlayback:rejecter:)
146
+ func stopPlayback(resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) {
147
+ stopPlaybackInternal()
148
+ resolve?(true)
149
+ }
150
+
151
+ private func stopPlaybackInternal() {
152
+ if let token = timeObserverToken {
153
+ audioPlayer?.removeTimeObserver(token)
154
+ timeObserverToken = nil
155
+ }
156
+ audioPlayer?.pause()
157
+ audioPlayer = nil
158
+ NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
159
+ }
160
+
161
+ @objc(seekTo:)
162
+ func seekTo(seconds: Double) {
163
+ let time = CMTime(seconds: seconds, preferredTimescale: 1000)
164
+ audioPlayer?.seek(to: time)
165
+ }
166
+
167
+ @objc(togglePlayback)
168
+ func togglePlayback() {
169
+ if audioPlayer?.timeControlStatus == .playing {
170
+ audioPlayer?.pause()
171
+ sendEvent(withName: "onPlaybackStatus", body: ["status": "PAUSED"])
172
+ } else {
173
+ audioPlayer?.play()
174
+ sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
175
+ }
176
+ }
177
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "rns-recplay",
3
+ "version": "1.2.7",
4
+ "description": "High-performance React Native module for audio recording and audio playback on Android and iOS.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "expo": {
8
+ "autolinking": {
9
+ "ios": {
10
+ "podspec": "rns-recplay.podspec"
11
+ }
12
+ },
13
+ "plugin": "./app.plugin.js"
14
+ },
15
+ "homepage": "https://github.com/raiidr/rns-recplay",
16
+ "license": "MIT",
17
+ "author": "Raiidr <info@raiidr.com>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/raiidr/rns-recplay.git"
21
+ },
22
+ "scripts": {
23
+ "p": "npm publish --access public"
24
+ },
25
+ "keywords": [
26
+ "react-native",
27
+ "recplay",
28
+ "recoridng",
29
+ "audio",
30
+ "android",
31
+ "ios",
32
+ "expo",
33
+ "config-plugin"
34
+ ],
35
+ "files": [
36
+ "android",
37
+ "ios",
38
+ "app.plugin.js",
39
+ "index.d.ts",
40
+ "index.js",
41
+ "react-native.config.js",
42
+ "rns-recplay.podspec",
43
+ "withrecplayVoip.js"
44
+ ],
45
+ "peerDependencies": {
46
+ "expo": ">=45.0.0",
47
+ "react-native": ">=0.60.0"
48
+ },
49
+ "dependencies": {
50
+ "@expo/config-plugins": "^9.0.0"
51
+ }
52
+ }
@@ -0,0 +1,12 @@
1
+ ///Users/bush/Desktop/Apps/Raiidr/package/react-native.config.js
2
+ module.exports = {
3
+ dependencies: {
4
+ 'rns-nativecall': {
5
+ platforms: {
6
+ ios: {
7
+ podspecPath: 'rns-nativecall.podspec',
8
+ },
9
+ },
10
+ },
11
+ },
12
+ };