react-native-audio-api 0.11.0-alpha.3 → 0.11.0-alpha.5

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 (151) hide show
  1. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +34 -6
  2. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +4 -0
  3. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/ptrs.hpp +8 -0
  4. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.cpp +4 -0
  5. package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +1 -0
  6. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +164 -16
  7. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +10 -8
  8. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioRecorder.kt +10 -8
  9. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +3 -4
  10. package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +128 -0
  11. package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +116 -0
  12. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +115 -107
  13. package/android/src/main/java/com/swmansion/audioapi/system/PermissionRequestListener.kt +2 -1
  14. package/android/src/main/java/com/swmansion/audioapi/system/notification/BaseNotification.kt +47 -0
  15. package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +191 -0
  16. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +669 -0
  17. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +33 -0
  18. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +303 -0
  19. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +45 -0
  20. package/android/src/main/java/com/swmansion/audioapi/system/notification/SimpleNotification.kt +119 -0
  21. package/common/cpp/audioapi/core/utils/AudioFileWriter.h +1 -0
  22. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +1 -0
  23. package/common/cpp/audioapi/utils/AudioFileProperties.h +17 -17
  24. package/ios/audioapi/ios/AudioAPIModule.h +2 -2
  25. package/ios/audioapi/ios/AudioAPIModule.mm +108 -18
  26. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +8 -7
  27. package/ios/audioapi/ios/core/NativeAudioPlayer.m +1 -1
  28. package/ios/audioapi/ios/core/NativeAudioRecorder.m +9 -2
  29. package/ios/audioapi/ios/system/AudioEngine.h +2 -0
  30. package/ios/audioapi/ios/system/AudioEngine.mm +49 -6
  31. package/ios/audioapi/ios/system/AudioSessionManager.mm +12 -9
  32. package/ios/audioapi/ios/system/NotificationManager.mm +7 -4
  33. package/ios/audioapi/ios/system/notification/BaseNotification.h +58 -0
  34. package/ios/audioapi/ios/system/notification/NotificationRegistry.h +70 -0
  35. package/ios/audioapi/ios/system/notification/NotificationRegistry.mm +172 -0
  36. package/ios/audioapi/ios/system/notification/PlaybackNotification.h +27 -0
  37. package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +427 -0
  38. package/lib/commonjs/api.js +72 -1
  39. package/lib/commonjs/api.js.map +1 -1
  40. package/lib/commonjs/api.web.js +27 -14
  41. package/lib/commonjs/api.web.js.map +1 -1
  42. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  43. package/lib/commonjs/system/AudioManager.js +6 -9
  44. package/lib/commonjs/system/AudioManager.js.map +1 -1
  45. package/lib/commonjs/system/index.js +13 -0
  46. package/lib/commonjs/system/index.js.map +1 -1
  47. package/lib/commonjs/system/notification/PlaybackNotificationManager.js +135 -0
  48. package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -0
  49. package/lib/commonjs/system/notification/RecordingNotificationManager.js +182 -0
  50. package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -0
  51. package/lib/commonjs/system/notification/SimpleNotificationManager.js +122 -0
  52. package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -0
  53. package/lib/commonjs/system/notification/index.js +45 -0
  54. package/lib/commonjs/system/notification/index.js.map +1 -0
  55. package/lib/commonjs/system/notification/types.js +6 -0
  56. package/lib/commonjs/system/notification/types.js.map +1 -0
  57. package/lib/commonjs/types.js +17 -17
  58. package/lib/commonjs/types.js.map +1 -1
  59. package/lib/commonjs/web-system/index.js +17 -0
  60. package/lib/commonjs/web-system/index.js.map +1 -0
  61. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +34 -0
  62. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  63. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +34 -0
  64. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -0
  65. package/lib/commonjs/web-system/notification/index.js +21 -0
  66. package/lib/commonjs/web-system/notification/index.js.map +1 -0
  67. package/lib/module/api.js +4 -0
  68. package/lib/module/api.js.map +1 -1
  69. package/lib/module/api.web.js +3 -1
  70. package/lib/module/api.web.js.map +1 -1
  71. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  72. package/lib/module/system/AudioManager.js +6 -9
  73. package/lib/module/system/AudioManager.js.map +1 -1
  74. package/lib/module/system/index.js +1 -0
  75. package/lib/module/system/index.js.map +1 -1
  76. package/lib/module/system/notification/PlaybackNotificationManager.js +131 -0
  77. package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -0
  78. package/lib/module/system/notification/RecordingNotificationManager.js +178 -0
  79. package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -0
  80. package/lib/module/system/notification/SimpleNotificationManager.js +118 -0
  81. package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -0
  82. package/lib/module/system/notification/index.js +7 -0
  83. package/lib/module/system/notification/index.js.map +1 -0
  84. package/lib/module/system/notification/types.js +4 -0
  85. package/lib/module/system/notification/types.js.map +1 -0
  86. package/lib/module/types.js +17 -17
  87. package/lib/module/types.js.map +1 -1
  88. package/lib/module/web-system/index.js +4 -0
  89. package/lib/module/web-system/index.js.map +1 -0
  90. package/lib/module/web-system/notification/PlaybackNotificationManager.js +30 -0
  91. package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  92. package/lib/module/web-system/notification/RecordingNotificationManager.js +30 -0
  93. package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -0
  94. package/lib/module/web-system/notification/index.js +5 -0
  95. package/lib/module/web-system/notification/index.js.map +1 -0
  96. package/lib/typescript/api.d.ts +2 -0
  97. package/lib/typescript/api.d.ts.map +1 -1
  98. package/lib/typescript/api.web.d.ts +3 -1
  99. package/lib/typescript/api.web.d.ts.map +1 -1
  100. package/lib/typescript/events/types.d.ts +3 -3
  101. package/lib/typescript/events/types.d.ts.map +1 -1
  102. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +16 -5
  103. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  104. package/lib/typescript/system/AudioManager.d.ts +4 -5
  105. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  106. package/lib/typescript/system/index.d.ts +1 -0
  107. package/lib/typescript/system/index.d.ts.map +1 -1
  108. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +22 -0
  109. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  110. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +23 -0
  111. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -0
  112. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +20 -0
  113. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -0
  114. package/lib/typescript/system/notification/index.d.ts +5 -0
  115. package/lib/typescript/system/notification/index.d.ts.map +1 -0
  116. package/lib/typescript/system/notification/types.d.ts +65 -0
  117. package/lib/typescript/system/notification/types.d.ts.map +1 -0
  118. package/lib/typescript/system/types.d.ts +0 -16
  119. package/lib/typescript/system/types.d.ts.map +1 -1
  120. package/lib/typescript/types.d.ts +16 -16
  121. package/lib/typescript/types.d.ts.map +1 -1
  122. package/lib/typescript/web-system/index.d.ts +2 -0
  123. package/lib/typescript/web-system/index.d.ts.map +1 -0
  124. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +19 -0
  125. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  126. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +19 -0
  127. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -0
  128. package/lib/typescript/web-system/notification/index.d.ts +3 -0
  129. package/lib/typescript/web-system/notification/index.d.ts.map +1 -0
  130. package/package.json +1 -1
  131. package/src/api.ts +17 -0
  132. package/src/api.web.ts +7 -2
  133. package/src/events/types.ts +3 -4
  134. package/src/specs/NativeAudioAPIModule.ts +23 -7
  135. package/src/system/AudioManager.ts +10 -23
  136. package/src/system/index.ts +1 -0
  137. package/src/system/notification/PlaybackNotificationManager.ts +193 -0
  138. package/src/system/notification/RecordingNotificationManager.ts +242 -0
  139. package/src/system/notification/SimpleNotificationManager.ts +170 -0
  140. package/src/system/notification/index.ts +4 -0
  141. package/src/system/notification/types.ts +111 -0
  142. package/src/system/types.ts +0 -18
  143. package/src/types.ts +17 -17
  144. package/src/web-system/index.ts +1 -0
  145. package/src/web-system/notification/PlaybackNotificationManager.ts +60 -0
  146. package/src/web-system/notification/RecordingNotificationManager.ts +60 -0
  147. package/src/web-system/notification/index.ts +2 -0
  148. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +0 -347
  149. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +0 -273
  150. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +0 -57
  151. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +0 -61
