rns-recplay 2.0.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,35 +1,20 @@
1
1
  # 🎤 rns-recplay
2
2
 
3
- A **high-performance React Native audio recording and playback module** with native-level control & compatible with webRTC.
4
- Designed for **voice notes, chats, and media apps**, offering smooth looping, precise progress tracking, and automatic playback interruption.
3
+ A **high-performance React Native audio recording and audio playback** compatible with WebRTC.
4
+ Designed for **voice notes, audio workstations, and media apps**, offering real-time volume metering, smooth looping, precise seek, and automatic playback interruption handling.
5
5
 
6
6
  ---
7
7
 
8
8
  ## ✨ Features
9
9
 
10
- * 🎙️ **Audio Recording**
11
-
12
- * High-quality AAC / M4A format
13
- * Real-time recording timer
14
- * Pause & resume support
15
-
16
- * 🔊 **Audio Playback**
17
-
18
- * Powered by **ExoPlayer (Android)** and **AVPlayer (iOS)**
19
- * Native-level performance
20
-
21
- * 🔄 **Seamless Looping**
22
-
23
- * Gapless looping handled natively
24
-
25
- * 📊 **Progress Tracking**
26
-
27
- * Position & duration updates every **500ms**
28
-
29
- * 🛡️ **Smart Audio Control**
30
-
31
- * Automatically stops playback when recording begins
32
- * Prevents overlapping audio sessions
10
+ - 🎙️ **Audio Recording** — High-quality AAC / M4A, real-time timer, pause & resume
11
+ - 📊 **Real-time Volume Metering** — dB + normalized (0.0–1.0) every 100ms for UI visualizers
12
+ - 🔊 **Audio Playback** — Powered by **ExoPlayer (Android)** and **AVPlayer (iOS)**
13
+ - 🎯 **Precision Seek** — Zero-tolerance sample-accurate seeking
14
+ - 🔄 **Seamless Looping** — Gapless looping handled natively
15
+ - 📈 **Smooth Progress Tracking** — Position & duration updates every **50ms**
16
+ - 🛡️ **Smart Audio Control** — Audio focus, interruption handling, headphone unplug detection
17
+ - 🎙️ **WebRTC Compatible** — Detects active calls and adapts audio routing automatically
33
18
 
34
19
  ---
35
20
 
@@ -39,8 +24,6 @@ Designed for **voice notes, chats, and media apps**, offering smooth looping, pr
39
24
  npm install rns-recplay
40
25
  ```
41
26
 
42
- or (Expo):
43
-
44
27
  ```bash
45
28
  npx expo install rns-recplay
46
29
  ```
@@ -49,185 +32,274 @@ npx expo install rns-recplay
49
32
 
50
33
  ## ⚙️ Expo Configuration
51
34
 
52
- Add the plugin to your **app.json** or **app.config.json**:
35
+ Add the plugin to your `app.json` or `app.config.js`:
53
36
 
54
37
  ```json
55
- [
56
- "rns-recplay",
57
- {
58
- "microphonePermission": "Microphone is used strictly for voice messages and live audio or video interactions. Tap 'Allow' to enable microphone usage when needed."
59
- }
60
- ]
38
+ {
39
+ "plugins": [
40
+ [
41
+ "rns-recplay",
42
+ {
43
+ "microphonePermission": "Allow microphone access for voice recording."
44
+ }
45
+ ]
46
+ ]
47
+ }
61
48
  ```
62
49
 
63
50
  ---
64
51
 
65
52
  ## 🚀 Usage
66
53
 
67
- ### 🎙️ Recording Example
54
+ ### 🎙️ Start & Stop Recording
68
55
 
69
56
  ```js
70
57
  import Recplay from 'rns-recplay';
71
58
 
72
- const startMyRecording = async () => {
73
- try {
74
- const fileName = await Recplay.startRecording({
75
- fileName: "my_voice_note", // optional file name
76
- shouldStopPlayback: true,
77
- duck: true,
78
- mixWithOthers: true,
79
- onSecondsUpdate: (seconds) => console.log(`Recorded: ${seconds}s`)
80
- });
81
-
82
- console.log("Recording started:", fileName);
83
- } catch (err) {
84
- console.error(err);
85
- }
86
- };
59
+ // Start recording
60
+ const fileName = await Recplay.startRecording({
61
+ fileName: "my_voice_note", // optional, defaults to rec_<timestamp>
62
+ shouldStopPlayback: true, // stop any playing audio first
63
+ duck: true, // lower other audio while recording
64
+ mixWithOthers: true, // mix with other audio sessions
65
+ useBT: false, // false = force built-in mic (recommended)
66
+ onSecondsUpdate: (seconds) => {
67
+ console.log(`Recorded: ${seconds}s`);
68
+ },
69
+ onVolumeUpdate: (db, normalized) => {
70
+ // db: raw decibel value (~-60 to 0)
71
+ // normalized: 0.0 to 1.0 — use this to drive a waveform visualizer
72
+ console.log(`Volume: ${normalized}`);
73
+ },
74
+ });
75
+
76
+ console.log("Recording started, file name:", fileName);
87
77
 
