nosnia-audio-recorder 0.1.1 β 0.2.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 +20 -1
- package/android/src/main/java/com/nosniaaudiorecorder/NosniaAudioRecorderModule.kt +59 -1
- package/ios/NosniaAudioRecorder.h +2 -1
- package/ios/NosniaAudioRecorder.mm +51 -0
- package/lib/module/index.js +34 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +49 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A compact, high-performance audio recorder library for React Native that records
|
|
|
8
8
|
- π± **Cross-Platform**: Works seamlessly on iOS and Android
|
|
9
9
|
- ποΈ **Configurable**: Adjust bitrate, sample rate, and channels
|
|
10
10
|
- βΈοΈ **Pause/Resume**: Full control over recording with pause and resume
|
|
11
|
+
- β±οΈ **Real-time Duration**: Get continuous recording duration updates via callback
|
|
11
12
|
- π **Permission Handling**: Built-in permission checking and requesting
|
|
12
13
|
- πΎ **File Management**: Automatic file naming and directory management
|
|
13
14
|
- π¦ **Compact**: Minimal dependencies, optimized for bundle size
|
|
@@ -52,6 +53,7 @@ console.log('Recording saved to:', filePath);
|
|
|
52
53
|
|
|
53
54
|
```js
|
|
54
55
|
import { NosniaAudioRecorder } from 'nosnia-audio-recorder';
|
|
56
|
+
import { useState } from 'react';
|
|
55
57
|
|
|
56
58
|
// Check and request permission
|
|
57
59
|
const hasPermission = await NosniaAudioRecorder.checkPermission();
|
|
@@ -59,9 +61,16 @@ if (!hasPermission) {
|
|
|
59
61
|
await NosniaAudioRecorder.requestPermission();
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
// Start recording
|
|
64
|
+
// Start recording with real-time duration updates
|
|
63
65
|
await NosniaAudioRecorder.startRecording();
|
|
64
66
|
|
|
67
|
+
// Add listener for recording duration updates (every 100ms)
|
|
68
|
+
const removeListener = NosniaAudioRecorder.addRecordingProgressListener(
|
|
69
|
+
({ duration, isRecording }) => {
|
|
70
|
+
console.log(`Recording ${Math.floor(duration / 1000)}s`);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
65
74
|
// Pause if needed
|
|
66
75
|
await NosniaAudioRecorder.pauseRecording();
|
|
67
76
|
await NosniaAudioRecorder.resumeRecording();
|
|
@@ -73,6 +82,9 @@ console.log(`Recording: ${status.isRecording}, Duration: ${status.duration}ms`);
|
|
|
73
82
|
// Stop and save
|
|
74
83
|
const filePath = await NosniaAudioRecorder.stopRecording();
|
|
75
84
|
|
|
85
|
+
// Remove listener when done
|
|
86
|
+
removeListener();
|
|
87
|
+
|
|
76
88
|
// Or cancel (discard)
|
|
77
89
|
await NosniaAudioRecorder.cancelRecording();
|
|
78
90
|
```
|
|
@@ -89,6 +101,8 @@ await NosniaAudioRecorder.cancelRecording();
|
|
|
89
101
|
- `resumeRecording(): Promise<void>` - Resume recording
|
|
90
102
|
- `cancelRecording(): Promise<void>` - Cancel and discard
|
|
91
103
|
- `getStatus(): Promise<RecorderStatus>` - Get recorder status
|
|
104
|
+
- `addRecordingProgressListener(callback: RecordingProgressCallback): () => void` - Add listener for duration updates (returns cleanup function)
|
|
105
|
+
- `removeRecordingProgressListener(): void` - Remove duration listener
|
|
92
106
|
|
|
93
107
|
### Types
|
|
94
108
|
|
|
@@ -105,6 +119,11 @@ interface RecorderStatus {
|
|
|
105
119
|
duration: number; // In milliseconds
|
|
106
120
|
currentFilePath?: string;
|
|
107
121
|
}
|
|
122
|
+
|
|
123
|
+
type RecordingProgressCallback = (data: {
|
|
124
|
+
duration: number; // In milliseconds
|
|
125
|
+
isRecording: boolean;
|
|
126
|
+
}) => void;
|
|
108
127
|
```
|
|
109
128
|
|
|
110
129
|
## Platform Setup
|
|
@@ -5,12 +5,16 @@ import android.content.Context
|
|
|
5
5
|
import android.media.MediaRecorder
|
|
6
6
|
import android.os.Build
|
|
7
7
|
import android.os.Environment
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
8
10
|
import androidx.core.content.ContextCompat
|
|
9
11
|
import com.facebook.react.bridge.Promise
|
|
10
12
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
13
|
import com.facebook.react.bridge.ReadableMap
|
|
12
14
|
import com.facebook.react.bridge.WritableMap
|
|
13
15
|
import com.facebook.react.bridge.WritableNativeMap
|
|
16
|
+
import com.facebook.react.bridge.Arguments
|
|
17
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
14
18
|
import com.facebook.react.module.annotations.ReactModule
|
|
15
19
|
import java.io.File
|
|
16
20
|
import java.text.SimpleDateFormat
|
|
@@ -25,6 +29,42 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
25
29
|
private var currentFilePath: String? = null
|
|
26
30
|
private var isRecording = false
|
|
27
31
|
private var isPaused = false
|
|
32
|
+
private var startTime: Long = 0
|
|
33
|
+
private var pausedDuration: Long = 0
|
|
34
|
+
private var pauseStartTime: Long = 0
|
|
35
|
+
|
|
36
|
+
private val progressHandler = Handler(Looper.getMainLooper())
|
|
37
|
+
private val progressRunnable = object : Runnable {
|
|
38
|
+
override fun run() {
|
|
39
|
+
if (isRecording && !isPaused) {
|
|
40
|
+
val currentTime = System.currentTimeMillis()
|
|
41
|
+
val duration = currentTime - startTime - pausedDuration
|
|
42
|
+
|
|
43
|
+
sendEvent("onRecordingProgress", Arguments.createMap().apply {
|
|
44
|
+
putDouble("duration", duration.toDouble())
|
|
45
|
+
putBoolean("isRecording", true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
progressHandler.postDelayed(this, 100) // Update every 100ms
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private fun sendEvent(eventName: String, params: WritableMap?) {
|
|
54
|
+
reactApplicationContext
|
|
55
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
56
|
+
.emit(eventName, params)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun startProgressUpdates() {
|
|
60
|
+
startTime = System.currentTimeMillis()
|
|
61
|
+
pausedDuration = 0
|
|
62
|
+
progressHandler.post(progressRunnable)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun stopProgressUpdates() {
|
|
66
|
+
progressHandler.removeCallbacks(progressRunnable)
|
|
67
|
+
}
|
|
28
68
|
|
|
29
69
|
override fun getName(): String {
|
|
30
70
|
return NAME
|
|
@@ -69,6 +109,7 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
69
109
|
|
|
70
110
|
isRecording = true
|
|
71
111
|
isPaused = false
|
|
112
|
+
startProgressUpdates()
|
|
72
113
|
promise.resolve(null)
|
|
73
114
|
} catch (e: Exception) {
|
|
74
115
|
mediaRecorder?.release()
|
|
@@ -85,6 +126,7 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
85
126
|
return
|
|
86
127
|
}
|
|
87
128
|
|
|
129
|
+
stopProgressUpdates()
|
|
88
130
|
mediaRecorder?.apply {
|
|
89
131
|
stop()
|
|
90
132
|
release()
|
|
@@ -97,6 +139,7 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
97
139
|
currentFilePath = null
|
|
98
140
|
promise.resolve(filePath)
|
|
99
141
|
} catch (e: Exception) {
|
|
142
|
+
stopProgressUpdates()
|
|
100
143
|
mediaRecorder?.release()
|
|
101
144
|
mediaRecorder = null
|
|
102
145
|
isRecording = false
|
|
@@ -114,6 +157,7 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
114
157
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
115
158
|
mediaRecorder?.pause()
|
|
116
159
|
isPaused = true
|
|
160
|
+
pauseStartTime = System.currentTimeMillis()
|
|
117
161
|
promise.resolve(null)
|
|
118
162
|
} else {
|
|
119
163
|
promise.reject("NOT_SUPPORTED", "Pause is not supported on this API level")
|
|
@@ -133,6 +177,7 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
133
177
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
134
178
|
mediaRecorder?.resume()
|
|
135
179
|
isPaused = false
|
|
180
|
+
pausedDuration += System.currentTimeMillis() - pauseStartTime
|
|
136
181
|
promise.resolve(null)
|
|
137
182
|
} else {
|
|
138
183
|
promise.reject("NOT_SUPPORTED", "Resume is not supported on this API level")
|
|
@@ -144,6 +189,8 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
144
189
|
|
|
145
190
|
override fun cancelRecording(promise: Promise) {
|
|
146
191
|
try {
|
|
192
|
+
stopProgressUpdates()
|
|
193
|
+
|
|
147
194
|
mediaRecorder?.apply {
|
|
148
195
|
try {
|
|
149
196
|
stop()
|
|
@@ -169,9 +216,20 @@ class NosniaAudioRecorderModule(reactContext: ReactApplicationContext) :
|
|
|
169
216
|
|
|
170
217
|
override fun getRecorderStatus(promise: Promise) {
|
|
171
218
|
try {
|
|
219
|
+
val currentTime = System.currentTimeMillis()
|
|
220
|
+
val duration = if (isRecording) {
|
|
221
|
+
if (isPaused) {
|
|
222
|
+
pauseStartTime - startTime - pausedDuration
|
|
223
|
+
} else {
|
|
224
|
+
currentTime - startTime - pausedDuration
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
0
|
|
228
|
+
}
|
|
229
|
+
|
|
172
230
|
val status = WritableNativeMap().apply {
|
|
173
231
|
putBoolean("isRecording", isRecording)
|
|
174
|
-
putDouble("duration",
|
|
232
|
+
putDouble("duration", duration.toDouble())
|
|
175
233
|
if (currentFilePath != null) {
|
|
176
234
|
putString("currentFilePath", currentFilePath)
|
|
177
235
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import <NosniaAudioRecorderSpec/NosniaAudioRecorderSpec.h>
|
|
2
2
|
#import <React/RCTBridgeModule.h>
|
|
3
|
+
#import <React/RCTEventEmitter.h>
|
|
3
4
|
|
|
4
|
-
@interface NosniaAudioRecorder :
|
|
5
|
+
@interface NosniaAudioRecorder : RCTEventEmitter <NativeNosniaAudioRecorderSpec>
|
|
5
6
|
|
|
6
7
|
@end
|
|
@@ -1,27 +1,72 @@
|
|
|
1
1
|
#import "NosniaAudioRecorder.h"
|
|
2
2
|
#import <AVFoundation/AVFoundation.h>
|
|
3
3
|
#import <React/RCTBridgeModule.h>
|
|
4
|
+
#import <React/RCTEventEmitter.h>
|
|
4
5
|
|
|
5
6
|
@interface NosniaAudioRecorder () <AVAudioRecorderDelegate>
|
|
6
7
|
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
|
|
7
8
|
@property (nonatomic, strong) NSURL *recordingURL;
|
|
8
9
|
@property (nonatomic, assign) BOOL isRecording;
|
|
10
|
+
@property (nonatomic, strong) NSTimer *progressTimer;
|
|
9
11
|
@end
|
|
10
12
|
|
|
11
13
|
@implementation NosniaAudioRecorder {
|
|
12
14
|
AVAudioRecorder *_audioRecorder;
|
|
13
15
|
NSURL *_recordingURL;
|
|
14
16
|
BOOL _isRecording;
|
|
17
|
+
NSTimer *_progressTimer;
|
|
18
|
+
BOOL _hasListeners;
|
|
15
19
|
}
|
|
16
20
|
|
|
21
|
+
RCT_EXPORT_MODULE(NosniaAudioRecorder)
|
|
22
|
+
|
|
17
23
|
- (instancetype)init {
|
|
18
24
|
self = [super init];
|
|
19
25
|
if (self) {
|
|
20
26
|
_isRecording = NO;
|
|
27
|
+
_hasListeners = NO;
|
|
21
28
|
}
|
|
22
29
|
return self;
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
- (NSArray<NSString *> *)supportedEvents {
|
|
33
|
+
return @[@"onRecordingProgress"];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)startObserving {
|
|
37
|
+
_hasListeners = YES;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
- (void)stopObserving {
|
|
41
|
+
_hasListeners = NO;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)startProgressTimer {
|
|
45
|
+
if (_progressTimer) {
|
|
46
|
+
[_progressTimer invalidate];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_progressTimer = [NSTimer scheduledTimerWithTimeInterval:0.1
|
|
50
|
+
repeats:YES
|
|
51
|
+
block:^(NSTimer * _Nonnull timer) {
|
|
52
|
+
if (self->_hasListeners && self->_audioRecorder && self->_isRecording) {
|
|
53
|
+
NSTimeInterval currentTime = self->_audioRecorder.currentTime;
|
|
54
|
+
[self sendEventWithName:@"onRecordingProgress"
|
|
55
|
+
body:@{
|
|
56
|
+
@"duration": @(currentTime * 1000),
|
|
57
|
+
@"isRecording": @(self->_audioRecorder.isRecording)
|
|
58
|
+
}];
|
|
59
|
+
}
|
|
60
|
+
}];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
- (void)stopProgressTimer {
|
|
64
|
+
if (_progressTimer) {
|
|
65
|
+
[_progressTimer invalidate];
|
|
66
|
+
_progressTimer = nil;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
- (NSString *)getRecordingDirectory {
|
|
26
71
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(
|
|
27
72
|
NSDocumentDirectory,
|
|
@@ -104,6 +149,7 @@
|
|
|
104
149
|
}
|
|
105
150
|
|
|
106
151
|
_isRecording = YES;
|
|
152
|
+
[self startProgressTimer];
|
|
107
153
|
resolve(nil);
|
|
108
154
|
} @catch (NSException *exception) {
|
|
109
155
|
reject(@"START_RECORDING_ERROR", exception.reason, nil);
|
|
@@ -118,6 +164,7 @@
|
|
|
118
164
|
return;
|
|
119
165
|
}
|
|
120
166
|
|
|
167
|
+
[self stopProgressTimer];
|
|
121
168
|
[_audioRecorder stop];
|
|
122
169
|
NSString *filePath = [_recordingURL path];
|
|
123
170
|
_audioRecorder = nil;
|
|
@@ -162,6 +209,8 @@
|
|
|
162
209
|
- (void)cancelRecording:(RCTPromiseResolveBlock)resolve
|
|
163
210
|
reject:(RCTPromiseRejectBlock)reject {
|
|
164
211
|
@try {
|
|
212
|
+
[self stopProgressTimer];
|
|
213
|
+
|
|
165
214
|
if (_audioRecorder) {
|
|
166
215
|
[_audioRecorder stop];
|
|
167
216
|
_audioRecorder = nil;
|
|
@@ -224,11 +273,13 @@
|
|
|
224
273
|
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder
|
|
225
274
|
successfully:(BOOL)flag {
|
|
226
275
|
_isRecording = NO;
|
|
276
|
+
[self stopProgressTimer];
|
|
227
277
|
}
|
|
228
278
|
|
|
229
279
|
- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder
|
|
230
280
|
error:(NSError *)error {
|
|
231
281
|
_isRecording = NO;
|
|
282
|
+
[self stopProgressTimer];
|
|
232
283
|
}
|
|
233
284
|
|
|
234
285
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
package/lib/module/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
import { NativeEventEmitter, NativeModules } from 'react-native';
|
|
3
4
|
import NativeModule from "./NativeNosniaAudioRecorder.js";
|
|
4
5
|
class AudioRecorder {
|
|
5
6
|
static instance = null;
|
|
6
|
-
|
|
7
|
+
progressListener = null;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.eventEmitter = new NativeEventEmitter(NativeModules.NosniaAudioRecorder);
|
|
10
|
+
}
|
|
7
11
|
static getInstance() {
|
|
8
12
|
if (!AudioRecorder.instance) {
|
|
9
13
|
AudioRecorder.instance = new AudioRecorder();
|
|
@@ -11,6 +15,35 @@ class AudioRecorder {
|
|
|
11
15
|
return AudioRecorder.instance;
|
|
12
16
|
}
|
|
13
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Add a listener for recording progress updates
|
|
20
|
+
* @param callback Function called with duration updates (every 100ms during recording)
|
|
21
|
+
* @returns Function to remove the listener
|
|
22
|
+
*/
|
|
23
|
+
addRecordingProgressListener(callback) {
|
|
24
|
+
// Remove existing listener if any
|
|
25
|
+
if (this.progressListener) {
|
|
26
|
+
this.progressListener.remove();
|
|
27
|
+
}
|
|
28
|
+
this.progressListener = this.eventEmitter.addListener('onRecordingProgress', data => callback(data));
|
|
29
|
+
return () => {
|
|
30
|
+
if (this.progressListener) {
|
|
31
|
+
this.progressListener.remove();
|
|
32
|
+
this.progressListener = null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Remove the recording progress listener
|
|
39
|
+
*/
|
|
40
|
+
removeRecordingProgressListener() {
|
|
41
|
+
if (this.progressListener) {
|
|
42
|
+
this.progressListener.remove();
|
|
43
|
+
this.progressListener = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
/**
|
|
15
48
|
* Request audio recording permission from the user
|
|
16
49
|
* @returns Promise that resolves to true if permission is granted
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModule","AudioRecorder","instance","constructor","getInstance","requestPermission","requestAudioPermission","error","console","checkPermission","checkAudioPermission","startRecording","options","hasPermission","Error","config","bitrate","channels","sampleRate","stopRecording","pauseRecording","resumeRecording","cancelRecording","getStatus","getRecorderStatus"
|
|
1
|
+
{"version":3,"names":["NativeEventEmitter","NativeModules","NativeModule","AudioRecorder","instance","progressListener","constructor","eventEmitter","NosniaAudioRecorder","getInstance","addRecordingProgressListener","callback","remove","addListener","data","removeRecordingProgressListener","requestPermission","requestAudioPermission","error","console","checkPermission","checkAudioPermission","startRecording","options","hasPermission","Error","config","bitrate","channels","sampleRate","stopRecording","pauseRecording","resumeRecording","cancelRecording","getStatus","getRecorderStatus"],"sourceRoot":"..\\..\\src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { type RecorderOptions, type RecorderStatus } from './NativeNosniaAudioRecorder';
|
|
2
2
|
export type { RecorderOptions, RecorderStatus };
|
|
3
|
+
export type RecordingProgressCallback = (data: {
|
|
4
|
+
duration: number;
|
|
5
|
+
isRecording: boolean;
|
|
6
|
+
}) => void;
|
|
3
7
|
declare class AudioRecorder {
|
|
4
8
|
private static instance;
|
|
9
|
+
private eventEmitter;
|
|
10
|
+
private progressListener;
|
|
5
11
|
private constructor();
|
|
6
12
|
static getInstance(): AudioRecorder;
|
|
13
|
+
/**
|
|
14
|
+
* Add a listener for recording progress updates
|
|
15
|
+
* @param callback Function called with duration updates (every 100ms during recording)
|
|
16
|
+
* @returns Function to remove the listener
|
|
17
|
+
*/
|
|
18
|
+
addRecordingProgressListener(callback: RecordingProgressCallback): () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Remove the recording progress listener
|
|
21
|
+
*/
|
|
22
|
+
removeRecordingProgressListener(): void;
|
|
7
23
|
/**
|
|
8
24
|
* Request audio recording permission from the user
|
|
9
25
|
* @returns Promise that resolves to true if permission is granted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAqB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,6BAA6B,CAAC;AAErC,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC;AAEhD,MAAM,MAAM,yBAAyB,GAAG,CAAC,IAAI,EAAE;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB,KAAK,IAAI,CAAC;AAEX,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA8B;IACrD,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,gBAAgB,CAAa;IAErC,OAAO;IAMP,MAAM,CAAC,WAAW,IAAI,aAAa;IAOnC;;;;OAIG;IACH,4BAA4B,CAC1B,QAAQ,EAAE,yBAAyB,GAClC,MAAM,IAAI;IAmBb;;OAEG;IACH,+BAA+B,IAAI,IAAI;IAOvC;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAS3C;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IASzC;;;;OAIG;IACG,cAAc,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB9D;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAStC;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IASrC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAStC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAStC;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC;CAQ3C;AAED,eAAO,MAAM,mBAAmB,eAA8B,CAAC"}
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NativeEventEmitter, NativeModules } from 'react-native';
|
|
1
2
|
import NativeModule, {
|
|
2
3
|
type RecorderOptions,
|
|
3
4
|
type RecorderStatus,
|
|
@@ -5,10 +6,21 @@ import NativeModule, {
|
|
|
5
6
|
|
|
6
7
|
export type { RecorderOptions, RecorderStatus };
|
|
7
8
|
|
|
9
|
+
export type RecordingProgressCallback = (data: {
|
|
10
|
+
duration: number;
|
|
11
|
+
isRecording: boolean;
|
|
12
|
+
}) => void;
|
|
13
|
+
|
|
8
14
|
class AudioRecorder {
|
|
9
15
|
private static instance: AudioRecorder | null = null;
|
|
16
|
+
private eventEmitter: NativeEventEmitter;
|
|
17
|
+
private progressListener: any = null;
|
|
10
18
|
|
|
11
|
-
private constructor() {
|
|
19
|
+
private constructor() {
|
|
20
|
+
this.eventEmitter = new NativeEventEmitter(
|
|
21
|
+
NativeModules.NosniaAudioRecorder
|
|
22
|
+
);
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
static getInstance(): AudioRecorder {
|
|
14
26
|
if (!AudioRecorder.instance) {
|
|
@@ -17,6 +29,42 @@ class AudioRecorder {
|
|
|
17
29
|
return AudioRecorder.instance;
|
|
18
30
|
}
|
|
19
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Add a listener for recording progress updates
|
|
34
|
+
* @param callback Function called with duration updates (every 100ms during recording)
|
|
35
|
+
* @returns Function to remove the listener
|
|
36
|
+
*/
|
|
37
|
+
addRecordingProgressListener(
|
|
38
|
+
callback: RecordingProgressCallback
|
|
39
|
+
): () => void {
|
|
40
|
+
// Remove existing listener if any
|
|
41
|
+
if (this.progressListener) {
|
|
42
|
+
this.progressListener.remove();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.progressListener = this.eventEmitter.addListener(
|
|
46
|
+
'onRecordingProgress',
|
|
47
|
+
(data: any) => callback(data)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
if (this.progressListener) {
|
|
52
|
+
this.progressListener.remove();
|
|
53
|
+
this.progressListener = null;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove the recording progress listener
|
|
60
|
+
*/
|
|
61
|
+
removeRecordingProgressListener(): void {
|
|
62
|
+
if (this.progressListener) {
|
|
63
|
+
this.progressListener.remove();
|
|
64
|
+
this.progressListener = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
20
68
|
/**
|
|
21
69
|
* Request audio recording permission from the user
|
|
22
70
|
* @returns Promise that resolves to true if permission is granted
|