@@ -11,8 +11,8 @@
11
11
  #import <audioapi/AudioAPIModuleInstaller.h>
12
12
  #import <audioapi/ios/system/AudioEngine.h>
13
13
  #import <audioapi/ios/system/AudioSessionManager.h>
14
- #import <audioapi/ios/system/LockScreenManager.h>
15
14
  #import <audioapi/ios/system/NotificationManager.h>
15
+ #import <audioapi/ios/system/notification/NotificationRegistry.h>
16
16
 
17
17
  #import <audioapi/events/AudioEventHandlerRegistry.h>
18
18
 
@@ -50,7 +50,7 @@ RCT_EXPORT_MODULE(AudioAPIModule);
50
50
  [self.audioEngine cleanup];
51
51
  [self.notificationManager cleanup];
52
52
  [self.audioSessionManager cleanup];
53
- [self.lockScreenManager cleanup];
53
+ [self.notificationRegistry cleanup];
54
54
 
55
55
  _eventHandler = nullptr;
56
56
 
@@ -66,8 +66,8 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
66
66
  {
67
67
  self.audioSessionManager = [[AudioSessionManager alloc] init];
68
68
  self.audioEngine = [[AudioEngine alloc] init];
69
- self.lockScreenManager = [[LockScreenManager alloc] initWithAudioAPIModule:self];
70
69
  self.notificationManager = [[NotificationManager alloc] initWithAudioAPIModule:self];
70
+ self.notificationRegistry = [[NotificationRegistry alloc] initWithAudioAPIModule:self];
71
71
 
72
72
  auto jsiRuntime = reinterpret_cast<facebook::jsi::Runtime *>(self.bridge.runtime);
73
73
 
@@ -142,21 +142,6 @@ RCT_EXPORT_METHOD(
142
142
  allowHaptics:allowHaptics];
143
143
  }
