nosnia-audio-recorder 0.1.1 → 0.3.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 +66 -3
- package/android/src/main/java/com/nosniaaudiorecorder/NosniaAudioPlayerModule.kt +238 -0
- package/android/src/main/java/com/nosniaaudiorecorder/NosniaAudioRecorderModule.kt +59 -1
- package/android/src/main/java/com/nosniaaudiorecorder/NosniaAudioRecorderPackage.kt +12 -4
- package/ios/NosniaAudioPlayer.h +7 -0
- package/ios/NosniaAudioPlayer.mm +263 -0
- package/ios/NosniaAudioRecorder.h +2 -1
- package/ios/NosniaAudioRecorder.mm +51 -0
- package/lib/module/AudioPlayer.js +176 -0
- package/lib/module/AudioPlayer.js.map +1 -0
- package/lib/module/NativeNosniaAudioPlayer.js +5 -0
- package/lib/module/NativeNosniaAudioPlayer.js.map +1 -0
- package/lib/module/index.js +37 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/AudioPlayer.d.ts +76 -0
- package/lib/typescript/src/AudioPlayer.d.ts.map +1 -0
- package/lib/typescript/src/NativeNosniaAudioPlayer.d.ts +24 -0
- package/lib/typescript/src/NativeNosniaAudioPlayer.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +17 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +19 -7
- package/src/AudioPlayer.tsx +201 -0
- package/src/NativeNosniaAudioPlayer.ts +26 -0
- package/src/index.tsx +58 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#import "NosniaAudioPlayer.h"
|
|
2
|
+
#import <AVFoundation/AVFoundation.h>
|
|
3
|
+
#import <React/RCTBridgeModule.h>
|
|
4
|
+
#import <React/RCTEventEmitter.h>
|
|
5
|
+
|
|
6
|
+
@interface NosniaAudioPlayer () <AVAudioPlayerDelegate>
|
|
7
|
+
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;
|
|
8
|
+
@property (nonatomic, assign) BOOL isPlaying;
|
|
9
|
+
@property (nonatomic, strong) NSTimer *progressTimer;
|
|
10
|
+
@property (nonatomic, strong) NSString *currentFilePath;
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
@implementation NosniaAudioPlayer {
|
|
14
|
+
AVAudioPlayer *_audioPlayer;
|
|
15
|
+
BOOL _isPlaying;
|
|
16
|
+
NSTimer *_progressTimer;
|
|
17
|
+
NSString *_currentFilePath;
|
|
18
|
+
BOOL _hasListeners;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
RCT_EXPORT_MODULE(NosniaAudioPlayer)
|
|
22
|
+
|
|
23
|
+
- (instancetype)init {
|
|
24
|
+
self = [super init];
|
|
25
|
+
if (self) {
|
|
26
|
+
_isPlaying = NO;
|
|
27
|
+
_hasListeners = NO;
|
|
28
|
+
}
|
|
29
|
+
return self;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
- (NSArray<NSString *> *)supportedEvents {
|
|
33
|
+
return @[@"onPlaybackProgress", @"onPlaybackComplete"];
|
|
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->_audioPlayer && self->_isPlaying) {
|
|
53
|
+
NSTimeInterval currentTime = self->_audioPlayer.currentTime;
|
|
54
|
+
NSTimeInterval duration = self->_audioPlayer.duration;
|
|
55
|
+
[self sendEventWithName:@"onPlaybackProgress"
|
|
56
|
+
body:@{
|
|
57
|
+
@"currentTime": @(currentTime * 1000),
|
|
58
|
+
@"duration": @(duration * 1000),
|
|
59
|
+
@"isPlaying": @(self->_audioPlayer.isPlaying)
|
|
60
|
+
}];
|
|
61
|
+
}
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
- (void)stopProgressTimer {
|
|
66
|
+
if (_progressTimer) {
|
|
67
|
+
[_progressTimer invalidate];
|
|
68
|
+
_progressTimer = nil;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)startPlaying:(NSDictionary *)options
|
|
73
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
74
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
75
|
+
@try {
|
|
76
|
+
NSString *filePath = options[@"filePath"];
|
|
77
|
+
if (!filePath) {
|
|
78
|
+
reject(@"INVALID_PATH", @"File path is required", nil);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
NSNumber *volume = options[@"volume"] ?: @(1.0);
|
|
83
|
+
NSNumber *loop = options[@"loop"] ?: @(NO);
|
|
84
|
+
|
|
85
|
+
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
|
|
86
|
+
NSError *error = nil;
|
|
87
|
+
|
|
88
|
+
// Setup audio session for playback
|
|
89
|
+
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
|
90
|
+
[audioSession setCategory:AVAudioSessionCategoryPlayback error:&error];
|
|
91
|
+
if (error) {
|
|
92
|
+
reject(@"AUDIO_SESSION_ERROR", error.description, error);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
[audioSession setActive:YES error:&error];
|
|
96
|
+
if (error) {
|
|
97
|
+
reject(@"AUDIO_SESSION_ERROR", error.description, error);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error];
|
|
102
|
+
if (error) {
|
|
103
|
+
reject(@"INIT_PLAYER_ERROR", error.description, error);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_audioPlayer.delegate = self;
|
|
108
|
+
_audioPlayer.volume = [volume floatValue];
|
|
109
|
+
_audioPlayer.numberOfLoops = [loop boolValue] ? -1 : 0;
|
|
110
|
+
|
|
111
|
+
if (![_audioPlayer prepareToPlay]) {
|
|
112
|
+
reject(@"PREPARE_ERROR", @"Failed to prepare audio player", nil);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (![_audioPlayer play]) {
|
|
117
|
+
reject(@"START_PLAYING_ERROR", @"Failed to start playing", nil);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_isPlaying = YES;
|
|
122
|
+
_currentFilePath = filePath;
|
|
123
|
+
[self startProgressTimer];
|
|
124
|
+
resolve(nil);
|
|
125
|
+
} @catch (NSException *exception) {
|
|
126
|
+
reject(@"START_PLAYING_ERROR", exception.reason, nil);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
- (void)stopPlaying:(RCTPromiseResolveBlock)resolve
|
|
131
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
132
|
+
@try {
|
|
133
|
+
[self stopProgressTimer];
|
|
134
|
+
|
|
135
|
+
if (_audioPlayer) {
|
|
136
|
+
[_audioPlayer stop];
|
|
137
|
+
_audioPlayer = nil;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_isPlaying = NO;
|
|
141
|
+
_currentFilePath = nil;
|
|
142
|
+
resolve(nil);
|
|
143
|
+
} @catch (NSException *exception) {
|
|
144
|
+
reject(@"STOP_PLAYING_ERROR", exception.reason, nil);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
- (void)pausePlaying:(RCTPromiseResolveBlock)resolve
|
|
149
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
150
|
+
@try {
|
|
151
|
+
if (!_isPlaying || !_audioPlayer) {
|
|
152
|
+
reject(@"NOT_PLAYING", @"No playback in progress", nil);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
[_audioPlayer pause];
|
|
157
|
+
_isPlaying = NO;
|
|
158
|
+
resolve(nil);
|
|
159
|
+
} @catch (NSException *exception) {
|
|
160
|
+
reject(@"PAUSE_ERROR", exception.reason, nil);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
- (void)resumePlaying:(RCTPromiseResolveBlock)resolve
|
|
165
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
166
|
+
@try {
|
|
167
|
+
if (!_audioPlayer || _isPlaying) {
|
|
168
|
+
reject(@"NOT_PAUSED", @"Playback is not paused", nil);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
[_audioPlayer play];
|
|
173
|
+
_isPlaying = YES;
|
|
174
|
+
resolve(nil);
|
|
175
|
+
} @catch (NSException *exception) {
|
|
176
|
+
reject(@"RESUME_ERROR", exception.reason, nil);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
- (void)seekToTime:(double)time
|
|
181
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
182
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
183
|
+
@try {
|
|
184
|
+
if (!_audioPlayer) {
|
|
185
|
+
reject(@"NO_PLAYER", @"No audio player initialized", nil);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_audioPlayer.currentTime = time;
|
|
190
|
+
resolve(nil);
|
|
191
|
+
} @catch (NSException *exception) {
|
|
192
|
+
reject(@"SEEK_ERROR", exception.reason, nil);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
- (void)setVolume:(double)volume
|
|
197
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
198
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
199
|
+
@try {
|
|
200
|
+
if (!_audioPlayer) {
|
|
201
|
+
reject(@"NO_PLAYER", @"No audio player initialized", nil);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_audioPlayer.volume = volume;
|
|
206
|
+
resolve(nil);
|
|
207
|
+
} @catch (NSException *exception) {
|
|
208
|
+
reject(@"SET_VOLUME_ERROR", exception.reason, nil);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
- (void)getPlayerStatus:(RCTPromiseResolveBlock)resolve
|
|
213
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
214
|
+
@try {
|
|
215
|
+
NSMutableDictionary *status = [NSMutableDictionary dictionary];
|
|
216
|
+
status[@"isPlaying"] = @(_isPlaying);
|
|
217
|
+
|
|
218
|
+
if (_audioPlayer) {
|
|
219
|
+
status[@"duration"] = @(_audioPlayer.duration * 1000);
|
|
220
|
+
status[@"currentTime"] = @(_audioPlayer.currentTime * 1000);
|
|
221
|
+
} else {
|
|
222
|
+
status[@"duration"] = @(0);
|
|
223
|
+
status[@"currentTime"] = @(0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (_currentFilePath) {
|
|
227
|
+
status[@"currentFilePath"] = _currentFilePath;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
resolve(status);
|
|
231
|
+
} @catch (NSException *exception) {
|
|
232
|
+
reject(@"STATUS_ERROR", exception.reason, nil);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#pragma mark - AVAudioPlayerDelegate
|
|
237
|
+
|
|
238
|
+
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
|
|
239
|
+
successfully:(BOOL)flag {
|
|
240
|
+
_isPlaying = NO;
|
|
241
|
+
[self stopProgressTimer];
|
|
242
|
+
|
|
243
|
+
if (_hasListeners) {
|
|
244
|
+
[self sendEventWithName:@"onPlaybackComplete" body:@{}];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player
|
|
249
|
+
error:(NSError *)error {
|
|
250
|
+
_isPlaying = NO;
|
|
251
|
+
[self stopProgressTimer];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
255
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
256
|
+
return std::make_shared<facebook::react::NativeNosniaAudioPlayerSpecJSI>(params);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
+ (NSString *)moduleName {
|
|
260
|
+
return @"NosniaAudioPlayer";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@end
|
|
@@ -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:
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { NativeEventEmitter, NativeModules } from 'react-native';
|
|
4
|
+
import NativeModule from "./NativeNosniaAudioPlayer.js";
|
|
5
|
+
class AudioPlayer {
|
|
6
|
+
static instance = null;
|
|
7
|
+
progressListener = null;
|
|
8
|
+
completeListener = null;
|
|
9
|
+
constructor() {
|
|
10
|
+
this.eventEmitter = new NativeEventEmitter(NativeModules.NosniaAudioPlayer);
|
|
11
|
+
}
|
|
12
|
+
static getInstance() {
|
|
13
|
+
if (!AudioPlayer.instance) {
|
|
14
|
+
AudioPlayer.instance = new AudioPlayer();
|
|
15
|
+
}
|
|
16
|
+
return AudioPlayer.instance;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Add a listener for playback progress updates
|
|
21
|
+
* @param callback Function called with playback progress (every 100ms during playback)
|
|
22
|
+
* @returns Function to remove the listener
|
|
23
|
+
*/
|
|
24
|
+
addPlaybackProgressListener(callback) {
|
|
25
|
+
if (this.progressListener) {
|
|
26
|
+
this.progressListener.remove();
|
|
27
|
+
}
|
|
28
|
+
this.progressListener = this.eventEmitter.addListener('onPlaybackProgress', 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 playback progress listener
|
|
39
|
+
*/
|
|
40
|
+
removePlaybackProgressListener() {
|
|
41
|
+
if (this.progressListener) {
|
|
42
|
+
this.progressListener.remove();
|
|
43
|
+
this.progressListener = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Add a listener for playback completion
|
|
49
|
+
* @param callback Function called when playback completes
|
|
50
|
+
* @returns Function to remove the listener
|
|
51
|
+
*/
|
|
52
|
+
addPlaybackCompleteListener(callback) {
|
|
53
|
+
if (this.completeListener) {
|
|
54
|
+
this.completeListener.remove();
|
|
55
|
+
}
|
|
56
|
+
this.completeListener = this.eventEmitter.addListener('onPlaybackComplete', callback);
|
|
57
|
+
return () => {
|
|
58
|
+
if (this.completeListener) {
|
|
59
|
+
this.completeListener.remove();
|
|
60
|
+
this.completeListener = null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Remove the playback complete listener
|
|
67
|
+
*/
|
|
68
|
+
removePlaybackCompleteListener() {
|
|
69
|
+
if (this.completeListener) {
|
|
70
|
+
this.completeListener.remove();
|
|
71
|
+
this.completeListener = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start playing audio from a file path
|
|
77
|
+
* @param options Configuration options (filePath, volume, loop)
|
|
78
|
+
* @returns Promise that resolves when playback starts
|
|
79
|
+
*/
|
|
80
|
+
async startPlaying(options) {
|
|
81
|
+
try {
|
|
82
|
+
const config = {
|
|
83
|
+
volume: 1.0,
|
|
84
|
+
loop: false,
|
|
85
|
+
...options
|
|
86
|
+
};
|
|
87
|
+
return await NativeModule.startPlaying(config);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Error starting playback:', error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Stop playing and reset player
|
|
96
|
+
* @returns Promise that resolves when playback stops
|
|
97
|
+
*/
|
|
98
|
+
async stopPlaying() {
|
|
99
|
+
try {
|
|
100
|
+
return await NativeModule.stopPlaying();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Error stopping playback:', error);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Pause the current playback
|
|
109
|
+
* @returns Promise that resolves when playback is paused
|
|
110
|
+
*/
|
|
111
|
+
async pausePlaying() {
|
|
112
|
+
try {
|
|
113
|
+
return await NativeModule.pausePlaying();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Error pausing playback:', error);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resume a paused playback
|
|
122
|
+
* @returns Promise that resolves when playback resumes
|
|
123
|
+
*/
|
|
124
|
+
async resumePlaying() {
|
|
125
|
+
try {
|
|
126
|
+
return await NativeModule.resumePlaying();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('Error resuming playback:', error);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Seek to a specific time in the audio
|
|
135
|
+
* @param time Time in seconds
|
|
136
|
+
* @returns Promise that resolves when seek completes
|
|
137
|
+
*/
|
|
138
|
+
async seekToTime(time) {
|
|
139
|
+
try {
|
|
140
|
+
return await NativeModule.seekToTime(time);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('Error seeking:', error);
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Set the playback volume
|
|
149
|
+
* @param volume Volume level (0.0 to 1.0)
|
|
150
|
+
* @returns Promise that resolves when volume is set
|
|
151
|
+
*/
|
|
152
|
+
async setVolume(volume) {
|
|
153
|
+
try {
|
|
154
|
+
const clampedVolume = Math.max(0, Math.min(1, volume));
|
|
155
|
+
return await NativeModule.setVolume(clampedVolume);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error('Error setting volume:', error);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get the current status of the player
|
|
164
|
+
* @returns Promise that resolves to the current player status
|
|
165
|
+
*/
|
|
166
|
+
async getStatus() {
|
|
167
|
+
try {
|
|
168
|
+
return await NativeModule.getPlayerStatus();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Error getting player status:', error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export const NosniaAudioPlayer = AudioPlayer.getInstance();
|
|
176
|
+
//# sourceMappingURL=AudioPlayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NativeEventEmitter","NativeModules","NativeModule","AudioPlayer","instance","progressListener","completeListener","constructor","eventEmitter","NosniaAudioPlayer","getInstance","addPlaybackProgressListener","callback","remove","addListener","data","removePlaybackProgressListener","addPlaybackCompleteListener","removePlaybackCompleteListener","startPlaying","options","config","volume","loop","error","console","stopPlaying","pausePlaying","resumePlaying","seekToTime","time","setVolume","clampedVolume","Math","max","min","getStatus","getPlayerStatus"],"sourceRoot":"..\\..\\src","sources":["AudioPlayer.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"..\\..\\src","sources":["NativeNosniaAudioPlayer.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAyBpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,mBAAmB,CAAC","ignoreList":[]}
|
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
|
|
@@ -130,4 +163,7 @@ class AudioRecorder {
|
|
|
130
163
|
}
|
|
131
164
|
}
|
|
132
165
|
export const NosniaAudioRecorder = AudioRecorder.getInstance();
|
|
166
|
+
|
|
167
|
+
// Export AudioPlayer
|
|
168
|
+
export { NosniaAudioPlayer } from "./AudioPlayer.js";
|
|
133
169
|
//# sourceMappingURL=index.js.map
|
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","NosniaAudioPlayer"],"sourceRoot":"..\\..\\src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|