88
- const stopMyRecording = async () => {
89
- const fileUri = await Recplay.stopRecording();
90
- console.log("File saved at:", fileUri);
91
- };
78
+ // Stop recording
79
+ const result = await Recplay.stopRecording();
80
+ console.log("URI:", result.uri); // file:///...path/to/file.m4a
81
+ console.log("Duration:", result.duration); // seconds, e.g. 5.4
92
82
  ```
93
83
 
94
84
  ---
95
85
 
96
- ### 🔊 Playback Example (Looping Enabled)
86
+ ### ⏸️ Pause & Resume Recording
97
87
 
98
88
  ```js
99
- import Recplay from 'rns-recplay';
89
+ await Recplay.pauseRecording();
90
+ await Recplay.resumeRecording();
91
+ ```
92
+
93
+ ---
100
94
 
95
+ ### 🔊 Playback
96
+
97
+ ```js
101
98
  Recplay.playAudio({
102
99
  uri: "file:///path/to/audio.m4a",
103
100
  shouldStopPrevious: true,
104
- loop: true,
101
+ loop: false,
105
102
  mixWithOthers: true,
106
- duck: false,
103
+ duck: true,
107
104
  callbacks: {
108
- onStatus: (status) => console.log("Status:", status),
109
- onProgress: (position, duration) => console.log(`Progress: ${position} / ${duration}`),
110
- onPlaybackFinished: () => console.log("Playback finished"),
105
+ onStatus: (status) => {
106
+ // 'BUFFERING' | 'PLAYING' | 'PAUSED' | 'ENDED' | 'ERROR'
107
+ console.log("Status:", status);
108
+ if (status === 'PLAYING') {
109
+ // safe to seek or update UI
110
+ }
111
+ if (status === 'ENDED') {
112
+ // reset playhead
113
+ }
114
+ },
115
+ onProgress: (currentPosition, duration) => {
116
+ // fires every ~50ms while playing
117
+ console.log(`${currentPosition} / ${duration}`);
118
+ },
119
+ onPlaybackFinished: () => {
120
+ console.log("Playback finished");
121
+ },
111
122
  }
112
123
  });
113
124
  ```
114
125
 
115
126
  ---
116
127
 
117
- ## 📚 API Reference
128
+ ### 🎯 Seek, Toggle & Stop
118
129
 
119
- ### 🔌 Permission Checks
130
+ ```js
131
+ // Seek to a specific time (sample-accurate)
132
+ Recplay.seekTo({ seconds: 30.5 });
120
133
 
121
- | Method | Description |
122
- | --------------------- | ------------------------------------------------ |
123
- | `checkPermission()` | Checks the current microphone permission status. |
124
- | `requestPermission()` | Triggers the system permission dialog. |
134
+ // Toggle between play and pause
135
+ Recplay.togglePlayback();
125
136
 