144
144
 
145
- RCT_EXPORT_METHOD(setLockScreenInfo : (NSDictionary *)info)
146
- {
147
- [self.lockScreenManager setLockScreenInfo:info];
148
- }
149
-
150
- RCT_EXPORT_METHOD(resetLockScreenInfo)
151
- {
152
- [self.lockScreenManager resetLockScreenInfo];
153
- }
154
-
155
- RCT_EXPORT_METHOD(enableRemoteCommand : (NSString *)name enabled : (BOOL)enabled)
156
- {
157
- [self.lockScreenManager enableRemoteCommand:name enabled:enabled];
158
- }
159
-
160
145
  RCT_EXPORT_METHOD(observeAudioInterruptions : (BOOL)enabled)
161
146
  {
162
147
  [self.notificationManager observeAudioInterruptions:enabled];
@@ -190,6 +175,25 @@ RCT_EXPORT_METHOD(
190
175
  });
191
176
  }
192
177
 
178
+ RCT_EXPORT_METHOD(
179
+ requestNotificationPermissions : (nonnull RCTPromiseResolveBlock)
180
+ resolve reject : (nonnull RCTPromiseRejectBlock)reject)
181
+ {
182
+ // iOS doesn't require explicit notification permissions for media controls
183
+ // MPNowPlayingInfoCenter and MPRemoteCommandCenter work without permissions
184
+ // Return 'granted' to match the spec interface
185
+ resolve(@"granted");
186
+ }
187
+
188
+ RCT_EXPORT_METHOD(
189
+ checkNotificationPermissions : (nonnull RCTPromiseResolveBlock)
190
+ resolve reject : (nonnull RCTPromiseRejectBlock)reject)
191
+ {
192
+ // iOS doesn't require explicit notification permissions for media controls
193
+ // Return 'granted' to match the spec interface
194
+ resolve(@"granted");
195
+ }
196
+
193
197
  RCT_EXPORT_METHOD(
194
198
  getDevicesInfo : (nonnull RCTPromiseResolveBlock)
195
199
  resolve reject : (nonnull RCTPromiseRejectBlock)reject)
@@ -204,6 +208,92 @@ RCT_EXPORT_METHOD(disableSessionManagement)
204
208
  [self.audioSessionManager disableSessionManagement];
205
209
  }
206
210
 
