react-native-audio-api 0.11.0-nightly-db51488-20251208 → 0.11.0-nightly-6ba0571-20251209

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 (134) hide show
  1. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +164 -16
  2. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +10 -8
  3. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioRecorder.kt +10 -8
  4. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +8 -23
  5. package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +127 -0
  6. package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +116 -0
  7. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +115 -107
  8. package/android/src/main/java/com/swmansion/audioapi/system/PermissionRequestListener.kt +2 -1
  9. package/android/src/main/java/com/swmansion/audioapi/system/notification/BaseNotification.kt +47 -0
  10. package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +191 -0
  11. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +668 -0
  12. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +33 -0
  13. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +303 -0
  14. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +43 -0
  15. package/android/src/main/java/com/swmansion/audioapi/system/notification/SimpleNotification.kt +119 -0
  16. package/ios/audioapi/ios/AudioAPIModule.h +2 -2
  17. package/ios/audioapi/ios/AudioAPIModule.mm +108 -18
  18. package/ios/audioapi/ios/system/AudioEngine.mm +2 -2
  19. package/ios/audioapi/ios/system/AudioSessionManager.mm +1 -1
  20. package/ios/audioapi/ios/system/NotificationManager.mm +1 -1
  21. package/ios/audioapi/ios/system/notification/BaseNotification.h +58 -0
  22. package/ios/audioapi/ios/system/notification/NotificationRegistry.h +70 -0
  23. package/ios/audioapi/ios/system/notification/NotificationRegistry.mm +172 -0
  24. package/ios/audioapi/ios/system/notification/PlaybackNotification.h +27 -0
  25. package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +427 -0
  26. package/lib/commonjs/api.js +59 -10
  27. package/lib/commonjs/api.js.map +1 -1
  28. package/lib/commonjs/api.web.js +27 -14
  29. package/lib/commonjs/api.web.js.map +1 -1
  30. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  31. package/lib/commonjs/system/AudioManager.js +6 -9
  32. package/lib/commonjs/system/AudioManager.js.map +1 -1
  33. package/lib/commonjs/system/index.js +13 -0
  34. package/lib/commonjs/system/index.js.map +1 -1
  35. package/lib/commonjs/system/notification/PlaybackNotificationManager.js +135 -0
  36. package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -0
  37. package/lib/commonjs/system/notification/RecordingNotificationManager.js +182 -0
  38. package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -0
  39. package/lib/commonjs/system/notification/SimpleNotificationManager.js +122 -0
  40. package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -0
  41. package/lib/commonjs/system/notification/index.js +45 -0
  42. package/lib/commonjs/system/notification/index.js.map +1 -0
  43. package/lib/commonjs/system/notification/types.js +6 -0
  44. package/lib/commonjs/system/notification/types.js.map +1 -0
  45. package/lib/commonjs/web-system/index.js +17 -0
  46. package/lib/commonjs/web-system/index.js.map +1 -0
  47. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +34 -0
  48. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  49. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +34 -0
  50. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -0
  51. package/lib/commonjs/web-system/notification/index.js +21 -0
  52. package/lib/commonjs/web-system/notification/index.js.map +1 -0
  53. package/lib/module/api.js +5 -1
  54. package/lib/module/api.js.map +1 -1
  55. package/lib/module/api.web.js +3 -1
  56. package/lib/module/api.web.js.map +1 -1
  57. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  58. package/lib/module/system/AudioManager.js +6 -9
  59. package/lib/module/system/AudioManager.js.map +1 -1
  60. package/lib/module/system/index.js +1 -0
  61. package/lib/module/system/index.js.map +1 -1
  62. package/lib/module/system/notification/PlaybackNotificationManager.js +131 -0
  63. package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -0
  64. package/lib/module/system/notification/RecordingNotificationManager.js +178 -0
  65. package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -0
  66. package/lib/module/system/notification/SimpleNotificationManager.js +118 -0
  67. package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -0
  68. package/lib/module/system/notification/index.js +7 -0
  69. package/lib/module/system/notification/index.js.map +1 -0
  70. package/lib/module/system/notification/types.js +4 -0
  71. package/lib/module/system/notification/types.js.map +1 -0
  72. package/lib/module/web-system/index.js +4 -0
  73. package/lib/module/web-system/index.js.map +1 -0
  74. package/lib/module/web-system/notification/PlaybackNotificationManager.js +30 -0
  75. package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  76. package/lib/module/web-system/notification/RecordingNotificationManager.js +30 -0
  77. package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -0
  78. package/lib/module/web-system/notification/index.js +5 -0
  79. package/lib/module/web-system/notification/index.js.map +1 -0
  80. package/lib/typescript/api.d.ts +3 -1
  81. package/lib/typescript/api.d.ts.map +1 -1
  82. package/lib/typescript/api.web.d.ts +3 -1
  83. package/lib/typescript/api.web.d.ts.map +1 -1
  84. package/lib/typescript/events/types.d.ts +4 -18
  85. package/lib/typescript/events/types.d.ts.map +1 -1
  86. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +16 -5
  87. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  88. package/lib/typescript/system/AudioManager.d.ts +4 -5
  89. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  90. package/lib/typescript/system/index.d.ts +1 -0
  91. package/lib/typescript/system/index.d.ts.map +1 -1
  92. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +22 -0
  93. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  94. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +23 -0
  95. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -0
  96. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +20 -0
  97. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -0
  98. package/lib/typescript/system/notification/index.d.ts +5 -0
  99. package/lib/typescript/system/notification/index.d.ts.map +1 -0
  100. package/lib/typescript/system/notification/types.d.ts +65 -0
  101. package/lib/typescript/system/notification/types.d.ts.map +1 -0
  102. package/lib/typescript/system/types.d.ts +0 -16
  103. package/lib/typescript/system/types.d.ts.map +1 -1
  104. package/lib/typescript/web-system/index.d.ts +2 -0
  105. package/lib/typescript/web-system/index.d.ts.map +1 -0
  106. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +19 -0
  107. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  108. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +19 -0
  109. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -0
  110. package/lib/typescript/web-system/notification/index.d.ts +3 -0
  111. package/lib/typescript/web-system/notification/index.d.ts.map +1 -0
  112. package/package.json +1 -1
  113. package/src/api.ts +17 -2
  114. package/src/api.web.ts +7 -2
  115. package/src/events/types.ts +4 -20
  116. package/src/specs/NativeAudioAPIModule.ts +23 -7
  117. package/src/system/AudioManager.ts +10 -23
  118. package/src/system/index.ts +1 -0
  119. package/src/system/notification/PlaybackNotificationManager.ts +193 -0
  120. package/src/system/notification/RecordingNotificationManager.ts +242 -0
  121. package/src/system/notification/SimpleNotificationManager.ts +170 -0
  122. package/src/system/notification/index.ts +4 -0
  123. package/src/system/notification/types.ts +110 -0
  124. package/src/system/types.ts +0 -18
  125. package/src/web-system/index.ts +1 -0
  126. package/src/web-system/notification/PlaybackNotificationManager.ts +60 -0
  127. package/src/web-system/notification/RecordingNotificationManager.ts +60 -0
  128. package/src/web-system/notification/index.ts +2 -0
  129. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +0 -347
  130. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +0 -273
  131. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +0 -57
  132. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +0 -61
  133. package/ios/audioapi/ios/system/LockScreenManager.h +0 -23
  134. package/ios/audioapi/ios/system/LockScreenManager.mm +0 -314
