nosnia-audio-recorder 0.4.0 → 0.4.2

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.
@@ -1,259 +1,259 @@
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
- @end
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
+ @end
package/ios/Podfile ADDED
@@ -0,0 +1,29 @@
1
+ post_install do |installer|
2
+ # Keep existing RN post install if present
3
+ begin
4
+ react_native_post_install(installer)
5
+ rescue NameError
6
+ # react_native_post_install not defined in some templates — ignore
7
+ end
8
+
9
+ # Allow non-modular includes in framework modules to avoid
10
+ # "non-modular-include-in-framework-module" compile errors coming from RN pods.
11
+ installer.pods_project.targets.each do |target|
12
+ target.build_configurations.each do |config|
13
+ config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
14
+ # keep inherited header search paths
15
+ config.build_settings['HEADER_SEARCH_PATHS'] ||= ['$(inherited)']
16
+ end
17
+ end
18
+
19
+ # Also apply to pod native targets (defensive)
20
+ installer.pod_targets.each do |pod|
21
+ begin
22
+ pod.native_target.build_configurations.each do |config|
23
+ config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
24
+ end
25
+ rescue => e
26
+ # ignore pods without native_target
27
+ end
28
+ end
29
+ end