211
+ // New notification system methods
212
+ RCT_EXPORT_METHOD(
213
+ registerNotification : (NSString *)type key : (NSString *)key resolve : (RCTPromiseResolveBlock)
214
+ resolve reject : (RCTPromiseRejectBlock)reject)
215
+ {
216
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
217
+ BOOL success = [self.notificationRegistry registerNotificationType:type withKey:key];
218
+
219
+ if (success) {
220
+ resolve(@{@"success" : @true});
221
+ } else {
222
+ resolve(@{@"success" : @false, @"error" : @"Failed to register notification"});
223
+ }
224
+ });
225
+ }
226
+
227
+ RCT_EXPORT_METHOD(
228
+ showNotification : (NSString *)key options : (NSDictionary *)
229
+ options resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
230
+ {
231
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
232
+ BOOL success = [self.notificationRegistry showNotificationWithKey:key options:options];
233
+
234
+ if (success) {
235
+ resolve(@{@"success" : @true});
236
+ } else {
237
+ resolve(@{@"success" : @false, @"error" : @"Failed to show notification"});
238
+ }
239
+ });
240
+ }
241
+
242
+ RCT_EXPORT_METHOD(
243
+ updateNotification : (NSString *)key options : (NSDictionary *)
244
+ options resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
245
+ {
246
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
247
+ BOOL success = [self.notificationRegistry updateNotificationWithKey:key options:options];
248
+
249
+ if (success) {
250
+ resolve(@{@"success" : @true});
251
+ } else {
252
+ resolve(@{@"success" : @false, @"error" : @"Failed to update notification"});
253
+ }
254
+ });
255
+ }
256
+
257
+ RCT_EXPORT_METHOD(
258
+ hideNotification : (NSString *)key resolve : (RCTPromiseResolveBlock)
259
+ resolve reject : (RCTPromiseRejectBlock)reject)
260
+ {
261
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
262
+ BOOL success = [self.notificationRegistry hideNotificationWithKey:key];
263
+
264
+ if (success) {
265
+ resolve(@{@"success" : @true});
266
+ } else {
267
+ resolve(@{@"success" : @false, @"error" : @"Failed to hide notification"});
268
+ }
269
+ });
270
+ }
271
+
272
+ RCT_EXPORT_METHOD(
273
+ unregisterNotification : (NSString *)key resolve : (RCTPromiseResolveBlock)
274
+ resolve reject : (RCTPromiseRejectBlock)reject)
275
+ {
276
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
277
+ BOOL success = [self.notificationRegistry unregisterNotificationWithKey:key];
278
+
279
+ if (success) {
280
+ resolve(@{@"success" : @true});
281
+ } else {
282
+ resolve(@{@"success" : @false, @"error" : @"Failed to unregister notification"});
283
+ }
284
+ });
285
+ }
286
+
287
+ RCT_EXPORT_METHOD(
288
+ isNotificationActive : (NSString *)key resolve : (RCTPromiseResolveBlock)
289
+ resolve reject : (RCTPromiseRejectBlock)reject)
290
+ {
291
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
292
+ BOOL isActive = [self.notificationRegistry isNotificationActiveWithKey:key];
293
+ resolve(@(isActive));
294
+ });
295
+ }
296
+
207
297
  #ifdef RCT_NEW_ARCH_ENABLED
208
298
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
209
299
  (const facebook::react::ObjCTurboModule::InitParams &)params
