rns-recplay 1.2.7 → 1.2.9

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 (117) hide show
  1. package/README.md +105 -79
  2. package/ios/RecPlayModule.m +19 -16
  3. package/ios/RecPlayModule.swift +64 -29
  4. package/package.json +2 -2
  5. package/withNativeRecPlay.js +43 -0
  6. package/android/build/.transforms/cdad66fa505a583e6d3a40af343cb85b/results.bin +0 -1
  7. package/android/build/.transforms/cdad66fa505a583e6d3a40af343cb85b/transformed/classes/classes_dex/classes.dex +0 -0
  8. package/android/build/generated/source/buildConfig/debug/com/rnsrecplay/BuildConfig.java +0 -10
  9. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
  10. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  11. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  12. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
  13. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  14. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  15. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  16. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
  17. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  18. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
  19. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
  20. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +0 -2
  21. package/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/rns-recplay_debug.kotlin_module +0 -0
  22. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/rnsrecplay/BuildConfig.class +0 -0
  23. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
  24. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
  25. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
  26. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  27. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  28. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  29. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  57. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
  58. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  59. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  60. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  61. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  62. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  63. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  64. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
  65. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  66. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  67. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  68. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  69. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  70. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  71. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
  72. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  73. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
  74. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
  75. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
  76. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  77. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
  78. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
  79. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  80. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  81. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  82. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
  83. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  84. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  85. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
  86. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +0 -2
  87. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  88. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  89. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  90. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  91. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  92. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  93. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
  94. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  95. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  96. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  97. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  98. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  99. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  100. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  101. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  102. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  103. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  104. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  105. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  106. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  107. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
  108. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  109. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  110. package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -16
  111. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  112. package/android/build/tmp/kotlin-classes/debug/META-INF/rns-recplay_debug.kotlin_module +0 -0
  113. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule$playAudio$1$1.class +0 -0
  114. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule$playbackRunnable$1.class +0 -0
  115. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule$timerRunnable$1.class +0 -0
  116. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayModule.class +0 -0
  117. package/android/build/tmp/kotlin-classes/debug/com/rnsrecplay/RecPlayPackage.class +0 -0
package/README.md CHANGED
@@ -1,63 +1,65 @@
1
- # rns-recplay
1
+ # 🎤 rns-recplay
2
2
 
3
- A high-performance React Native module for audio recording and playback with native support for looping, progress tracking, and automatic playback interruption.
3
+ A **high-performance React Native audio recording and playback module** with native-level control.
4
+ Designed for **voice notes, chats, and media apps**, offering smooth looping, precise progress tracking, and automatic playback interruption.
4
5
 
5
6
  ---
6
7
 
7
- ## Table of Contents
8
+ ## Features
8
9
 
