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
@@ -0,0 +1,303 @@
1
+ package com.swmansion.audioapi.system.notification
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.app.PendingIntent
7
+ import android.content.Context
8
+ import android.content.Intent
9
+ import android.content.IntentFilter
10
+ import android.graphics.Color
11
+ import android.os.Build
12
+ import android.util.Log
13
+ import androidx.core.app.NotificationCompat
14
+ import com.facebook.react.bridge.ReactApplicationContext
15
+ import com.facebook.react.bridge.ReadableMap
16
+ import com.swmansion.audioapi.AudioAPIModule
17
+ import java.lang.ref.WeakReference
18
+
19
+ /**
20
+ * RecordingNotification
21
+ *
22
+ * Simple notification for audio recording:
23
+ * - Shows recording status with red background when recording
24
+ * - Simple start/stop button with microphone icon
25
+ * - Is persistent and cannot be swiped away when recording
26
+ * - Notifies its dismissal via RecordingNotificationReceiver
27
+ */
28
+ class RecordingNotification(
29
+ private val reactContext: WeakReference<ReactApplicationContext>,
30
+ private val audioAPIModule: WeakReference<AudioAPIModule>,
31
+ private val notificationId: Int,
32
+ private val channelId: String,
33
+ ) : BaseNotification {
34
+ companion object {
35
+ private const val TAG = "RecordingNotification"
36
+ const val ACTION_START = "com.swmansion.audioapi.RECORDING_START"
37
+ const val ACTION_STOP = "com.swmansion.audioapi.RECORDING_STOP"
38
+ }
39
+
40
+ private var notificationBuilder: NotificationCompat.Builder? = null
41
+ private var isRecording: Boolean = false
42
+ private var title: String = "Audio Recording"
43
+ private var description: String = "Ready to record"
44
+ private var receiver: RecordingNotificationReceiver? = null
45
+ private var startEnabled: Boolean = true
46
+ private var stopEnabled: Boolean = true
47
+
48
+ override fun init(params: ReadableMap?): Notification {
49
+ val context = reactContext.get() ?: throw IllegalStateException("React context is null")
50
+
51
+ // Register broadcast receiver
52
+ registerReceiver()
53
+
54
+ // Create notification channel first
55
+ createNotificationChannel()
56
+
57
+ // Create notification builder
58
+ notificationBuilder =
59
+ NotificationCompat
60
+ .Builder(context, channelId)
61
+ .setSmallIcon(android.R.drawable.ic_btn_speak_now)
62
+ .setContentTitle(title)
63
+ .setContentText(description)
64
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
65
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
66
+ .setOngoing(false)
67
+ .setAutoCancel(false)
68
+
69
+ // Set content intent to open app
70
+ val packageName = context.packageName
71
+ val openAppIntent = context.packageManager.getLaunchIntentForPackage(packageName)
72
+ if (openAppIntent != null) {
73
+ val pendingIntent =
74
+ PendingIntent.getActivity(
75
+ context,
76
+ 0,
77
+ openAppIntent,
78
+ PendingIntent.FLAG_IMMUTABLE,
79
+ )
80
+ notificationBuilder?.setContentIntent(pendingIntent)
81
+ }
82
+
83
+ // Set delete intent to handle dismissal
84
+ val deleteIntent = Intent(RecordingNotificationReceiver.ACTION_NOTIFICATION_DISMISSED)
85
+ deleteIntent.setPackage(context.packageName)
86
+ val deletePendingIntent =
87
+ PendingIntent.getBroadcast(
88
+ context,
89
+ notificationId,
90
+ deleteIntent,
91
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
92
+ )
93
+ notificationBuilder?.setDeleteIntent(deletePendingIntent)
94
+
95
+ // Apply initial params if provided
96
+ if (params != null) {
97
+ update(params)
98
+ }
99
+
100
+ return buildNotification()
101
+ }
102
+
103
+ override fun reset() {
104
+ // Unregister receiver
105
+ unregisterReceiver()
106
+
107
+ // Reset state
108
+ title = "Audio Recording"
109
+ description = "Ready to record"
110
+ isRecording = false
111
+ notificationBuilder = null
112
+ }
113
+
114
+ override fun getNotificationId(): Int = notificationId
115
+
116
+ override fun getChannelId(): String = channelId
117
+
118
+ override fun update(options: ReadableMap?): Notification {
119
+ if (options == null) {
120
+ return buildNotification()
121
+ }
122
+
123
+ // Handle control enable/disable
124
+ if (options.hasKey("control") && options.hasKey("enabled")) {
125
+ val control = options.getString("control")
126
+ val enabled = options.getBoolean("enabled")
127
+ when (control) {
128
+ "start" -> startEnabled = enabled
129
+ "stop" -> stopEnabled = enabled
130
+ }
131
+ updateActions()
132
+ return buildNotification()
133
+ }
134
+
135
+ // Update metadata
136
+ if (options.hasKey("title")) {
137
+ title = options.getString("title") ?: "Audio Recording"
138
+ }
139
+
140
+ if (options.hasKey("description")) {
141
+ description = options.getString("description") ?: "Ready to record"
142
+ }
143
+
144
+ // Update recording state
145
+ if (options.hasKey("state")) {
146
+ when (options.getString("state")) {
147
+ "recording" -> isRecording = true
148
+ "stopped" -> isRecording = false
149
+ }
150
+ }
151
+
152
+ // Update notification content
153
+ val statusText =
154
+ description.ifEmpty {
155
+ if (isRecording) "Recording..." else "Ready to record"
156
+ }
157
+ notificationBuilder
158
+ ?.setContentTitle(title)
159
+ ?.setContentText(statusText)
160
+ ?.setOngoing(isRecording)
161
+
162
+ // Set red color when recording
163
+ if (isRecording) {
164
+ notificationBuilder
165
+ ?.setColor(Color.RED)
166
+ ?.setColorized(true)
167
+ } else {
168
+ notificationBuilder
169
+ ?.setColorized(false)
170
+ }
171
+
172
+ // Update action button
173
+ updateActions()
174
+
175
+ return buildNotification()
176
+ }
177
+
178
+ private fun buildNotification(): Notification =
179
+ notificationBuilder?.build()
180
+ ?: throw IllegalStateException("Notification not initialized. Call init() first.")
181
+
182
+ private fun updateActions() {
183
+ val context = reactContext.get() ?: return
184
+
185
+ // Clear existing actions
186
+ notificationBuilder?.clearActions()
187
+
188
+ // Add appropriate action based on recording state and enabled controls
189
+ // Note: Android shows text labels in collapsed view, icons only in expanded/Auto/Wear
190
+ if (isRecording && stopEnabled) {
191
+ // Show STOP button when recording
192
+ val stopIntent = Intent(ACTION_STOP)
193
+ stopIntent.setPackage(context.packageName)
194
+ val stopPendingIntent =
195
+ PendingIntent.getBroadcast(
196
+ context,
197
+ 1001,
198
+ stopIntent,
199
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
200
+ )
201
+ val stopAction =
202
+ NotificationCompat.Action
203
+ .Builder(
204
+ android.R.drawable.ic_delete,
205
+ "Stop",
206
+ stopPendingIntent,
207
+ ).build()
208
+ notificationBuilder?.addAction(stopAction)
209
+ } else if (!isRecording && startEnabled) {
210
+ // Show START button when not recording
211
+ val startIntent = Intent(ACTION_START)
212
+ startIntent.setPackage(context.packageName)
213
+ val startPendingIntent =
214
+ PendingIntent.getBroadcast(
215
+ context,
216
+ 1000,
217
+ startIntent,
218
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
219
+ )
220
+ val startAction =
221
+ NotificationCompat.Action
222
+ .Builder(
223
+ android.R.drawable.ic_btn_speak_now,
224
+ "Record",
225
+ startPendingIntent,
226
+ ).build()
227
+ notificationBuilder?.addAction(startAction)
228
+ }
229
+
230
+ // Use BigTextStyle to ensure actions are visible
231
+ val statusText =
232
+ description.ifEmpty {
233
+ if (isRecording) "Recording in progress..." else "Ready to record"
234
+ }
235
+ notificationBuilder?.setStyle(
236
+ NotificationCompat
237
+ .BigTextStyle()
238
+ .bigText(statusText),
239
+ )
240
+ }
241
+
242
+ private fun createNotificationChannel() {
243
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
244
+ val context = reactContext.get() ?: return
245
+
246
+ val channel =
247
+ NotificationChannel(
248
+ channelId,
249
+ "Audio Recording",
250
+ NotificationManager.IMPORTANCE_HIGH,
251
+ ).apply {
252
+ description = "Recording controls and status"
253
+ setShowBadge(true)
254
+ lockscreenVisibility = Notification.VISIBILITY_PUBLIC
255
+ enableLights(true)
256
+ lightColor = Color.RED
257
+ enableVibration(false)
258
+ }
259
+
260
+ val notificationManager =
261
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
262
+ notificationManager.createNotificationChannel(channel)
263
+
264
+ Log.d(TAG, "Notification channel created: $channelId")
265
+ }
266
+ }
267
+
268
+ private fun registerReceiver() {
269
+ val context = reactContext.get() ?: return
270
+
271
+ if (receiver == null) {
272
+ receiver = RecordingNotificationReceiver()
273
+ RecordingNotificationReceiver.setAudioAPIModule(audioAPIModule.get())
274
+
275
+ val filter = IntentFilter()
276
+ filter.addAction(ACTION_START)
277
+ filter.addAction(ACTION_STOP)
278
+ filter.addAction(RecordingNotificationReceiver.ACTION_NOTIFICATION_DISMISSED)
279
+
280
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
281
+ context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
282
+ } else {
283
+ context.registerReceiver(receiver, filter)
284
+ }
285
+
286
+ Log.d(TAG, "RecordingNotificationReceiver registered")
287
+ }
288
+ }
289
+
290
+ private fun unregisterReceiver() {
291
+ val context = reactContext.get() ?: return
292
+
293
+ receiver?.let {
294
+ try {
295
+ context.unregisterReceiver(it)
296
+ receiver = null
297
+ Log.d(TAG, "RecordingNotificationReceiver unregistered")
298
+ } catch (e: Exception) {
299
+ Log.e(TAG, "Error unregistering receiver: ${e.message}", e)
300
+ }
301
+ }
302
+ }
303
+ }
@@ -0,0 +1,43 @@
1
+ package com.swmansion.audioapi.system.notification
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.util.Log
7
+ import com.swmansion.audioapi.AudioAPIModule
8
+
9
+ /**
10
+ * Broadcast receiver for handling recording notification actions and dismissal.
11
+ */
12
+ class RecordingNotificationReceiver : BroadcastReceiver() {
13
+ companion object {
14
+ const val ACTION_NOTIFICATION_DISMISSED = "com.swmansion.audioapi.RECORDING_NOTIFICATION_DISMISSED"
15
+ private const val TAG = "RecordingNotificationReceiver"
16
+
17
+ private var audioAPIModule: AudioAPIModule? = null
18
+
19
+ fun setAudioAPIModule(module: AudioAPIModule?) {
20
+ audioAPIModule = module
21
+ }
22
+ }
23
+
24
+ override fun onReceive(
25
+ context: Context?,
26
+ intent: Intent?,
27
+ ) {
28
+ when (intent?.action) {
29
+ ACTION_NOTIFICATION_DISMISSED -> {
30
+ Log.d(TAG, "Recording notification dismissed by user")
31
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody("recordingNotificationDismissed", mapOf())
32
+ }
33
+ RecordingNotification.ACTION_START -> {
34
+ Log.d(TAG, "Start recording action received")
35
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody("recordingNotificationStart", mapOf())
36
+ }
37
+ RecordingNotification.ACTION_STOP -> {
38
+ Log.d(TAG, "Stop recording action received")
39
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody("recordingNotificationStop", mapOf())
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,119 @@
1
+ package com.swmansion.audioapi.system.notification
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.app.PendingIntent
7
+ import android.content.Context
8
+ import android.os.Build
9
+ import androidx.core.app.NotificationCompat
10
+ import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.bridge.ReadableMap
12
+ import com.swmansion.audioapi.R
13
+ import java.lang.ref.WeakReference
14
+
15
+ /**
16
+ * This serves as a reference implementation and starting point for more complex notifications.
17
+ */
18
+ class SimpleNotification(
19
+ private val reactContext: WeakReference<ReactApplicationContext>,
20
+ private val notificationId: Int = DEFAULT_NOTIFICATION_ID,
21
+ ) : BaseNotification {
22
+ companion object {
23
+ const val DEFAULT_NOTIFICATION_ID = 200
24
+ const val CHANNEL_ID = "react-native-audio-api-simple"
25
+ const val CHANNEL_NAME = "Simple Notifications"
26
+ }
27
+
28
+ private var title: String = "Audio Playing"
29
+ private var text: String = ""
30
+
31
+ init {
32
+ createNotificationChannel()
33
+ }
34
+
35
+ override fun init(options: ReadableMap?): Notification {
36
+ // Parse options from JavaScript
37
+ if (options != null) {
38
+ if (options.hasKey("title")) {
39
+ title = options.getString("title") ?: "Audio Playing"
40
+ }
41
+ if (options.hasKey("text")) {
42
+ text = options.getString("text") ?: ""
43
+ }
44
+ }
45
+
46
+ return buildNotification()
47
+ }
48
+
49
+ override fun update(options: ReadableMap?): Notification {
50
+ // Update works the same as init for simple notifications
51
+ return init(options)
52
+ }
53
+
54
+ override fun reset() {
55
+ // Reset to default values
56
+ title = "Audio Playing"
57
+ text = ""
58
+ }
59
+
60
+ override fun getNotificationId(): Int = notificationId
61
+
62
+ override fun getChannelId(): String = CHANNEL_ID
63
+
64
+ private fun buildNotification(): Notification {
65
+ val context = reactContext.get() ?: throw IllegalStateException("React context is null")
66
+
67
+ // Use a system icon that's guaranteed to exist
68
+ val icon = android.R.drawable.ic_dialog_info
69
+
70
+ val builder =
71
+ NotificationCompat
72
+ .Builder(context, CHANNEL_ID)
73
+ .setContentTitle(title)
74
+ .setContentText(text)
75
+ .setSmallIcon(icon)
76
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
77
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
78
+ .setOngoing(false)
79
+ .setAutoCancel(false)
80
+
81
+ // Add content intent to open the app when notification is tapped
82
+ val packageName = context.packageName
83
+ val openAppIntent = context.packageManager?.getLaunchIntentForPackage(packageName)
84
+ if (openAppIntent != null) {
85
+ val pendingIntent =
86
+ PendingIntent.getActivity(
87
+ context,
88
+ 0,
89
+ openAppIntent,
90
+ PendingIntent.FLAG_IMMUTABLE,
91
+ )
92
+ builder.setContentIntent(pendingIntent)
93
+ }
94
+
95
+ return builder.build()
96
+ }
97
+
98
+ private fun createNotificationChannel() {
99
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
100
+ val context = reactContext.get() ?: return
101
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
102
+
103
+ val channel =
104
+ NotificationChannel(
105
+ CHANNEL_ID,
106
+ CHANNEL_NAME,
107
+ NotificationManager.IMPORTANCE_HIGH,
108
+ ).apply {
109
+ description = "Simple notification channel for audio playback"
110
+ setShowBadge(true)
111
+ lockscreenVisibility = Notification.VISIBILITY_PUBLIC
112
+ enableLights(true)
113
+ enableVibration(false)
114
+ }
115
+
116
+ notificationManager.createNotificationChannel(channel)
117
+ }
118
+ }
119
+ }
@@ -11,7 +11,7 @@
11
11
  @class AudioEngine;
12
12
  @class NotificationManager;
13
13
  @class AudioSessionManager;
14
- @class LockScreenManager;
14
+ @class NotificationRegistry;
15
15
 
16
16
  @interface AudioAPIModule : RCTEventEmitter
17
17
  #ifdef RCT_NEW_ARCH_ENABLED
@@ -23,7 +23,7 @@
23
23
  @property (nonatomic, strong) AudioEngine *audioEngine;
24
24
  @property (nonatomic, strong) NotificationManager *notificationManager;
25
25
  @property (nonatomic, strong) AudioSessionManager *audioSessionManager;
26
- @property (nonatomic, strong) LockScreenManager *lockScreenManager;
26
+ @property (nonatomic, strong) NotificationRegistry *notificationRegistry;
27
27
 
28
28
  - (void)invokeHandlerWithEventName:(NSString *)eventName eventBody:(NSDictionary *)eventBody;
29
29
 
@@ -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
 
@@ -61,8 +61,8 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
61
61
  {
62
62
  self.audioSessionManager = [[AudioSessionManager alloc] init];
63
63
  self.audioEngine = [[AudioEngine alloc] initWithAudioSessionManager:self.audioSessionManager];
64
- self.lockScreenManager = [[LockScreenManager alloc] initWithAudioAPIModule:self];
65
64
  self.notificationManager = [[NotificationManager alloc] initWithAudioAPIModule:self];
65
+ self.notificationRegistry = [[NotificationRegistry alloc] initWithAudioAPIModule:self];
66
66
 
67
67
  auto jsiRuntime = reinterpret_cast<facebook::jsi::Runtime *>(self.bridge.runtime);
68
68
 
@@ -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
@@ -255,10 +255,10 @@ static AudioEngine *_sharedInstance = nil;
255
255
  NSLog(@"================ 🎧 AVAudioEngine STATE ================");
256
256
 
257
257
  // AVAudioEngine state
258
- NSLog(@"➡️ engine.isRunning: %@", self.audioEngine.isRunning ? @"YES" : @"NO");
258
+ NSLog(@"➡️ engine.isRunning: %@", self.audioEngine.isRunning ? @"true" : @"false");
259
259
  NSLog(
260
260
  @"➡️ engine.isInManualRenderingMode: %@",
261
- self.audioEngine.isInManualRenderingMode ? @"YES" : @"NO");
261
+ self.audioEngine.isInManualRenderingMode ? @"true" : @"false");
262
262
 
263
263
  // Session state
264
264
  NSLog(@"🎚️ Session category: %@", session.category);
@@ -182,7 +182,7 @@
182
182
  self.sessionCategory,
183
183
  self.sessionMode,
184
184
  (unsigned long)self.sessionOptions,
185
- self.allowHapticsAndSystemSoundsDuringRecording ? @"YES" : @"NO");
185
+ self.allowHapticsAndSystemSoundsDuringRecording ? @"true" : @"false");
186
186
 
187
187
  NSError *error = nil;
188
188
 
@@ -261,7 +261,7 @@ static NSString *NotificationManagerContext = @"NotificationManagerContext";
261
261
  target:self
262
262
  selector:@selector(checkSecondaryAudioHint)
263
263
  userInfo:nil
264
- repeats:YES];
264
+ repeats:true];
265
265
 
266
266
  [[NSRunLoop mainRunLoop] addTimer:self.hintPollingTimer forMode:NSRunLoopCommonModes];
267
267
  }