@@ -4,13 +4,11 @@ import android.Manifest
4
4
  import android.app.NotificationChannel
5
5
  import android.app.NotificationManager
6
6
  import android.content.Context
7
- import android.content.Intent
8
7
  import android.content.IntentFilter
9
8
  import android.content.pm.PackageManager
10
9
  import android.media.AudioDeviceInfo
11
10
  import android.media.AudioManager
12
11
  import android.os.Build
13
- import android.support.v4.media.session.MediaSessionCompat
14
12
  import androidx.annotation.RequiresApi
15
13
  import androidx.core.app.NotificationCompat
16
14
  import androidx.core.content.ContextCompat
@@ -20,11 +18,14 @@ import com.facebook.react.bridge.ReadableMap
20
18
  import com.facebook.react.modules.core.PermissionAwareActivity
21
19
  import com.facebook.react.modules.core.PermissionListener
22
20
  import com.swmansion.audioapi.AudioAPIModule
23
- import com.swmansion.audioapi.core.NativeAudioPlayer
24
- import com.swmansion.audioapi.core.NativeAudioRecorder
25
21
  import com.swmansion.audioapi.system.PermissionRequestListener.Companion.RECORDING_REQUEST_CODE
22
+ import com.swmansion.audioapi.system.notification.NotificationRegistry
23
+ import com.swmansion.audioapi.system.notification.PlaybackNotification
24
+ import com.swmansion.audioapi.system.notification.PlaybackNotificationReceiver
25
+ import com.swmansion.audioapi.system.notification.RecordingNotification
26
+ import com.swmansion.audioapi.system.notification.RecordingNotificationReceiver
27
+ import com.swmansion.audioapi.system.notification.SimpleNotification
26
28
  import java.lang.ref.WeakReference
