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
@@ -2,7 +2,11 @@
2
2
  #include <audioapi/android/core/AndroidAudioRecorder.h>
3
3
  #include <audioapi/android/core/utils/AndroidFileWriterBackend.h>
4
4
  #include <audioapi/android/core/utils/AndroidRecorderCallback.h>
5
+
6
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
5
7
  #include <audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h>
8
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
9
+
6
10
  #include <audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h>
7
11
  #include <audioapi/core/sources/RecorderAdapterNode.h>
8
12
  #include <audioapi/core/utils/Constants.h>
@@ -29,6 +33,25 @@ AndroidAudioRecorder::AndroidAudioRecorder(
29
33
  }
30
34
 
31
35
  AndroidAudioRecorder::~AndroidAudioRecorder() {
36
+ {
37
+ std::scoped_lock dtorLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
38
+
39
+ if (usesFileOutput()) {
40
+ fileOutputEnabled_.store(false, std::memory_order_release);
41
+ fileWriter_->closeFile();
42
+ }
43
+
44
+ if (usesCallback()) {
45
+ callbackOutputEnabled_.store(false, std::memory_order_release);
46
+ dataCallback_->cleanup();
47
+ }
48
+
49
+ if (isConnected()) {
50
+ isConnected_.store(false, std::memory_order_release);
51
+ adapterNode_->cleanup();
52
+ }
53
+ }
54
+
32
55
  nativeAudioRecorder_.release();
33
56
 
34
57
  if (mStream_) {
@@ -86,7 +109,7 @@ Result<std::string, std::string> AndroidAudioRecorder::start() {
86
109
 
87
110
  if (usesFileOutput()) {
88
111
  auto fileResult =
89
- std::dynamic_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
112
+ std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
90
113
  ->openFile(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
91
114
 
92
115
  if (!fileResult.is_ok()) {
@@ -98,7 +121,7 @@ Result<std::string, std::string> AndroidAudioRecorder::start() {
98
121
  }
99
122
 
100
123
  if (usesCallback()) {
101
- std::dynamic_pointer_cast<AndroidRecorderCallback>(dataCallback_)
124
+ std::static_pointer_cast<AndroidRecorderCallback>(dataCallback_)
102
125
  ->prepare(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
103
126
  }
104
127
 
@@ -172,13 +195,18 @@ Result<std::string, std::string> AndroidAudioRecorder::enableFileOutput(
172
195
  if (properties->format == AudioFileProperties::Format::WAV) {
173
196
  fileWriter_ = std::make_shared<MiniAudioFileWriter>(audioEventHandlerRegistry_, properties);
174
197
  } else {
198
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
175
199
  fileWriter_ = std::make_shared<android::ffmpeg::FFmpegAudioFileWriter>(
176
200
  audioEventHandlerRegistry_, properties);
201
+ #else
202
+ return Result<std::string, std::string>::Err(
203
+ "FFmpeg backend is disabled. Cannot create file writer for the requested format. Use WAV format instead.");
204
+ #endif
177
205
  }
178
206
 
179
207
  if (!isIdle()) {
180
208
  auto fileResult =
181
- std::dynamic_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
209
+ std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
182
210
  ->openFile(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
183
211
 
184
212
  if (!fileResult.is_ok()) {
@@ -227,7 +255,7 @@ Result<NoneType, std::string> AndroidAudioRecorder::setOnAudioReadyCallback(
227
255
  audioEventHandlerRegistry_, sampleRate, bufferLength, channelCount, callbackId);
228
256
 
229
257
  if (!isIdle()) {
230
- std::dynamic_pointer_cast<AndroidRecorderCallback>(dataCallback_)
258
+ std::static_pointer_cast<AndroidRecorderCallback>(dataCallback_)
231
259
  ->prepare(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
232
260
  }
233
261
 
@@ -271,14 +299,14 @@ oboe::DataCallbackResult AndroidAudioRecorder::onAudioReady(
271
299
 
272
300
  if (usesFileOutput()) {
273
301
  if (auto fileWriterLock = Locker::tryLock(fileWriterMutex_)) {
274
- std::dynamic_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
302
+ std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
275
303
  ->writeAudioData(audioData, numFrames);
276
304
  }
277
305
  }
278
306
 
279
307
  if (usesCallback()) {
280
308
  if (auto callbackLock = Locker::tryLock(callbackMutex_)) {
281
- std::dynamic_pointer_cast<AndroidRecorderCallback>(dataCallback_)
309
+ std::static_pointer_cast<AndroidRecorderCallback>(dataCallback_)
282
310
  ->receiveAudioData(audioData, numFrames);
283
311
  }
284
312
  }
@@ -1,3 +1,5 @@
1
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
2
+
1
3
  extern "C" {
2
4
  #include <libavcodec/avcodec.h>
3
5
  #include <libavformat/avformat.h>
@@ -508,3 +510,5 @@ CloseFileResult FFmpegAudioFileWriter::finalizeOutput() {
508
510
  }
509
511
 
510
512
  } // namespace audioapi::android::ffmpeg
513
+
514
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
@@ -1,3 +1,7 @@
1
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
2
+
3
+ #pragma once
4
+
1
5
  extern "C" {
2
6
  #include <libavcodec/avcodec.h>
3
7
  #include <libavformat/avformat.h>
@@ -46,3 +50,7 @@ template<> inline void AvDtor<AVAudioFifo>::operator()(AVAudioFifo* fifo) const
46
50
  }
47
51
 
48
52
  } // namespace audioapi::android::ffmpeg
53
+
54
+ #else
55
+
56
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
@@ -1,3 +1,5 @@
1
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
2
+
1
3
  extern "C" {
2
4
  #include <libavcodec/avcodec.h>
3
5
  }
@@ -108,3 +110,5 @@ std::string parseErrorCode(int errorCode) {
108
110
  }
109
111
 
110
112
  } // namespace audioapi::android::ffmpeg
113
+
114
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
@@ -3,6 +3,7 @@
3
3
  #include <audioapi/android/core/utils/AndroidFileWriterBackend.h>
4
4
  #include <audioapi/libs/miniaudio/miniaudio.h>
5
5
 
6
+ #include <atomic>
6
7
  #include <string>
7
8
  #include <memory>
8
9
  #include <tuple>
@@ -1,6 +1,7 @@
1
1
  package com.swmansion.audioapi
2
2
 
3
3
  import com.facebook.jni.HybridData
4
+ import com.facebook.react.bridge.Arguments
4
5
  import com.facebook.react.bridge.LifecycleEventListener
5
6
  import com.facebook.react.bridge.Promise
6
7
  import com.facebook.react.bridge.ReactApplicationContext
@@ -9,6 +10,7 @@ import com.facebook.react.bridge.ReadableMap
9
10
  import com.facebook.react.common.annotations.FrameworkAPI
10
11
  import com.facebook.react.module.annotations.ReactModule
11
12
  import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
13
+ import com.swmansion.audioapi.system.ForegroundServiceManager
12
14
  import com.swmansion.audioapi.system.MediaSessionManager
13
15
  import com.swmansion.audioapi.system.NativeFileInfo
14
16
  import com.swmansion.audioapi.system.PermissionRequestListener
@@ -86,7 +88,8 @@ class AudioAPIModule(
86
88
 
87
89
  override fun invalidate() {
88
90
  reactContext.get()?.removeLifecycleEventListener(this)
89
- // think about cleaning up resources, singletons etc.
91
+ // Cleanup foreground service manager
92
+ ForegroundServiceManager.cleanup()
90
93
  }
91
94
 
92
95
  override fun getDevicePreferredSampleRate(): Double = MediaSessionManager.getDevicePreferredSampleRate()
@@ -111,21 +114,6 @@ class AudioAPIModule(
111
114
  // nothing to do here
112
115
  }
113
116
 
114
- override fun setLockScreenInfo(info: ReadableMap?) {
115
- MediaSessionManager.setLockScreenInfo(info)
116
- }
117
-
118
- override fun resetLockScreenInfo() {
119
- MediaSessionManager.resetLockScreenInfo()
120
- }
121
-
122
- override fun enableRemoteCommand(
123
- name: String?,
124
- enabled: Boolean,
125
- ) {
126
- MediaSessionManager.enableRemoteCommand(name!!, enabled)
127
- }
128
-
129
117
  override fun observeAudioInterruptions(enabled: Boolean) {
130
118
  MediaSessionManager.observeAudioInterruptions(enabled)
131
119
  }
@@ -147,7 +135,167 @@ class AudioAPIModule(
147
135
  promise.resolve(MediaSessionManager.checkRecordingPermissions())
148
136
  }
149
137
 
138
+ override fun requestNotificationPermissions(promise: Promise) {
139
+ val permissionRequestListener = PermissionRequestListener(promise)
140
+ MediaSessionManager.requestNotificationPermissions(permissionRequestListener)
141
+ }
142
+
143
+ override fun checkNotificationPermissions(promise: Promise) {
144
+ promise.resolve(MediaSessionManager.checkNotificationPermissions())
145
+ }
146
+
150
147
  override fun getDevicesInfo(promise: Promise) {
151
148
  promise.resolve(MediaSessionManager.getDevicesInfo())
152
149
  }
150
+
151
+ // New notification system methods
152
+ override fun registerNotification(
153
+ type: String?,
154
+ key: String?,
155
+ promise: Promise?,
156
+ ) {
157
+ try {
158
+ if (type == null || key == null) {
159
+ val result = Arguments.createMap()
160
+ result.putBoolean("success", false)
161
+ result.putString("error", "Type and key are required")
162
+ promise?.resolve(result)
163
+ return
164
+ }
165
+
166
+ MediaSessionManager.registerNotification(type, key)
167
+
168
+ val result = Arguments.createMap()
169
+ result.putBoolean("success", true)
170
+ promise?.resolve(result)
171
+ } catch (e: Exception) {
172
+ val result = Arguments.createMap()
173
+ result.putBoolean("success", false)
174
+ result.putString("error", e.message ?: "Unknown error")
175
+ promise?.resolve(result)
176
+ }
177
+ }
178
+
179
+ override fun showNotification(
180
+ key: String?,
181
+ options: ReadableMap?,
182
+ promise: Promise?,
183
+ ) {
184
+ try {
185
+ if (key == null) {
186
+ val result = Arguments.createMap()
187
+ result.putBoolean("success", false)
188
+ result.putString("error", "Key is required")
189
+ promise?.resolve(result)
190
+ return
191
+ }
192
+
193
+ MediaSessionManager.showNotification(key, options)
194
+
195
+ val result = Arguments.createMap()
196
+ result.putBoolean("success", true)
197
+ promise?.resolve(result)
198
+ } catch (e: Exception) {
199
+ val result = Arguments.createMap()
200
+ result.putBoolean("success", false)
201
+ result.putString("error", e.message ?: "Unknown error")
202
+ promise?.resolve(result)
203
+ }
204
+ }
205
+
206
+ override fun updateNotification(
207
+ key: String?,
208
+ options: ReadableMap?,
209
+ promise: Promise?,
210
+ ) {
211
+ try {
212
+ if (key == null) {
213
+ val result = Arguments.createMap()
214
+ result.putBoolean("success", false)
215
+ result.putString("error", "Key is required")
216
+ promise?.resolve(result)
217
+ return
218
+ }
219
+
220
+ MediaSessionManager.updateNotification(key, options)
221
+
222
+ val result = Arguments.createMap()
223
+ result.putBoolean("success", true)
224
+ promise?.resolve(result)
225
+ } catch (e: Exception) {
226
+ val result = Arguments.createMap()
227
+ result.putBoolean("success", false)
228
+ result.putString("error", e.message ?: "Unknown error")
229
+ promise?.resolve(result)
230
+ }
231
+ }
232
+
233
+ override fun hideNotification(
234
+ key: String?,
235
+ promise: Promise?,
236
+ ) {
237
+ try {
238
+ if (key == null) {
239
+ val result = Arguments.createMap()
240
+ result.putBoolean("success", false)
241
+ result.putString("error", "Key is required")
242
+ promise?.resolve(result)
243
+ return
244
+ }
245
+
246
+ MediaSessionManager.hideNotification(key)
247
+
248
+ val result = Arguments.createMap()
249
+ result.putBoolean("success", true)
250
+ promise?.resolve(result)
251
+ } catch (e: Exception) {
252
+ val result = Arguments.createMap()
253
+ result.putBoolean("success", false)
254
+ result.putString("error", e.message ?: "Unknown error")
255
+ promise?.resolve(result)
256
+ }
257
+ }
258
+
259
+ override fun unregisterNotification(
260
+ key: String?,
261
+ promise: Promise?,
262
+ ) {
263
+ try {
264
+ if (key == null) {
265
+ val result = Arguments.createMap()
266
+ result.putBoolean("success", false)
267
+ result.putString("error", "Key is required")
268
+ promise?.resolve(result)
269
+ return
270
+ }
271
+
272
+ MediaSessionManager.unregisterNotification(key)
273
+
274
+ val result = Arguments.createMap()
275
+ result.putBoolean("success", true)
276
+ promise?.resolve(result)
277
+ } catch (e: Exception) {
278
+ val result = Arguments.createMap()
279
+ result.putBoolean("success", false)
280
+ result.putString("error", e.message ?: "Unknown error")
281
+ promise?.resolve(result)
282
+ }
283
+ }
284
+
285
+ override fun isNotificationActive(
286
+ key: String?,
287
+ promise: Promise?,
288
+ ) {
289
+ try {
290
+ if (key == null) {
291
+ promise?.resolve(false)
292
+ return
293
+ }
294
+
295
+ val isActive = MediaSessionManager.isNotificationActive(key)
296
+ promise?.resolve(isActive)
297
+ } catch (e: Exception) {
298
+ promise?.resolve(false)
299
+ }
300
+ }
153
301
  }
@@ -1,24 +1,26 @@
1
1
  package com.swmansion.audioapi.core
2
2
 
3
3
  import com.facebook.common.internal.DoNotStrip
4
- import com.swmansion.audioapi.system.MediaSessionManager
4
+ import com.swmansion.audioapi.system.ForegroundServiceManager
5
+ import java.util.UUID
5
6
 
6
7
  @DoNotStrip
7
8
  class NativeAudioPlayer {
8
- private var sourceNodeId: String? = null
9
+ private var playerId: String? = null
9
10
 
10
11
  @DoNotStrip
11
12
  fun start() {
12
- this.sourceNodeId = MediaSessionManager.attachAudioPlayer(this)
13
- MediaSessionManager.startForegroundServiceIfNecessary()
13
+ if (playerId == null) {
14
+ playerId = UUID.randomUUID().toString()
15
+ ForegroundServiceManager.subscribe("player_$playerId")
16
+ }
14
17
  }
15
18
 
16
19
  @DoNotStrip
17
20
  fun stop() {
18
- this.sourceNodeId?.let {
19
- MediaSessionManager.detachAudioPlayer(it)
20
- this.sourceNodeId = null
21
+ playerId?.let {
22
+ ForegroundServiceManager.unsubscribe("player_$it")
23
+ playerId = null
21
24
  }
22
- MediaSessionManager.stopForegroundServiceIfNecessary()
23
25
  }
24
26
  }
@@ -1,24 +1,26 @@
1
1
  package com.swmansion.audioapi.core
2
2
 
3
3
  import com.facebook.common.internal.DoNotStrip
4
- import com.swmansion.audioapi.system.MediaSessionManager
4
+ import com.swmansion.audioapi.system.ForegroundServiceManager
5
+ import java.util.UUID
5
6
 
6
7
  @DoNotStrip
7
8
  class NativeAudioRecorder {
8
- private var inputNodeId: String? = null
9
+ private var recorderId: String? = null
9
10
 
10
11
  @DoNotStrip
11
12
  fun start() {
12
- this.inputNodeId = MediaSessionManager.attachAudioRecorder(this)
13
- MediaSessionManager.startForegroundServiceIfNecessary()
13
+ if (recorderId == null) {
14
+ recorderId = UUID.randomUUID().toString()
15
+ ForegroundServiceManager.subscribe("recorder_$recorderId")
16
+ }
14
17
  }
15
18
 
16
19
  @DoNotStrip
17
20
  fun stop() {
18
- this.inputNodeId?.let {
19
- MediaSessionManager.detachAudioRecorder(it)
20
- this.inputNodeId = null
21
+ recorderId?.let {
22
+ ForegroundServiceManager.unsubscribe("recorder_$it")
23
+ recorderId = null
21
24
  }
22
- MediaSessionManager.stopForegroundServiceIfNecessary()
23
25
  }
24
26
  }
@@ -11,7 +11,6 @@ import java.util.HashMap
11
11
  class AudioFocusListener(
12
12
  private val audioManager: WeakReference<AudioManager>,
13
13
  private val audioAPIModule: WeakReference<AudioAPIModule>,
14
- private val lockScreenManager: WeakReference<LockScreenManager>,
15
14
  ) : AudioManager.OnAudioFocusChangeListener {
16
15
  private var focusRequest: AudioFocusRequest? = null
17
16
 
@@ -22,7 +21,7 @@ class AudioFocusListener(
22
21
  val body =
23
22
  HashMap<String, Any>().apply {
24
23
  put("type", "began")
25
- put("shouldResume", false)
24
+ put("isTransient", false)
26
25
  }
27
26
  audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
28
27
  }
@@ -31,7 +30,7 @@ class AudioFocusListener(
31
30
  val body =
32
31
  HashMap<String, Any>().apply {
33
32
  put("type", "began")
34
- put("shouldResume", false)
33
+ put("isTransient", true)
35
34
  }
36
35
  audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
37
36
  }
@@ -40,7 +39,7 @@ class AudioFocusListener(
40
39
  val body =
41
40
  HashMap<String, Any>().apply {
42
41
  put("type", "ended")
43
- put("shouldResume", true)
42
+ put("isTransient", false)
44
43
  }
45
44
  audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
46
45
  }
@@ -0,0 +1,128 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.app.Service
7
+ import android.content.Context
8
+ import android.content.Intent
9
+ import android.content.pm.ServiceInfo
10
+ import android.os.Build
11
+ import android.os.IBinder
12
+ import android.util.Log
13
+ import androidx.core.app.NotificationCompat
14
+ import com.swmansion.audioapi.system.MediaSessionManager.CHANNEL_ID
15
+ import com.swmansion.audioapi.system.notification.NotificationRegistry
16
+
17
+ /**
18
+ * Centralized foreground service that can be used by any component that needs foreground capabilities.
19
+ */
20
+ class CentralizedForegroundService : Service() {
21
+ companion object {
22
+ private const val TAG = "CentralizedForegroundService"
23
+ private const val NOTIFICATION_ID = 100
24
+ const val ACTION_START = "START_FOREGROUND"
25
+ const val ACTION_STOP = "STOP_FOREGROUND"
26
+ }
27
+
28
+ override fun onBind(intent: Intent?): IBinder? = null
29
+
30
+ override fun onStartCommand(
31
+ intent: Intent?,
32
+ flags: Int,
33
+ startId: Int,
34
+ ): Int {
35
+ when (intent?.action) {
36
+ ACTION_START -> {
37
+ startForegroundWithNotification()
38
+ }
39
+
40
+ ACTION_STOP -> {
41
+ stopForeground(STOP_FOREGROUND_REMOVE)
42
+ stopSelf()
43
+ }
44
+ }
45
+ return START_NOT_STICKY
46
+ }
47
+
48
+ private fun startForegroundWithNotification() {
49
+ try {
50
+ createNotificationChannelIfNeeded()
51
+
52
+ // Try to use an existing notification first
53
+ val existingNotification = findExistingNotification()
54
+ val (notificationId, notification) =
55
+ if (existingNotification != null) {
56
+ existingNotification
57
+ } else {
58
+ // Fallback to default service notification
59
+ NOTIFICATION_ID to createServiceNotification()
60
+ }
61
+
62
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
63
+ startForeground(
64
+ notificationId,
65
+ notification,
66
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
67
+ )
68
+ } else {
69
+ startForeground(notificationId, notification)
70
+ }
71
+
72
+ Log.d(TAG, "Centralized foreground service started with notification ID: $notificationId")
73
+ } catch (e: Exception) {
74
+ Log.e(TAG, "Error starting foreground service: ${e.message}", e)
75
+ }
76
+ }
77
+
78
+ private fun findExistingNotification(): Pair<Int, Notification>? {
79
+ // Check for recording notification first (priority)
80
+ NotificationRegistry.getBuiltNotification(101)?.let {
81
+ return 101 to it
82
+ }
83
+
84
+ // Check for playback notification
85
+ NotificationRegistry.getBuiltNotification(100)?.let {
86
+ return 100 to it
87
+ }
88
+
89
+ return null
90
+ }
91
+
92
+ private fun createServiceNotification(): Notification =
93
+ NotificationCompat
94
+ .Builder(this, CHANNEL_ID)
95
+ .setContentTitle("Audio Service")
96
+ .setContentText("Audio processing in progress")
97
+ .setSmallIcon(android.R.drawable.ic_btn_speak_now)
98
+ .setPriority(NotificationCompat.PRIORITY_LOW)
99
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
100
+ .setOngoing(true)
101
+ .setAutoCancel(false)
102
+ .build()
103
+
104
+ private fun createNotificationChannelIfNeeded() {
105
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
106
+ val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
107
+
108
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
109
+ val channel =
110
+ NotificationChannel(
111
+ CHANNEL_ID,
112
+ "Audio Service",
113
+ NotificationManager.IMPORTANCE_LOW,
114
+ ).apply {
115
+ description = "Background audio processing"
116
+ setShowBadge(false)
117
+ lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
118
+ }
119
+ notificationManager.createNotificationChannel(channel)
120
+ }
121
+ }
122
+ }
123
+
124
+ override fun onDestroy() {
125
+ Log.d(TAG, "Centralized foreground service destroyed")
126
+ super.onDestroy()
127
+ }
128
+ }