126
- Returns: Promise<"granted" | "denied" | "blocked" | "unavailable">
137
+ // Stop playback and release resources
138
+ await Recplay.stopPlayback();
139
+ ```
127
140
 
128
- Status Meanings:
141
+ ---
129
142
 
130
- * granted: Permission is active. Ready to record.
131
- * denied: Not asked yet (iOS) or dismissed (Android). Can still ask.
132
- * blocked: User selected "Don't Allow" or "Never ask again". Must redirect to System Settings.
133
- * unavailable: Hardware is missing or restricted by OS.
143
+ ### 🔐 Permissions
144
+
145
+ ```js
146
+ // Check current status
147
+ const status = await Recplay.checkPermission();
148
+ // Returns: 'granted' | 'denied' | 'blocked' | 'unavailable'
149
+
150
+ // Request permission
151
+ const result = await Recplay.requestPermission();
152
+ // Returns: 'granted' | 'denied'
153
+ ```
134
154
 
135
155
  ---
136
156
 
137
- ### 🎙️ Recording
157
+ ## 📚 API Reference
138
158
 
139
- #### `startRecording({ fileName?, shouldStopPlayback?, duck?, mixWithOthers?, onSecondsUpdate? })`
159
+ ### `startRecording(options?)`
140
160
 
141
- | Parameter | Type | Default | Description |
142
- | -------------------- | ---------- | ------- | ------------------------------------------- |
143
- | `fileName` | `string` | `null` | Custom `.m4a` file name |
144
- | `shouldStopPlayback` | `boolean` | `true` | Stops any playing audio |
145
- | `duck` | `boolean` | `true` | Reduce volume of other audio when recording |
146
- | `mixWithOthers` | `boolean` | `true` | Mix recording with device playing audio |
147
- | `onSecondsUpdate` | `function` | `null` | Called every second |
161
+ | Parameter | Type | Default | Description |
162
+ |----------------------|------------|---------|---------------------------------------------------------------------|
163
+ | `fileName` | `string` | `null` | Output file name (without extension). Defaults to `rec_<timestamp>` |
164
+ | `shouldStopPlayback` | `boolean` | `true` | Stop any active playback before recording |
165
+ | `duck` | `boolean` | `true` | Lower volume of other audio while recording |
166
+ | `mixWithOthers` | `boolean` | `true` | Allow mixing with other active audio sessions |
167
+ | `useBT` | `boolean` | `false` | Use Bluetooth mic. `false` forces the built-in microphone |
168
+ | `onSecondsUpdate` | `function` | `null` | Called once per second with `(seconds: number)` |
169
+ | `onVolumeUpdate` | `function` | `null` | Called every 100ms with `(db: number, normalized: number)` |
148
170
 
149
- **Returns:** `Promise<string>` (file name)
171
+ **Returns:** `Promise<string>` — the file name (without extension)
150
172
 
151
173
  ---
152
174
 
153
- #### `stopRecording()`
175
+ ### `stopRecording()`
154
176
 
155
- Stops recording and releases the microphone.
177
+ Stops the active recording, releases the microphone, and returns the saved file info.
156
178
 
157
- **Returns:** `Promise<string>` (file URI)
179
+ **Returns:** `Promise<{ uri: string, duration: number }>`
180
+
181
+ | Field | Type | Description |
182
+ |------------|----------|-----------------------------------|
183
+ | `uri` | `string` | Full file URI, e.g. `file:///...` |
184
+ | `duration` | `number` | Duration in seconds, e.g. `5.4` |
158
185
 
159
186
  ---
160
187
 
161
- #### `pauseRecording()`
188
+ ### `pauseRecording()`
162
189
 
163
- Pauses the active recording session.
190
+ Pauses the active recording session. Elapsed time is preserved accurately across pauses.
164
191
 
165
- #### `resumeRecording()`
192
+ **Returns:** `Promise<boolean>`
193
+
194
+ ---
195
+
196
+ ### `resumeRecording()`
166
197
 
167
198
  Resumes a paused recording session.
168
199
 
200
+ **Returns:** `Promise<boolean>`
201
+
169
202
  ---
170
203
 
171
- ### 🔊 Playback
204
+ ### `playAudio(options)`
205
+
206
+ | Parameter | Type | Default | Description |
207
+ |----------------------|-----------|---------|-------------------------------------------|
208
+ | `uri` | `string` | — | Audio file URI or HTTP URL |
209
+ | `shouldStopPrevious` | `boolean` | `false` | Stop and release any previous playback |
210
+ | `loop` | `boolean` | `false` | Loop playback natively (gapless) |
211
+ | `mixWithOthers` | `boolean` | `true` | Mix with other active audio sessions |
212
+ | `duck` | `boolean` | `false` | Lower volume of other audio while playing |
213
+ | `callbacks` | `object` | `{}` | Event callbacks (see below) |
172
214
 
173
- #### `playAudio({ uri, shouldStopPrevious?, loop?, mixWithOthers?, duck?, callbacks? })`
215
+ #### Callbacks
174
216
 
175
- | Parameter | Type | Default | Description |
176
- | -------------------- | --------- | ------- | -------------------------------------------- |
177
- | `uri` | `string` | — | Audio file URI |
178
- | `shouldStopPrevious` | `boolean` | `false` | Stops previous playback |
179
- | `loop` | `boolean` | `false` | Enables native looping |
180
- | `mixWithOthers` | `boolean` | `true` | Mix audio playback with device playing audio |
181
- | `duck` | `boolean` | `false` | Reduce volume of other audio when playing |
182
- | `callbacks` | `object` | `{}` | Playback event callbacks |
217
+ | Callback | Signature | Description |
218
+ |----------------------|-----------------------------------------------------------|-----------------------------------|
219
+ | `onStatus` | `(status: string) => void` | Player state changes (see below) |
220
+ | `onProgress` | `(currentPosition: number, duration: number) => void` | Fires every ~50ms while playing |
221
+ | `onPlaybackFinished` | `() => void` | Fires when non-looping audio ends |
222
+
223
+ ---
183
224
 