27
- import java.util.UUID
28
29
 
29
30
  object MediaSessionManager {
30
31
  private lateinit var audioAPIModule: WeakReference<AudioAPIModule>
@@ -33,17 +34,13 @@ object MediaSessionManager {
33
34
  const val CHANNEL_ID = "react-native-audio-api"
34
35
 
35
36
  private lateinit var audioManager: AudioManager
36
- private lateinit var mediaSession: MediaSessionCompat
37
- lateinit var mediaNotificationManager: MediaNotificationManager
38
- private lateinit var lockScreenManager: LockScreenManager
39
37
  private lateinit var audioFocusListener: AudioFocusListener
40
38
  private lateinit var volumeChangeListener: VolumeChangeListener
41
- private lateinit var mediaReceiver: MediaReceiver
39
+ private lateinit var playbackNotificationReceiver: PlaybackNotificationReceiver
40
+ private lateinit var recordingNotificationReceiver: RecordingNotificationReceiver
42
41
 
43
- private var isServiceRunning = false
44
- private val serviceStateLock = Any()
45
- private val nativeAudioPlayers = mutableMapOf<String, NativeAudioPlayer>()
46
- private val nativeAudioRecorders = mutableMapOf<String, NativeAudioRecorder>()
42
+ // New notification system
43
+ private lateinit var notificationRegistry: NotificationRegistry
47
44
 
48
45
  fun initialize(
49
46
  audioAPIModule: WeakReference<AudioAPIModule>,
@@ -52,118 +49,54 @@ object MediaSessionManager {
52
49
  this.audioAPIModule = audioAPIModule
53
50
  this.reactContext = reactContext
54
51
  this.audioManager = reactContext.get()?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
55
- this.mediaSession = MediaSessionCompat(reactContext.get()!!, "MediaSessionManager")
52
+
53
+ // Initialize ForegroundServiceManager
54
+ ForegroundServiceManager.initialize(reactContext)
56
55
 
57
56
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
58
57
  createChannel()
59
58
  }
60
59
 
61
- this.mediaNotificationManager = MediaNotificationManager(this.reactContext)
62
- this.lockScreenManager = LockScreenManager(this.reactContext, WeakReference(this.mediaSession), WeakReference(mediaNotificationManager))
63
- this.mediaReceiver =
64
- MediaReceiver(this.reactContext, WeakReference(this.mediaSession), WeakReference(this.mediaNotificationManager), this.audioAPIModule)
65
- this.mediaSession.setCallback(MediaSessionCallback(this.audioAPIModule, WeakReference(this.mediaNotificationManager)))
66
-
67
- val filter = IntentFilter()
68
- filter.addAction(MediaNotificationManager.REMOVE_NOTIFICATION)
69
- filter.addAction(MediaNotificationManager.MEDIA_BUTTON)
70
- filter.addAction(Intent.ACTION_MEDIA_BUTTON)
71
- filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
60
+ // Set up PlaybackNotificationReceiver
61
+ PlaybackNotificationReceiver.setAudioAPIModule(audioAPIModule.get())
62
+ this.playbackNotificationReceiver = PlaybackNotificationReceiver()
72
63
 
64
+ // Register PlaybackNotificationReceiver
65
+ val playbackFilter = IntentFilter(PlaybackNotificationReceiver.ACTION_NOTIFICATION_DISMISSED)
73
66
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
74
- this.reactContext.get()!!.registerReceiver(mediaReceiver, filter, Context.RECEIVER_EXPORTED)
67
+ this.reactContext.get()!!.registerReceiver(playbackNotificationReceiver, playbackFilter, Context.RECEIVER_NOT_EXPORTED)
75
68
  } else {
76
69
  ContextCompat.registerReceiver(
77
70
  this.reactContext.get()!!,
78
- mediaReceiver,
79
- filter,
71
+ playbackNotificationReceiver,
72
+ playbackFilter,
80
73
  ContextCompat.RECEIVER_NOT_EXPORTED,
81
74
  )
82
75
  }
83
76
 
84
- this.audioFocusListener =
85
- AudioFocusListener(WeakReference(this.audioManager), this.audioAPIModule, WeakReference(this.lockScreenManager))
86
- this.volumeChangeListener = VolumeChangeListener(WeakReference(this.audioManager), this.audioAPIModule)
87
- }
88
-
89
- fun attachAudioPlayer(player: NativeAudioPlayer): String {
90
- val uuid = UUID.randomUUID().toString()
91
- nativeAudioPlayers[uuid] = player
92
-
93
- return uuid
94
- }
95
-
96
- fun detachAudioPlayer(uuid: String) {
97
- nativeAudioPlayers.remove(uuid)
98
- }
77
+ // Set up RecordingNotificationReceiver
78
+ RecordingNotificationReceiver.setAudioAPIModule(audioAPIModule.get())
79
+ this.recordingNotificationReceiver = RecordingNotificationReceiver()
99
80
 
100
- fun attachAudioRecorder(recorder: NativeAudioRecorder): String {
101
- val uuid = UUID.randomUUID().toString()
102
- nativeAudioRecorders[uuid] = recorder
103
-
104
- return uuid
105
- }
106
-
107
- fun detachAudioRecorder(uuid: String) {
108
- nativeAudioRecorders.remove(uuid)
109
- }
110
-
111
- fun startForegroundServiceIfNecessary() {
112
- if (nativeAudioPlayers.isNotEmpty() || nativeAudioRecorders.isNotEmpty()) {
113
- startForegroundService()
114
- }
115
- }
116
-
117
- fun stopForegroundServiceIfNecessary() {
118
- if (nativeAudioPlayers.isEmpty() && nativeAudioRecorders.isEmpty()) {
119
- stopForegroundService()
120
- }
121
- }
122
-
123
- private fun startForegroundService() {
124
- synchronized(serviceStateLock) {
125
- if (isServiceRunning || reactContext.get() == null) {
126
- return
127
- }
128
-
129
- val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java)
130
- intent.action = MediaNotificationManager.ForegroundAction.START_FOREGROUND.name
131
-
132
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
133
- ContextCompat.startForegroundService(reactContext.get()!!, intent)
134
- } else {
135
- reactContext.get()!!.startService(intent)
136
- }
137
- isServiceRunning = true
81
+ // Register RecordingNotificationReceiver
82
+ val recordingFilter = IntentFilter(RecordingNotificationReceiver.ACTION_NOTIFICATION_DISMISSED)
83
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
84
+ this.reactContext.get()!!.registerReceiver(recordingNotificationReceiver, recordingFilter, Context.RECEIVER_NOT_EXPORTED)
85
+ } else {
86
+ ContextCompat.registerReceiver(
87
+ this.reactContext.get()!!,
88
+ recordingNotificationReceiver,
89
+ recordingFilter,
90
+ ContextCompat.RECEIVER_NOT_EXPORTED,
91
+ )
138
92
  }