9
- - [Features](#features)
10
- - [Installation](#installation)
11
- - [Requirements](#requirements)
12
- - [Permissions (Crucial)](#permissions-crucial)
13
- - [Usage](#usage)
14
- - [Recording Example](#recording-example)
15
- - [Playback Example (with Looping)](#playback-example-with-looping)
16
- - [API Reference](#api-reference)
17
- - [Recording API](#recording-api)
18
- - [Playback API](#playback-api)
19
- - [Status Types](#status-types)
10
+ - 🎙️ **Audio Recording**
11
+ - High-quality AAC / M4A format
12
+ - Real-time recording timer
13
+ - Pause & resume support
20
14
 
21
- ---
15
+ - 🔊 **Audio Playback**
16
+ - Powered by **ExoPlayer (Android)** and **AVPlayer (iOS)**
17
+ - Native-level performance
18
+
19
+ - 🔄 **Seamless Looping**
20
+ - Gapless looping handled natively
22
21
 
23
- ## Features
22
+ - 📊 **Progress Tracking**
23
+ - Position & duration updates every **500ms**
24
24
 
25
- - 🎙️ **Recording**: High-quality AAC/M4A recording with real-time timer updates.
26
- - 🔊 **Playback**: Powered by ExoPlayer (Android) and AVPlayer (iOS).
27
- - 🔄 **Native Looping**: Seamless looping handled at the native level.
28
- - 📊 **Progress Tracking**: Real-time position and duration updates (every 500ms).
29
- - 🛡️ **Auto-Cleanup**: Option to automatically stop playback when starting a recording.
25
+ - 🛡️ **Smart Audio Control**
26
+ - Automatically stops playback when recording begins
27
+ - Prevents overlapping audio sessions
30
28
 
31
29
  ---
32
30
 
33
- ## Installation
31
+ ## 📦 Installation
34
32
 
35
- ### Requirements
33
+ ```bash
34
+ npm install rns-recplay
35
+ ```
36
36
 
37
- - **Android**: minSdkVersion 21 or higher
38
- - **iOS**: iOS 11.0 or higher
37
+ or (Expo):
39
38
 
40
- ### Permissions (Crucial)
39
+ ```bash
40
+ npx expo install rns-recplay
41
+ ```
41
42
 
42
- #### Android (`AndroidManifest.xml`)
43
+ ---
43
44
 
44
- ```xml
45
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
46
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
47
- ```
45
+ ## ⚙️ Expo Configuration
48
46
 
49
- #### iOS (`Info.plist`)
47
+ Add the plugin to your **app.json** or **app.config.json**:
50
48
 
51
- ```xml
52
- <key>NSMicrophoneUsageDescription</key>
53
- <string>We need access to your microphone to record voice notes.</string>
49
+ ```json
50
+ [
51
+ "rns-recplay",
52
+ {
53
+ "microphonePermission": "Microphone is used strictly for voice messages and live audio or video interactions. Tap 'Allow' to enable microphone usage when needed."
54
+ }
55
+ ]
54
56
  ```
55
57
 
56
58
  ---
57
59
 
58
- ## Usage
60
+ ## 🚀 Usage
59
61
 
60
- ### Recording Example
62
+ ### 🎙️ Recording Example
61
63
 
62
64
  ```js
63
65
  import Recplay from 'rns-recplay';
@@ -65,10 +67,11 @@ import Recplay from 'rns-recplay';
65
67
  const startMyRecording = async () => {
66
68
  try {
67
69
  const fileName = await Recplay.startRecording(
68
- "my_voice_note",
69
- true,
70
+ "my_voice_note", // optional file name (null for auto-generated)
71
+ true, // stop any playing audio
70
72
  (seconds) => console.log(`Recorded: ${seconds}s`)
71
73
  );
74
+
72
75
  console.log("Recording started:", fileName);
73
76
  } catch (err) {
74
77
  console.error(err);
@@ -83,90 +86,113 @@ const stopMyRecording = async () => {
83
86
 
84
87
  ---
85
88
 
86
- ### Playback Example (with Looping)
89
+ ### 🔊 Playback Example (Looping Enabled)
87
90
 
88
91
  ```js
89
92
  import Recplay from 'rns-recplay';
90
93
 
91
94
  Recplay.playAudio(
92
95
  "file:///path/to/audio.m4a",
93
- true,
94
- true,
96
+ true, // stop previous playback
97
+ true, // loop audio
95
98
  {
96
99
  onStatus: (status) => console.log("Status:", status),
97
- onProgress: (pos, dur) => console.log(`Progress: ${pos} / ${dur}`),
98
- onFinished: () => console.log("Done!")
100
+ onProgress: (position, duration) =>
101
+ console.log(`Progress: ${position} / ${duration}`),
102
+ onFinished: () => console.log("Playback finished"),
99
103
  }
100
104
  );
101
105
  ```
102
106
 
103
107
  ---
104
108
 
105
- ## API Reference
109
+ ## 📚 API Reference
106
110
 
107
- ### Recording API
111
+ ### 🎙️ Recording
108
112
 
109
- #### startRecording(fileName?, shouldStopPlayback?, onSecondsUpdate?)
113
+ #### `startRecording(fileName?, shouldStopPlayback?, onSecondsUpdate?)`
110
114
 
111
- | Param | Type | Default | Description |
112
- |--------------------|----------|---------|-------------|
113
- | fileName | string | null | Custom name for the `.m4a` file |
114
- | shouldStopPlayback | boolean | true | Stops any active audio player before recording |
115
- | onSecondsUpdate | function | null | Callback fired every second |
115
+ | Parameter | Type | Default | Description |
116
+ |--------|------|---------|------------|
117
+ | `fileName` | `string` | `null` | Custom `.m4a` file name |
118
+ | `shouldStopPlayback` | `boolean` | `true` | Stops any playing audio |
119
+ | `onSecondsUpdate` | `function` | `null` | Called every second |
120
+
121
+ **Returns:** `Promise<string>` (file name)
116
122
 
117
123
  ---
118
124
 
119
- #### stopRecording()
125
+ #### `stopRecording()`
120
126
 
121
- Stops the recording and releases the microphone.
127
+ Stops recording and releases the microphone.
122
128
 
123
- **Returns:** Promise<string>
129
+ **Returns:** `Promise<string>` (file URI)
124
130
 
125
131
  ---
126
132
 
127
- #### pauseRecording() / resumeRecording()
133
+ #### `pauseRecording()`
134
+ Pauses the active recording session.
128
135
 
129
- Pauses or resumes the current recording session.
136
+ #### `resumeRecording()`
137
+ Resumes a paused recording session.
130
138
 
131
139
  ---
132
140
 
133
- ### Playback API
141
+ ### 🔊 Playback
142
+
143
+ #### `playAudio(uri, shouldStopPrevious?, loop?, callbacks?)`
134
144
 
135
- #### playAudio(uri, shouldStopPrevious?, loop?, callbacks?)
145
+ | Parameter | Type | Default | Description |
146
+ |--------|------|---------|------------|
147
+ | `uri` | `string` | — | Audio file URI |
148
+ | `shouldStopPrevious` | `boolean` | `false` | Stops previous playback |
149
+ | `loop` | `boolean` | `false` | Enables native looping |
150
+ | `callbacks` | `object` | `{}` | Playback event callbacks |
136
151
 
137
- | Param | Type | Default | Description |
138
- |-------------------|---------|---------|-------------|
139
- | uri | string | Required | Audio URI |
140
- | shouldStopPrevious| boolean | false | Stops previous playback |
141
- | loop | boolean | false | Native looping |
142
- | callbacks | object | `{}` | onStatus, onProgress, onFinished |
152
+ ##### Callback Options
153
+
154
+ | Callback | Params | Description |
155
+ |-------|-------|-------------|
156
+ | `onStatus` | `(status)` | Player state updates |
157
+ | `onProgress` | `(position, duration)` | Playback progress |
158
+ | `onFinished` | `()` | Fired when playback ends |
143
159
 
144
160
  ---
145
161
 
146
- #### stopPlayback()
162
+ #### `stopPlayback()`
163
+ Stops playback immediately.
164
+
165
+ #### `togglePlayback()`
166
+ Toggles between play and pause.
167
+
168
+ #### `seekTo(seconds)`
147
169
 
148
- Stops audio immediately.
170
+ | Parameter | Type | Description |
171
+ |--------|------|-------------|
172
+ | `seconds` | `number` | Seek position in seconds |
149
173
 
150
174
  ---
151
175
 
152
- #### togglePlayback()
176
+ ## 📌 Status Types
153
177
 
154
- Plays or pauses the active player.
178
+ - `BUFFERING`
179
+ - `PLAYING`
180
+ - `PAUSED`
181
+ - `ENDED`
182
+ - `IDLE`
155
183
 
156
184
  ---
157
185
 
158
- #### seekTo(seconds)
186
+ ## 🛠️ Platform Support
159
187
 
160
- | Param | Type | Description |
161
- |--------|--------|-------------|
162
- | seconds | number | Seek position |
188
+ | Platform | Supported |
189
+ |--------|-----------|
190
+ | Android | |
191
+ | iOS | ✅ |
192
+ | Expo (Dev / EAS) | ✅ |
163
193
 
164
194
  ---
165
195
 
166
- ## Status Types
196
+ ## 📄 License
167
197
 
168
- - BUFFERING
169
- - PLAYING
170
- - PAUSED
171
- - ENDED
172
- - IDLE
198
+ MIT License
@@ -1,29 +1,32 @@
1
1
  #import <React/RCTBridgeModule.h>
2
2
  #import <React/RCTEventEmitter.h>
3
3
 
4
- @interface RCT_EXTERN_MODULE (RecPlayModule, RCTEventEmitter)
4
+ @interface RCT_EXTERN_MODULE(RecPlayModule, RCTEventEmitter)
5
5
 
6
- RCT_EXTERN_METHOD(startRecording : (NSString *)fileName shouldStopPlayback : (
7
- BOOL)shouldStopPlayback resolver : (RCTPromiseResolveBlock)
8
- resolve rejecter : (RCTPromiseRejectBlock)reject)
6
+ RCT_EXTERN_METHOD(startRecording:(NSString *)fileName
7
+ shouldStopPlayback:(BOOL)shouldStopPlayback
8
+ resolver:(RCTPromiseResolveBlock)resolve
9
+ rejecter:(RCTPromiseRejectBlock)reject)
9
10
 
10
- RCT_EXTERN_METHOD(stopRecording : (RCTPromiseResolveBlock)
11
- resolve rejecter : (RCTPromiseRejectBlock)reject)
11
+ RCT_EXTERN_METHOD(stopRecording:(RCTPromiseResolveBlock)resolve
12
+ rejecter:(RCTPromiseRejectBlock)reject)
12
13
 
13
- RCT_EXTERN_METHOD(pauseRecording : (RCTPromiseResolveBlock)
14
- resolve rejecter : (RCTPromiseRejectBlock)reject)
14
+ RCT_EXTERN_METHOD(pauseRecording:(RCTPromiseResolveBlock)resolve
15
+ rejecter:(RCTPromiseRejectBlock)reject)
15
16
 
16
- RCT_EXTERN_METHOD(resumeRecording : (RCTPromiseResolveBlock)
17
- resolve rejecter : (RCTPromiseRejectBlock)reject)
17
+ RCT_EXTERN_METHOD(resumeRecording:(RCTPromiseResolveBlock)resolve
18
+ rejecter:(RCTPromiseRejectBlock)reject)
18
19
 
19
- RCT_EXTERN_METHOD(playAudio : (NSString *)uri shouldStopPrevious : (BOOL)
20
- shouldStopPrevious loop : (BOOL)loop)
20
+ RCT_EXTERN_METHOD(playAudio:(NSString *)uri
21
+ shouldStopPrevious:(BOOL)shouldStopPrevious
22
+ loop:(BOOL)loop)
21
23
 
22
- RCT_EXTERN_METHOD(stopPlayback : (RCTPromiseResolveBlock)
23
- resolve rejecter : (RCTPromiseRejectBlock)reject)
24
+ // FIX: Ensure the internal name 'rejecter' matches the Swift signature
25
+ RCT_EXTERN_METHOD(stopPlayback:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject)
24
27
 
25
- RCT_EXTERN_METHOD(seekTo : (double)seconds)
28
+ RCT_EXTERN_METHOD(seekTo:(double)seconds)
26
29
 
27
30
  RCT_EXTERN_METHOD(togglePlayback)
28
31
 
29
- @end
32
+ @end
@@ -7,7 +7,7 @@ class RecPlayModule: RCTEventEmitter {
7
7
 
8
8
  private var audioRecorder: AVAudioRecorder?
9
9
  private var audioPlayer: AVPlayer?
10
- private var playerItemContext = 0
10
+ private var playerItem: AVPlayerItem?
11
11
  private var timeObserverToken: Any?
12
12
 
13
13
  private var recordingTimer: Timer?
@@ -34,7 +34,6 @@ class RecPlayModule: RCTEventEmitter {
34
34
 
35
35
  let session = AVAudioSession.sharedInstance()
36
36
  do {
37
- // Set category for both recording and playback
38
37
  try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth])
39
38
  try session.setActive(true)
40
39
 
@@ -57,6 +56,7 @@ class RecPlayModule: RCTEventEmitter {
57
56
  self.isPaused = false
58
57
 
59
58
  DispatchQueue.main.async {
59
+ self.recordingTimer?.invalidate()
60
60
  self.recordingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
61
61
  if !self.isPaused {
62
62
  self.sendEvent(withName: "onTimerUpdate", body: ["seconds": self.secondsElapsed])
@@ -64,7 +64,6 @@ class RecPlayModule: RCTEventEmitter {
64
64
  }
65
65
  }
66
66
  }
67
-
68
67
  resolve(name)
69
68
  } catch {
70
69
  reject("REC_ERROR", "Failed to start recording", error)
@@ -109,43 +108,56 @@ class RecPlayModule: RCTEventEmitter {
109
108
  stopPlaybackInternal()
110
109
  }
111
110
 
111
+ let session = AVAudioSession.sharedInstance()
112
+ try? session.setCategory(.playback, mode: .default, options: [.defaultToSpeaker])
113
+ try? session.setActive(true)
114
+
112
115
  guard let url = URL(string: uri) else { return }
116
+
117
+ let asset = AVURLAsset(url: url)
118
+ self.playerItem = AVPlayerItem(asset: asset)
113
119
  self.isLooping = loop
114
120
 
115
- let playerItem = AVPlayerItem(url: url)
116
- audioPlayer = AVPlayer(playerItem: playerItem)
121
+ // Optimize for PHP streaming
122
+ self.playerItem?.preferredForwardBufferDuration = 2.0
123
+ self.playerItem?.addObserver(self, forKeyPath: "status", options: [.new], context: nil)
117
124
 
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
- ])
125
+ NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: self.playerItem)
126
+
127
+ if audioPlayer == nil {
128
+ audioPlayer = AVPlayer(playerItem: self.playerItem)
129
+ } else {
130
+ audioPlayer?.replaceCurrentItem(with: self.playerItem)
126
131
  }
127
132
 
128
- // Notification for Finished
129
- NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
133
+ audioPlayer?.automaticallyWaitsToMinimizeStalling = true
130
134
 
135
+ setupProgressObserver()
131
136
  audioPlayer?.play()
132
- sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
137
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
133
138
  }
134
139
 
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"])
140
+ private func setupProgressObserver() {
141
+ if let token = timeObserverToken {
142
+ audioPlayer?.removeTimeObserver(token)
143
+ }
144
+ let interval = CMTime(seconds: 0.5, preferredTimescale: 1000)
145
+ timeObserverToken = audioPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
146
+ guard let self = self,
147
+ let duration = self.audioPlayer?.currentItem?.duration.seconds,
148
+ duration > 0, !duration.isNaN else { return }
149
+
150
+ self.sendEvent(withName: "onPlaybackProgress", body: [
151
+ "currentPosition": time.seconds,
152
+ "duration": duration
153
+ ])
142
154
  }
143
155
  }
144
156
 
145
157
  @objc(stopPlayback:rejecter:)
146
- func stopPlayback(resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) {
158
+ func stopPlayback(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
147
159
  stopPlaybackInternal()
148
- resolve?(true)
160
+ resolve(true)
149
161
  }
150
162
 
151
163
  private func stopPlaybackInternal() {
@@ -155,7 +167,12 @@ class RecPlayModule: RCTEventEmitter {
155
167
  }
156
168
  audioPlayer?.pause()
157
169
  audioPlayer = nil
158
- NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
170
+
171
+ if let item = playerItem {
172
+ item.removeObserver(self, forKeyPath: "status")
173
+ NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: item)
174
+ }
175
+ playerItem = nil
159
176
  }
160
177
 
161
178
  @objc(seekTo:)
@@ -168,10 +185,28 @@ class RecPlayModule: RCTEventEmitter {
168
185
  func togglePlayback() {
169
186
  if audioPlayer?.timeControlStatus == .playing {
170
187
  audioPlayer?.pause()
171
- sendEvent(withName: "onPlaybackStatus", body: ["status": "PAUSED"])
172
188
  } else {
173
189
  audioPlayer?.play()
174
- sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
175
190
  }
176
191
  }
177
- }
192
+
193
+ override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
194
+ if keyPath == "status", let item = object as? AVPlayerItem {
195
+ if item.status == .failed {
196
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "ERROR"])
197
+ } else if item.status == .readyToPlay {
198
+ print("DEBUG: Audio Ready")
199
+ }
200
+ }
201
+ }
202
+
203
+ @objc func playerDidFinishPlaying(note: NSNotification) {
204
+ if isLooping {
205
+ audioPlayer?.seek(to: .zero)
206
+ audioPlayer?.play()
207
+ } else {
208
+ self.sendEvent(withName: "onPlaybackFinished", body: ["finished": true])
209
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "ENDED"])
210
+ }
211
+ }
212
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-recplay",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "High-performance React Native module for audio recording and audio playback on Android and iOS.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "index.js",
41
41
  "react-native.config.js",
42
42
  "rns-recplay.podspec",
43
- "withrecplayVoip.js"
43
+ "withNativeRecPlay.js"
44
44
  ],
45
45
  "peerDependencies": {
46
46
  "expo": ">=45.0.0",
@@ -0,0 +1,43 @@
1
+ const { withInfoPlist, withAndroidManifest } = require('@expo/config-plugins');
2
+
3
+ const withIosPermissions = (config, { microphonePermission }) => {
4
+ return withInfoPlist(config, (config) => {
5
+ config.modResults.NSMicrophoneUsageDescription =
6
+ microphonePermission || "Allow $(PRODUCT_NAME) to access your microphone for recording audio.";
7
+ return config;
8
+ });
9
+ };
10
+
11
+ const withAndroidPermissions = (config) => {
12
+ return withAndroidManifest(config, (config) => {
13
+ const manifest = config.modResults.manifest;
14
+ const permissionsToAdd = [
15
+ 'android.permission.RECORD_AUDIO',
16
+ 'android.permission.MODIFY_AUDIO_SETTINGS',
17
+ ];
18
+
19
+ if (!manifest['uses-permission']) {
20
+ manifest['uses-permission'] = [];
21
+ }
22
+
23
+ permissionsToAdd.forEach(perm => {
24
+ // CHECK: Only push if it's not already there
25
+ const exists = manifest['uses-permission'].some(
26
+ p => p.$['android:name'] === perm
27
+ );
28
+ if (!exists) {
29
+ manifest['uses-permission'].push({
30
+ '$': { 'android:name': perm }
31
+ });
32
+ }
33
+ });
34
+
35
+ return config;
36
+ });
37
+ };
38
+
39
+ module.exports = (config, props) => {
40
+ config = withIosPermissions(config, props);
41
+ config = withAndroidPermissions(config);
42
+ return config;
43
+ };
@@ -1,10 +0,0 @@
1
- /**
2
- * Automatically generated file. DO NOT MODIFY
3
- */
4
- package com.rnsrecplay;
5
-
6
- public final class BuildConfig {
7
- public static final boolean DEBUG = Boolean.parseBoolean("true");
8
- public static final String LIBRARY_PACKAGE_NAME = "com.rnsrecplay";
9
- public static final String BUILD_TYPE = "debug";
10
- }
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
- package="com.rnsrecplay" >
4
-
5
- <uses-sdk android:minSdkVersion="24" />
6
-
7
- </manifest>
@@ -1,18 +0,0 @@
1
- {
2
- "version": 3,
3
- "artifactType": {
4
- "type": "AAPT_FRIENDLY_MERGED_MANIFESTS",
5
- "kind": "Directory"
6
- },
7
- "applicationId": "com.rnsrecplay",
8
- "variantName": "debug",
9
- "elements": [
10
- {
11
- "type": "SINGLE",
12
- "filters": [],
13
- "attributes": [],
14
- "outputFile": "AndroidManifest.xml"
15
- }
16
- ],
17
- "elementType": "File"
18
- }
@@ -1,6 +0,0 @@
1
- aarFormatVersion=1.0
2
- aarMetadataVersion=1.0
3
- minCompileSdk=1
4
- minCompileSdkExtension=0
5
- minAndroidGradlePluginVersion=1.0.0
6
- coreLibraryDesugaringEnabled=false
@@ -1,2 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <merger version="3"><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/main/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main" generated-set="main$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/main/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/debug/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug" generated-set="debug$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/debug/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/generated/res/resValues/debug"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated" generated-set="generated$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/generated/res/resValues/debug"/></dataSet><mergedItems/></merger>
@@ -1,2 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/main/jniLibs"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/debug/jniLibs"/></dataSet></merger>
@@ -1,2 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/main/shaders"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/debug/shaders"/></dataSet></merger>
@@ -1,2 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/main/assets"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/src/debug/assets"/></dataSet><dataSet config="generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/shader_assets/debug/compileDebugShaders/out"/></dataSet></merger>
@@ -1,2 +0,0 @@
1
- R_DEF: Internal format may change without notice
2
- local
@@ -1,7 +0,0 @@
1
- 1<?xml version="1.0" encoding="utf-8"?>
2
- 2<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
- 3 package="com.rnsrecplay" >
4
- 4
5
- 5 <uses-sdk android:minSdkVersion="24" />
6
- 6
7
- 7</manifest>
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
- package="com.rnsrecplay" >
4
-
5
- <uses-sdk android:minSdkVersion="24" />
6
-
7
- </manifest>
@@ -1,16 +0,0 @@
1
- -- Merging decision tree log ---
2
- manifest
3
- ADDED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml:2:13-83
4
- INJECTED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml:2:13-83
5
- package
6
- INJECTED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml
7
- xmlns:android
8
- ADDED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml:2:23-81
9
- uses-sdk
10
- INJECTED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml reason: use-sdk injection requested
11
- INJECTED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml
12
- INJECTED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml
13
- android:targetSdkVersion
14
- INJECTED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml
15
- android:minSdkVersion
16
- INJECTED from /Users/bush/Desktop/Apps/Raiidr/node_modules/rns-recplay/android/build/intermediates/tmp/ProcessLibraryManifest/debug/tempAndroidManifest10474479560867336143.xml