@@ -31,14 +31,14 @@ IOSAudioRecorder::IOSAudioRecorder(
31
31
  AudioReceiverBlock receiverBlock = ^(const AudioBufferList *inputBuffer, int numFrames) {
32
32
  if (usesFileOutput()) {
33
33
  if (auto lock = Locker::tryLock(fileWriterMutex_)) {
34
- std::dynamic_pointer_cast<IOSFileWriter>(fileWriter_)
34
+ std::static_pointer_cast<IOSFileWriter>(fileWriter_)
35
35
  ->writeAudioData(inputBuffer, numFrames);
36
36
  }
37
37
  }
38
38
 
39
39
  if (usesCallback()) {
40
40
  if (auto lock = Locker::tryLock(callbackMutex_)) {
41
- std::dynamic_pointer_cast<IOSRecorderCallback>(dataCallback_)
41
+ std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
42
42
  ->receiveAudioData(inputBuffer, numFrames);
43
43
  }
44
44
  }
@@ -76,7 +76,7 @@ Result<std::string, std::string> IOSAudioRecorder::start()
76
76
  return Result<std::string, std::string>::Err("Microphone permissions are not granted");
77
77
  }
78
78
 
79
- // TODO: recorder should probably request activating the session and setting the options if not set by user
79
+ // TODO: recorder should probably request the options if not set by user
80
80
  // but lets handle that in another PR
81
81
  if (![audioSessionManager isSessionActive]) {
82
82
  return Result<std::string, std::string>::Err("Audio session is not active");
@@ -90,11 +90,12 @@ Result<std::string, std::string> IOSAudioRecorder::start()
90
90
  // Engine will be started again once the native recorder starts
91
91
  [AudioEngine.sharedInstance stopIfNecessary];
92
92
 
93
+ // Estimate the maximum input buffer lengths that can be expected from the sink node
93
94
  size_t maxInputBufferLength = [nativeRecorder_ getBufferSize];
94
95
  auto inputFormat = [nativeRecorder_ getInputFormat];
95
96
 
96
97
  if (usesFileOutput()) {
97
- auto fileResult = std::dynamic_pointer_cast<IOSFileWriter>(fileWriter_)
98
+ auto fileResult = std::static_pointer_cast<IOSFileWriter>(fileWriter_)
98
99
  ->openFile(inputFormat, maxInputBufferLength);
99
100
 
100
101
  if (fileResult.is_err()) {
@@ -106,7 +107,7 @@ Result<std::string, std::string> IOSAudioRecorder::start()
106
107
  }
107
108
 
108
109
  if (usesCallback()) {
109
- auto callbackResult = std::dynamic_pointer_cast<IOSRecorderCallback>(dataCallback_)
110
+ auto callbackResult = std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
110
111
  ->prepare(inputFormat, maxInputBufferLength);
111
112
 
112
113
  if (callbackResult.is_err()) {
@@ -172,7 +173,7 @@ Result<std::string, std::string> IOSAudioRecorder::enableFileOutput(
172
173
  fileWriter_ = std::make_shared<IOSFileWriter>(audioEventHandlerRegistry_, properties);
173
174
 
174
175
  if (!isIdle()) {
175
- auto result = std::dynamic_pointer_cast<IOSFileWriter>(fileWriter_)
176
+ auto result = std::static_pointer_cast<IOSFileWriter>(fileWriter_)
176
177
  ->openFile([nativeRecorder_ getInputFormat], [nativeRecorder_ getBufferSize]);
177
178
 
178
179
  if (result.is_err()) {
@@ -273,7 +274,7 @@ Result<NoneType, std::string> IOSAudioRecorder::setOnAudioReadyCallback(
273
274
  audioEventHandlerRegistry_, sampleRate, bufferLength, channelCount, callbackId);
274
275
 
275
276
  if (!isIdle()) {
276
- auto result = std::dynamic_pointer_cast<IOSRecorderCallback>(dataCallback_)
277
+ auto result = std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
277
278
  ->prepare([nativeRecorder_ getInputFormat], [nativeRecorder_ getBufferSize]);
278
279
 
279
280
  if (result.is_err()) {
@@ -61,7 +61,7 @@
61
61
  assert(audioEngine != nil);
62
62
 
63
63
  [audioEngine detachSourceNodeWithId:self.sourceNodeId];
64
- [audioEngine stopIfNecessary];
64
+ [audioEngine stopIfPossible];
65
65
  self.sourceNodeId = nil;
66
66
  }
67
67
 
@@ -42,6 +42,7 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
42
42
  return self;
43
43
  }
44
44
 
45
+ // Note: this method should be called only after the session is activated
45
46
  - (AVAudioFormat *)getInputFormat
46
47
  {
47
48
  AVAudioFormat *format = [AudioEngine.sharedInstance.audioEngine.inputNode inputFormatForBus:0];
@@ -95,9 +96,15 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
95
96
  {
96
97
  AudioEngine *audioEngine = [AudioEngine sharedInstance];
97
98
  assert(audioEngine != nil);
98
- [audioEngine stopIfNecessary];
99
+ [audioEngine stopIfPossible];
99
100
  [audioEngine detachInputNode];
100
- [audioEngine restartAudioEngine];
101
+
102
+ // This makes sure that the engine releases the input properly when we no longer need it
103
+ // (i.e. no more misleading dot)
104
+ // Restart only if is not running to avoid interruptions of playback
105
+ if ([audioEngine getState] != AudioEngineStateRunning) {
106
+ [audioEngine restartAudioEngine];
107
+ }
101
108
  }
102
109
 
103
110
  - (void)pause
@@ -41,6 +41,8 @@ typedef NS_ENUM(NSInteger, AudioEngineState) {
41
41
  - (void)pauseIfNecessary;
42
42
  - (void)stopIfNecessary;
43
43
 
44
+ - (void)stopIfPossible;
45
+
44
46
  - (void)restartAudioEngine;
45
47
 
46
48
  - (void)logAudioEngineState;
@@ -57,8 +57,6 @@ static AudioEngine *_sharedInstance = nil;
57
57
 
58
58
  - (void)detachSourceNodeWithId:(NSString *)sourceNodeId
59
59
  {
60
- NSLog(@"[AudioEngine] detaching source node with ID: %@", sourceNodeId);
61
-
62
60
  AVAudioSourceNode *sourceNode = [self.sourceNodes valueForKey:sourceNodeId];
63
61
 
64
62
  if (sourceNode == nil) {
@@ -75,7 +73,6 @@ static AudioEngine *_sharedInstance = nil;
75
73
  - (void)attachInputNode:(AVAudioSinkNode *)inputNode
76
74
  {
77
75
  self.inputNode = inputNode;
78
-
79
76
  AVAudioFormat *format = [self.audioEngine.inputNode inputFormatForBus:0];
80
77
 
81
78
  [self.audioEngine attachNode:inputNode];
@@ -105,11 +102,39 @@ static AudioEngine *_sharedInstance = nil;
105
102
 
106
103
  - (void)onInterruptionEnd:(bool)shouldResume
107
104
  {
105
+ NSError *error = nil;
106
+
108
107
  if (self.state != AudioEngineState::AudioEngineStateInterrupted) {
108
+ // If engine was not interrupted, do nothing
109
+ // Not a real condition, but better be safe than sorry :shrug:
110
+ return;
111
+ }
112
+
113
+ // Stop just in case, reset the engine and build it from scratch
114
+ [self stopIfNecessary];
115
+ [self.audioEngine reset];
116
+ [self rebuildAudioEngine];
117
+
118
+ // If shouldResume is false, mark the engine as paused and wait
119
+ // for JS-side resume command
120
+ // TODO: this should be notified to the user f.e. via Event Emitter
121
+ if (!shouldResume) {
122
+ self.state = AudioEngineState::AudioEngineStatePaused;
123
+ return;
124
+ }
125
+
126
+ [self.audioEngine prepare];
127
+ [self.audioEngine startAndReturnError:&error];
128
+
129
+ if (error != nil) {
130
+ NSLog(
131
+ @"Error while restarting the audio engine after interruption: %@",
132
+ [error debugDescription]);
133
+ self.state = AudioEngineState::AudioEngineStateIdle;
109
134
  return;
110
135
  }
111
136
 
112
- // TODO: try to recover
137
+ self.state = AudioEngineState::AudioEngineStateRunning;
113
138
  }
114
139
 
115
140
  - (AudioEngineState)getState
@@ -117,6 +142,7 @@ static AudioEngine *_sharedInstance = nil;
117
142
  return self.state;
118
143
  }
119
144
 
145
+ /// @brief Rebuilds the audio engine by re-attaching and re-connecting all source nodes and input node.
120
146
  - (void)rebuildAudioEngine
121
147
  {
122
148
  self.audioEngine = [[AVAudioEngine alloc] init];
@@ -135,6 +161,7 @@ static AudioEngine *_sharedInstance = nil;
135
161
  }
136
162
  }
137
163
 
164
+ // @brief Starts the audio engine if not already running.
138
165
  - (bool)startEngine
139
166
  {
140
167
  NSError *error = nil;
@@ -208,6 +235,22 @@ static AudioEngine *_sharedInstance = nil;
208
235
  [self stopEngine];
209
236
  }
210
237
 
238
+ - (void)stopIfPossible
239
+ {
240
+ if (self.state == AudioEngineState::AudioEngineStateIdle) {
241
+ return;
242
+ }
243
+
244
+ bool hasInput = self.inputNode != nil;
245
+ bool hasSources = [self.sourceNodes count] > 0;
246
+
247
+ if (hasInput || hasSources) {
248
+ return;
249
+ }
250
+
251
+ [self stopEngine];
252
+ }
253
+
211
254
  - (void)restartAudioEngine
212
255
  {
213
256
  if ([self.audioEngine isRunning]) {
@@ -225,10 +268,10 @@ static AudioEngine *_sharedInstance = nil;
225
268
  NSLog(@"================ 🎧 AVAudioEngine STATE ================");
226
269
 
227
270
  // AVAudioEngine state
228
- NSLog(@"➡️ engine.isRunning: %@", self.audioEngine.isRunning ? @"YES" : @"NO");
271
+ NSLog(@"➡️ engine.isRunning: %@", self.audioEngine.isRunning ? @"true" : @"false");
229
272
  NSLog(
230
273
  @"➡️ engine.isInManualRenderingMode: %@",
231
- self.audioEngine.isInManualRenderingMode ? @"YES" : @"NO");
274
+ self.audioEngine.isInManualRenderingMode ? @"true" : @"false");
232
275
 
233
276
  // Session state
234
277
  NSLog(@"🎚️ Session category: %@", session.category);
@@ -66,15 +66,18 @@ static AudioSessionManager *_sharedInstance = nil;
66
66
  (unsigned long)self.audioSession.categoryOptions);
67
67
  }
68
68
 
69
- if (self.audioSession.allowHapticsAndSystemSoundsDuringRecording != self.allowHapticsAndSounds) {
70
- [self.audioSession setAllowHapticsAndSystemSoundsDuringRecording:self.allowHapticsAndSounds
71
- error:&error];
72
-
73
- if (error != nil) {
74
- NSLog(
75
- @"Error while setting allowHapticsAndSystemSoundsDuringRecording: %@",
76
- [error debugDescription]);
77
- return false;
69
+ if (@available(iOS 13.0, *)) {
70
+ if (self.audioSession.allowHapticsAndSystemSoundsDuringRecording !=
71
+ self.allowHapticsAndSounds) {
72
+ [self.audioSession setAllowHapticsAndSystemSoundsDuringRecording:self.allowHapticsAndSounds
73
+ error:&error];
74
+
75
+ if (error != nil) {
76
+ NSLog(
77
+ @"Error while setting allowHapticsAndSystemSoundsDuringRecording: %@",
78
+ [error debugDescription]);
79
+ return false;
80
+ }
78
81
  }
79
82
  }
80
83
 
@@ -129,11 +129,12 @@ static NSString *NotificationManagerContext = @"NotificationManagerContext";
129
129
  }
130
130
 
131
131
  bool shouldResume = interruptionOption == AVAudioSessionInterruptionOptionShouldResume;
132
- [audioEngine onInterruptionEnd:shouldResume];
133
132
 
134
133
  if (self.audioInterruptionsObserved) {
135
134
  NSDictionary *body = @{@"type" : @"ended", @"shouldResume" : @(shouldResume)};
136
135
  [self.audioAPIModule invokeHandlerWithEventName:@"interruption" eventBody:body];
136
+ } else {
137
+ [audioEngine onInterruptionEnd:shouldResume];
137
138
  }
138
139
  }
139
140
 
@@ -156,11 +157,12 @@ static NSString *NotificationManagerContext = @"NotificationManagerContext";
156
157
  }
157
158
 
158
159
  bool shouldResume = secondaryAudioType == AVAudioSessionSilenceSecondaryAudioHintTypeEnd;
159
- [audioEngine onInterruptionEnd:shouldResume];
160
160
 
161
161
  if (self.audioInterruptionsObserved) {
162
162
  NSDictionary *body = @{@"type" : @"ended", @"shouldResume" : @(shouldResume)};
163
163
  [self.audioAPIModule invokeHandlerWithEventName:@"interruption" eventBody:body];
164
+ } else {
165
+ [audioEngine onInterruptionEnd:shouldResume];
164
166
  }
165
167
  }
166
168
 
@@ -237,7 +239,7 @@ static NSString *NotificationManagerContext = @"NotificationManagerContext";
237
239
  target:self
238
240
  selector:@selector(checkSecondaryAudioHint)
239
241
  userInfo:nil
240
- repeats:YES];
242
+ repeats:true];
241
243
 
242
244
  [[NSRunLoop mainRunLoop] addTimer:self.hintPollingTimer forMode:NSRunLoopCommonModes];
243
245
  }
@@ -273,11 +275,12 @@ static NSString *NotificationManagerContext = @"NotificationManagerContext";
273
275
  return;
274
276
  }
275
277
 
276
- [audioEngine onInterruptionEnd:true];
277
278
  NSDictionary *body = @{@"type" : @"ended", @"shouldResume" : @true};
278
279
 
279
280
  if (self.audioInterruptionsObserved) {
280
281
  [self.audioAPIModule invokeHandlerWithEventName:@"interruption" eventBody:body];
282
+ } else {
283
+ [audioEngine onInterruptionEnd:true];
281
284
  }
282
285
  }
283
286
 
@@ -0,0 +1,58 @@
1
+ #pragma once
2
+
3
+ #import <Foundation/Foundation.h>
4
+
5
+ /**
6
+ * BaseNotification protocol
7
+ *
8
+ * Interface that all notification types must implement.
9
+ */
10
+ @protocol BaseNotification <NSObject>
11
+
12
+ @required
13
+
14
+ /**
15
+ * Initialize the notification.
16
+ * @param options Initialization options (can be nil)
17
+ * @return YES if successful
18
+ */
19
+ - (BOOL)initializeWithOptions:(NSDictionary *)options;
20
+
21
+ /**
22
+ * Show the notification (sets metadata on iOS).
23
+ * @param options Notification options
24
+ * @return YES if successful
25
+ */
26
+ - (BOOL)showWithOptions:(NSDictionary *)options;
27
+
28
+ /**
29
+ * Update notification metadata.
30
+ * @param options Updated information
31
+ * @return YES if successful
32
+ */
33
+ - (BOOL)updateWithOptions:(NSDictionary *)options;
34
+
35
+ /**
36
+ * Hide the notification (clears metadata on iOS).
37
+ * @return YES if successful
38
+ */
39
+ - (BOOL)hide;
40
+
41
+ /**
42
+ * Clean up and release resources.
43
+ */
44
+ - (void)cleanup;
45
+
46
+ /**
47
+ * Check if notification is active.
48
+ * @return YES if active
49
+ */
50
+ - (BOOL)isActive;
51
+
52
+ /**
53
+ * Get notification type identifier.
54
+ * @return Type identifier (e.g., "playback", "recording")
55
+ */
56
+ - (NSString *)getNotificationType;
57
+
58
+ @end
@@ -0,0 +1,70 @@
1
+ #pragma once
2
+
3
+ #import <Foundation/Foundation.h>
4
+ #import <audioapi/ios/system/notification/BaseNotification.h>
5
+
6
+ @class AudioAPIModule;
7
+
8
+ /**
9
+ * NotificationRegistry
10
+ *
11
+ * Central manager for all notification types.
12
+ * Manages registration, lifecycle, and routing of notification implementations.
13
+ */
14
+ @interface NotificationRegistry : NSObject
15
+
16
+ @property (nonatomic, weak) AudioAPIModule *audioAPIModule;
17
+
18
+ - (instancetype)initWithAudioAPIModule:(AudioAPIModule *)audioAPIModule;
19
+
20
+ /**
21
+ * Register a new notification type.
22
+ * @param type The notification type identifier (e.g., "playback", "recording")
23
+ * @param key Unique key for this notification instance
24
+ * @return YES if registration succeeded, NO otherwise
25
+ */
26
+ - (BOOL)registerNotificationType:(NSString *)type withKey:(NSString *)key;
27
+
28
+ /**
29
+ * Show a registered notification.
30
+ * @param key The notification key
31
+ * @param options Options for showing the notification
32
+ * @return YES if successful, NO otherwise
33
+ */
34
+ - (BOOL)showNotificationWithKey:(NSString *)key options:(NSDictionary *)options;
35
+
36
+ /**
37
+ * Update a shown notification.
38
+ * @param key The notification key
39
+ * @param options Updated options
40
+ * @return YES if successful, NO otherwise
41
+ */
42
+ - (BOOL)updateNotificationWithKey:(NSString *)key options:(NSDictionary *)options;
43
+
44
+ /**
45
+ * Hide a notification.
46
+ * @param key The notification key
47
+ * @return YES if successful, NO otherwise
48
+ */
49
+ - (BOOL)hideNotificationWithKey:(NSString *)key;
50
+
51
+ /**
52
+ * Unregister and clean up a notification.
53
+ * @param key The notification key
54
+ * @return YES if successful, NO otherwise
55
+ */
56
+ - (BOOL)unregisterNotificationWithKey:(NSString *)key;
57
+
58
+ /**
59
+ * Check if a notification is active.
60
+ * @param key The notification key
61
+ * @return YES if active, NO otherwise
62
+ */
63
+ - (BOOL)isNotificationActiveWithKey:(NSString *)key;
64
+
65
+ /**
66
+ * Clean up all notifications.
67
+ */
68
+ - (void)cleanup;
69
+
70
+ @end