139
- }
140
93
 
141
- private fun stopForegroundService() {
142
- synchronized(serviceStateLock) {
143
- if (!isServiceRunning || reactContext.get() == null) {
144
- return
145
- }
146
-
147
- val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java)
148
- intent.action = MediaNotificationManager.ForegroundAction.STOP_FOREGROUND.name
149
- reactContext.get()!!.startService(intent)
150
- isServiceRunning = false
151
- }
152
- }
153
-
154
- fun setLockScreenInfo(info: ReadableMap?) {
155
- lockScreenManager.setLockScreenInfo(info)
156
- }
157
-
158
- fun resetLockScreenInfo() {
159
- lockScreenManager.resetLockScreenInfo()
160
- }
94
+ this.audioFocusListener =
95
+ AudioFocusListener(WeakReference(this.audioManager), this.audioAPIModule)
96
+ this.volumeChangeListener = VolumeChangeListener(WeakReference(this.audioManager), this.audioAPIModule)
161
97
 
162
- fun enableRemoteCommand(
163
- name: String,
164
- enabled: Boolean,
165
- ) {
166
- lockScreenManager.enableRemoteCommand(name, enabled)
98
+ // Initialize new notification system
99
+ this.notificationRegistry = NotificationRegistry(this.reactContext)
167
100
  }