184
- ##### Callback Options
225
+ ### `stopPlayback()`
185
226
 
186
- | Callback | Params | Description |
187
- | -------------------- | ---------------------- | ------------------------ |
188
- | `onStatus` | `(status)` | Player state updates |
189
- | `onProgress` | `(position, duration)` | Playback progress |
190
- | `onPlaybackFinished` | `()` | Fired when playback ends |
227
+ Stops playback, removes all observers, and releases the player.
228
+
229
+ **Returns:** `Promise<boolean>`
191
230
 
192
231
  ---
193
232
 
194
- #### `stopPlayback()`
233
+ ### `togglePlayback()`
195
234
 
196
- Stops playback immediately.
235
+ Toggles between play and pause on the current player instance.
197
236
 
198
- #### `togglePlayback()`
237
+ ---
199
238
 
200
- Toggles between play and pause.
239
+ ### `seekTo({ seconds })`
201
240
 
202
- #### `seekTo({ seconds })`
241
+ Seeks to a precise position. Uses zero-tolerance seeking for sample accuracy.
203
242
 
204
- | Parameter | Type | Description |
205
- | --------- | -------- | ------------------------ |
206
- | `seconds` | `number` | Seek position in seconds |
243
+ | Parameter | Type | Description |
244
+ |-----------|----------|----------------------------|
245
+ | `seconds` | `number` | Target position in seconds |
207
246
 
208
247
  ---
209
248
 
210
- ## 📌 Status Types
249
+ ### `checkPermission()`
250
+
251
+ **Returns:** `Promise<'granted' | 'denied' | 'blocked' | 'unavailable'>`
211
252
 
212
- * `BUFFERING`
213
- * `PLAYING`
214
- * `PAUSED`
215
- * `ERROR`
216
- * `ENDED`
217
- * `IDLE`
253
+ | Status | Meaning |
254
+ |---------------|--------------------------------------------------------------------------|
255
+ | `granted` | Microphone is available and permitted |
256
+ | `denied` | Not asked yet (iOS) or dismissed once (Android) — can still request |
257
+ | `blocked` | User selected "Don't Allow" / "Never ask again" — redirect to Settings |
258
+ | `unavailable` | Hardware missing or OS-restricted |
259
+
260
+ ---
261
+
262
+ ### `requestPermission()`
263
+
264
+ Triggers the native system permission dialog.
265
+
266
+ **Returns:** `Promise<'granted' | 'denied'>`
267
+
268
+ ---
269
+
270
+ ## 📌 Playback Status Values
271
+
272
+ | Status | When |
273
+ |-------------|-----------------------------------------------|
274
+ | `BUFFERING` | Loading / rebuffering (network or large file) |
275
+ | `PLAYING` | Audio is actively playing |
276
+ | `PAUSED` | Paused by user or audio focus loss |
277
+ | `ENDED` | Playback reached the end of the file |
278
+ | `ERROR` | Playback failed (bad URI, decode error, etc.) |
218
279
 
219
280
  ---
220
281
 
221
282
  ## 🛠️ Platform Support
222
283
 
223
284
  | Platform | Supported |
224
- | ---------------- | --------- |
225
- | Android | ✅ |
226
- | iOS | ✅ |
227
- | Expo (Dev / EAS) | ✅ |
285
+ |------------------|-----------|
286
+ | Android | ✅ |
287
+ | iOS | ✅ |
288
+ | Expo (Dev / EAS) | ✅ |
289
+
290
+ ---
291
+
292
+ ## 📝 Notes
293
+
294
+ - Recorded files are saved in the app's **cache directory** as `.m4a` (AAC, 44.1kHz, 128kbps, mono)
295
+ - `onVolumeUpdate` fires at 100ms intervals — use `normalized` (0.0–1.0) to drive waveform visualizer bars
296
+ - `onSecondsUpdate` fires once per second only on whole-second boundaries to avoid flooding JS
297
+ - `useBT: false` (default) forces the phone's **built-in microphone** even when Bluetooth headphones are connected — this prevents Lightning/Bluetooth accessories from switching to low-quality HFP mode
298
+ - `seekTo` is safe to call immediately after `onStatus: 'PLAYING'` fires
299
+ - Calling `stopPlayback` cleans up all native observers — no memory leaks
228
300
 
229
301
  ---
230
302
 
231
303
  ## 📄 License
232
304
 
233
- MIT License
305
+ MIT