168
101
 
169
102
  fun getDevicePreferredSampleRate(): Double {
@@ -211,6 +144,41 @@ object MediaSessionManager {
211
144
  "Denied"
212
145
  }
213
146
 
147
+ fun requestNotificationPermissions(permissionListener: PermissionListener) {
148
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
149
+ val permissionAwareActivity = reactContext.get()!!.currentActivity as PermissionAwareActivity
150
+ permissionAwareActivity.requestPermissions(
151
+ arrayOf(Manifest.permission.POST_NOTIFICATIONS),
152
+ PermissionRequestListener.NOTIFICATION_REQUEST_CODE,
153
+ permissionListener,
154
+ )
155
+ } else {
156
+ // For Android < 13, permission is granted by default
157
+ val result = Arguments.createMap()
158
+ result.putString("status", "Granted")
159
+ permissionListener.onRequestPermissionsResult(
160
+ PermissionRequestListener.NOTIFICATION_REQUEST_CODE,
161
+ arrayOf(Manifest.permission.POST_NOTIFICATIONS),
162
+ intArrayOf(PackageManager.PERMISSION_GRANTED),
163
+ )
164
+ }
165
+ }
166
+
167
+ fun checkNotificationPermissions(): String {
168
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
169
+ return if (reactContext.get()!!.checkSelfPermission(
170
+ Manifest.permission.POST_NOTIFICATIONS,
171
+ ) == PackageManager.PERMISSION_GRANTED
172
+ ) {
173
+ "Granted"
174
+ } else {
175
+ "Denied"
176
+ }
177
+ }
178
+ // For Android < 13, permission is granted by default
179
+ return "Granted"
180
+ }
181
+
214
182
  @RequiresApi(Build.VERSION_CODES.O)
215
183
  private fun createChannel() {
216
184
  val notificationManager =
@@ -267,4 +235,44 @@ object MediaSessionManager {
267
235
  AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth SCO"
268
236
  else -> "Other (${device.type})"
269
237
  }
238
+
239
+ // New notification system methods
240
+ fun registerNotification(
241
+ type: String,
242
+ key: String,
243
+ ) {
244
+ val notification =
245
+ when (type) {
246
+ "simple" -> SimpleNotification(reactContext)
247
+ "playback" -> PlaybackNotification(reactContext, audioAPIModule, 100, "audio_playback")
248
+ "recording" -> RecordingNotification(reactContext, audioAPIModule, 101, "audio_recording23")
249
+ else -> throw IllegalArgumentException("Unknown notification type: $type")
250
+ }
251
+
252
+ notificationRegistry.registerNotification(key, notification)
253
+ }
254
+
255
+ fun showNotification(
256
+ key: String,
257
+ options: ReadableMap?,
258
+ ) {
259
+ notificationRegistry.showNotification(key, options)
260
+ }
261
+
262
+ fun updateNotification(
263
+ key: String,
264
+ options: ReadableMap?,
265
+ ) {
266
+ notificationRegistry.updateNotification(key, options)
267
+ }
268
+
269
+ fun hideNotification(key: String) {
270
+ notificationRegistry.hideNotification(key)
271
+ }
272
+
273
+ fun unregisterNotification(key: String) {
274
+ notificationRegistry.unregisterNotification(key)
275
+ }
276
+
277
+ fun isNotificationActive(key: String): Boolean = notificationRegistry.isNotificationActive(key)
270
278
  }
@@ -9,6 +9,7 @@ class PermissionRequestListener(
9
9
  ) : PermissionListener {
10
10
  companion object {
11
11
  const val RECORDING_REQUEST_CODE = 1234
12
+ const val NOTIFICATION_REQUEST_CODE = 1235
12
13
  }
13
14
 
14
15
  override fun onRequestPermissionsResult(
@@ -16,7 +17,7 @@ class PermissionRequestListener(
16
17
  permissions: Array<String>,
17
18
  grantResults: IntArray,
18
19
  ): Boolean {
19
- if (requestCode == RECORDING_REQUEST_CODE) {
20
+ if (requestCode == RECORDING_REQUEST_CODE || requestCode == NOTIFICATION_REQUEST_CODE) {
20
21
  if (grantResults.isEmpty()) {
21
22
  this.promise.resolve("Undetermined")
22
23
  } else {
@@ -0,0 +1,47 @@
1
+ package com.swmansion.audioapi.system.notification
2
+
3
+ import android.app.Notification
4
+ import com.facebook.react.bridge.ReadableMap
5
+
6
+ /**
7
+ * Base interface for all notification types.
8
+ * Implementations should handle their own notification channel creation,
9
+ * notification building, and lifecycle management.
10
+ */
11
+ interface BaseNotification {
12
+ /**
13
+ * Initialize the notification with the provided options.
14
+ * This method should create the notification and prepare it for display.
15
+ *
16
+ * @param options Configuration options from JavaScript side
17
+ * @return The built Notification ready to be shown
18
+ */
19
+ fun init(options: ReadableMap?): Notification
20
+
21
+ /**
22
+ * Update the notification with new options.
23
+ * This method should rebuild the notification with updated data.
24
+ *
25
+ * @param options New configuration options from JavaScript side
26
+ * @return The updated Notification ready to be shown
27
+ */
28
+ fun update(options: ReadableMap?): Notification
29
+
30
+ /**
31
+ * Reset the notification to its initial state.
32
+ * This should clear any stored data and stop any ongoing processes.
33
+ */
34
+ fun reset()
35
+
36
+ /**
37
+ * Get the unique ID for this notification.
38
+ * Used by the NotificationManager to track and manage notifications.
39
+ */
40
+ fun getNotificationId(): Int
41
+
42
+ /**
43
+ * Get the channel ID for this notification.
44
+ * Required for Android O+ notification channels.
45
+ */
46
+ fun getChannelId(): String
47
+ }
@@ -0,0 +1,191 @@
1
+ package com.swmansion.audioapi.system.notification
2
+
3
+ import android.app.Notification
4
+ import android.util.Log
5
+ import androidx.core.app.NotificationManagerCompat
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.bridge.ReadableMap
8
+ import com.swmansion.audioapi.system.ForegroundServiceManager
9
+ import java.lang.ref.WeakReference
10
+
11
+ /**
12
+ * Central notification registry that manages multiple notification instances.
13
+ */
14
+ class NotificationRegistry(
15
+ private val reactContext: WeakReference<ReactApplicationContext>,
16
+ ) {
17
+ companion object {
18
+ private const val TAG = "NotificationRegistry"
19
+
20
+ // Store last built notifications for foreground service access
21
+ private val builtNotifications = mutableMapOf<Int, Notification>()
22
+
23
+ fun getBuiltNotification(notificationId: Int): Notification? = builtNotifications[notificationId]
24
+ }
25
+
26
+ private val notifications = mutableMapOf<String, BaseNotification>()
27
+ private val activeNotifications = mutableMapOf<String, Boolean>()
28
+
29
+ /**
30
+ * Register a new notification instance.
31
+ *
32
+ * @param key Unique identifier for this notification
33
+ * @param notification The notification instance to register
34
+ */
35
+ fun registerNotification(
36
+ key: String,
37
+ notification: BaseNotification,
38
+ ) {
39
+ notifications[key] = notification
40
+ Log.d(TAG, "Registered notification: $key")
41
+ }
42
+
43
+ /**
44
+ * Initialize and show a notification.
45
+ *
46
+ * @param key The unique identifier of the notification
47
+ * @param options Configuration options from JavaScript
48
+ */
49
+ fun showNotification(
50
+ key: String,
51
+ options: ReadableMap?,
52
+ ) {
53
+ val notification = notifications[key]
54
+ if (notification == null) {
55
+ Log.w(TAG, "Notification not found: $key")
56
+ return
57
+ }
58
+
59
+ try {
60
+ val builtNotification = notification.init(options)
61
+ displayNotification(notification.getNotificationId(), builtNotification)
62
+ activeNotifications[key] = true
63
+
64
+ // Subscribe to foreground service for persistent notifications
65
+ ForegroundServiceManager.subscribe("notification_$key")
66
+
67
+ Log.d(TAG, "Showing notification: $key")
68
+ } catch (e: Exception) {
69
+ Log.e(TAG, "Error showing notification $key: ${e.message}", e)
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Update an existing notification with new options.
75
+ *
76
+ * @param key The unique identifier of the notification
77
+ * @param options New configuration options from JavaScript
78
+ */
79
+ fun updateNotification(
80
+ key: String,
81
+ options: ReadableMap?,
82
+ ) {
83
+ if (!activeNotifications.getOrDefault(key, false)) {
84
+ Log.w(TAG, "Cannot update inactive notification: $key")
85
+ return
86
+ }
87
+
88
+ val notification = notifications[key]
89
+ if (notification == null) {
90
+ Log.w(TAG, "Notification not found: $key")
91
+ return
92
+ }
93
+
94
+ try {
95
+ val builtNotification = notification.update(options)
96
+ displayNotification(notification.getNotificationId(), builtNotification)
97
+ Log.d(TAG, "Updated notification: $key")
98
+ } catch (e: Exception) {
99
+ Log.e(TAG, "Error updating notification $key: ${e.message}", e)
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Hide and reset a notification.
105
+ *
106
+ * @param key The unique identifier of the notification
107
+ */
108
+ fun hideNotification(key: String) {
109
+ val notification = notifications[key]
110
+ if (notification == null) {
111
+ Log.w(TAG, "Notification not found: $key")
112
+ return
113
+ }
114
+
115
+ try {
116
+ cancelNotification(notification.getNotificationId())
117
+ notification.reset()
118
+ activeNotifications[key] = false
119
+
120
+ // Unsubscribe from foreground service
121
+ ForegroundServiceManager.unsubscribe("notification_$key")
122
+
123
+ Log.d(TAG, "Hiding notification: $key")
124
+ } catch (e: Exception) {
125
+ Log.e(TAG, "Error hiding notification $key: ${e.message}", e)
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Unregister and cleanup a notification.
131
+ *
132
+ * @param key The unique identifier of the notification
133
+ */
134
+ fun unregisterNotification(key: String) {
135
+ hideNotification(key)
136
+ notifications.remove(key)
137
+ activeNotifications.remove(key)
138
+ Log.d(TAG, "Unregistered notification: $key")
139
+ }
140
+
141
+ /**
142
+ * Check if a notification is currently active.
143
+ */
144
+ fun isNotificationActive(key: String): Boolean = activeNotifications.getOrDefault(key, false)
145
+
146
+ /**
147
+ * Get all registered notification keys.
148
+ */
149
+ fun getRegisteredKeys(): Set<String> = notifications.keys.toSet()
150
+
151
+ /**
152
+ * Cleanup all notifications.
153
+ */
154
+ fun cleanup() {
155
+ notifications.keys.toList().forEach { key ->
156
+ hideNotification(key)
157
+ }
158
+ notifications.clear()
159
+ activeNotifications.clear()
160
+ builtNotifications.clear()
161
+
162
+ // Cleanup foreground service manager
163
+ ForegroundServiceManager.cleanup()
164
+
165
+ Log.d(TAG, "Cleaned up all notifications")
166
+ }
167
+
168
+ private fun displayNotification(
169
+ id: Int,
170
+ notification: Notification,
171
+ ) {
172
+ val context = reactContext.get() ?: throw IllegalStateException("React context is null")
173
+ Log.d(TAG, "Displaying notification with ID: $id")
174
+ try {
175
+ // Store notification for foreground service access
176
+ builtNotifications[id] = notification
177
+
178
+ NotificationManagerCompat.from(context).notify(id, notification)
179
+ Log.d(TAG, "Notification posted successfully with ID: $id")
180
+ } catch (e: Exception) {
181
+ Log.e(TAG, "Error posting notification: ${e.message}", e)
182
+ }
183
+ }
184
+
185
+ private fun cancelNotification(id: Int) {
186
+ val context = reactContext.get() ?: return
187
+ NotificationManagerCompat.from(context).cancel(id)
188
+ // Clean up stored notification
189
+ builtNotifications.remove(id)
190
+ }
191
+ }