@siteed/expo-audio-studio 2.4.0 → 2.4.1

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
 
11
+ ## [2.4.1] - 2025-04-08
12
+ ### Changed
13
+ - feat(audio-stream): enhance background audio handling and permission checks (#200) ([60befbe](https://github.com/deeeed/expo-audio-stream/commit/60befbedc9d3cbcc1fc684254d812381e5905e43))
11
14
  ## [2.4.0] - 2025-04-03
12
15
  ### Changed
13
16
  - fix(audio-stream): resolve iOS sample rate mismatch and enhance recording stability (#198) ([05bfc61](https://github.com/deeeed/expo-audio-stream/commit/05bfc6159e7f71fb1d70c3de24fa487cdfb73a62))
@@ -189,7 +192,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
189
192
  - Feature: Audio features extraction during recording.
190
193
  - Feature: Consistent WAV PCM recording format across all platforms.
191
194
 
192
- [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.4.0...HEAD
195
+ [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.4.1...HEAD
196
+ [2.4.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.4.0...@siteed/expo-audio-studio@2.4.1
193
197
  [2.4.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.3.1...@siteed/expo-audio-studio@2.4.0
194
198
  [2.3.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.3.0...@siteed/expo-audio-studio@2.3.1
195
199
  [2.3.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.2.0...@siteed/expo-audio-studio@2.3.0
@@ -38,7 +38,8 @@ class AudioRecorderManager(
38
38
  private val permissionUtils: PermissionUtils,
39
39
  private val audioDataEncoder: AudioDataEncoder,
40
40
  private val eventSender: EventSender,
41
- private val enablePhoneStateHandling: Boolean = true
41
+ private val enablePhoneStateHandling: Boolean = true,
42
+ private val enableBackgroundAudio: Boolean = true
42
43
  ) {
43
44
  private var audioRecord: AudioRecord? = null
44
45
  private var bufferSizeInBytes = 0
@@ -232,12 +233,13 @@ class AudioRecorderManager(
232
233
  permissionUtils: PermissionUtils,
233
234
  audioDataEncoder: AudioDataEncoder,
234
235
  eventSender: EventSender,
235
- enablePhoneStateHandling: Boolean = true
236
+ enablePhoneStateHandling: Boolean = true,
237
+ enableBackgroundAudio: Boolean = true
236
238
  ): AudioRecorderManager {
237
239
  return instance ?: synchronized(this) {
238
240
  instance ?: AudioRecorderManager(
239
241
  context, filesDir, permissionUtils, audioDataEncoder, eventSender,
240
- enablePhoneStateHandling
242
+ enablePhoneStateHandling, enableBackgroundAudio
241
243
  ).also { instance = it }
242
244
  }
243
245
  }
@@ -441,7 +443,7 @@ class AudioRecorderManager(
441
443
  }
442
444
 
443
445
  private fun isAudioFormatSupported(sampleRate: Int, channels: Int, format: Int): Boolean {
444
- if (!permissionUtils.checkRecordingPermission()) {
446
+ if (!permissionUtils.checkRecordingPermission(enableBackgroundAudio)) {
445
447
  throw SecurityException("Recording permission has not been granted")
446
448
  }
447
449
 
@@ -477,7 +479,7 @@ class AudioRecorderManager(
477
479
  }
478
480
 
479
481
  private fun checkPermissions(options: Map<String, Any?>, promise: Promise): Boolean {
480
- if (!permissionUtils.checkRecordingPermission()) {
482
+ if (!permissionUtils.checkRecordingPermission(enableBackgroundAudio)) {
481
483
  promise.reject(
482
484
  "PERMISSION_DENIED",
483
485
  "Recording permission has not been granted",
@@ -595,7 +597,7 @@ class AudioRecorderManager(
595
597
 
596
598
 
597
599
  private fun initializeAudioRecord(promise: Promise): Boolean {
598
- if (!permissionUtils.checkRecordingPermission()) {
600
+ if (!permissionUtils.checkRecordingPermission(enableBackgroundAudio)) {
599
601
  promise.reject(
600
602
  "PERMISSION_DENIED",
601
603
  "Recording permission has not been granted",
@@ -19,6 +19,7 @@ class ExpoAudioStreamModule : Module(), EventSender {
19
19
  private lateinit var audioProcessor: AudioProcessor
20
20
  private var enablePhoneStateHandling: Boolean = false // Default to false until we check manifest
21
21
  private var enableNotificationHandling: Boolean = false // Default to false until we check manifest
22
+ private var enableBackgroundAudio: Boolean = false // Default to false until we check manifest
22
23
 
23
24
  private val audioFileHandler by lazy {
24
25
  AudioFileHandler(appContext.reactContext?.filesDir ?: throw IllegalStateException("React context not available"))
@@ -50,12 +51,17 @@ class ExpoAudioStreamModule : Module(), EventSender {
50
51
  // Check if POST_NOTIFICATIONS is in the requested permissions
51
52
  enableNotificationHandling = packageInfo.requestedPermissions?.contains(Manifest.permission.POST_NOTIFICATIONS) ?: false
52
53
 
54
+ // Check if background audio is enabled by looking for FOREGROUND_SERVICE permission
55
+ enableBackgroundAudio = packageInfo.requestedPermissions?.contains(Manifest.permission.FOREGROUND_SERVICE) ?: false
56
+
53
57
  Log.d(Constants.TAG, "Phone state handling ${if (enablePhoneStateHandling) "enabled" else "disabled"} based on manifest permissions")
54
58
  Log.d(Constants.TAG, "Notification handling ${if (enableNotificationHandling) "enabled" else "disabled"} based on manifest permissions")
59
+ Log.d(Constants.TAG, "Background audio handling ${if (enableBackgroundAudio) "enabled" else "disabled"} based on manifest permissions")
55
60
  } catch (e: Exception) {
56
61
  Log.e(Constants.TAG, "Failed to check manifest permissions: ${e.message}", e)
57
62
  enablePhoneStateHandling = false
58
63
  enableNotificationHandling = false
64
+ enableBackgroundAudio = false
59
65
  }
60
66
 
61
67
  Events(
@@ -68,6 +74,11 @@ class ExpoAudioStreamModule : Module(), EventSender {
68
74
  // Initialize AudioRecorderManager
69
75
  initializeManager()
70
76
 
77
+ // Add a convenience function to check for foreground service permission separately
78
+ fun isForegroundServiceMicRequired(): Boolean {
79
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && enableBackgroundAudio
80
+ }
81
+
71
82
  AsyncFunction("startRecording") { options: Map<String, Any?>, promise: Promise ->
72
83
  // If notifications are requested but permission not in manifest, modify options
73
84
  if (options["showNotification"] as? Boolean == true && !enableNotificationHandling) {
@@ -215,8 +226,8 @@ class ExpoAudioStreamModule : Module(), EventSender {
215
226
  permissions.add(Manifest.permission.READ_PHONE_STATE)
216
227
  }
217
228
 
218
- // Add foreground service permission for Android 14+
219
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
229
+ // Add foreground service permission for Android 14+ only if background audio is enabled
230
+ if (isForegroundServiceMicRequired()) {
220
231
  Log.d(Constants.TAG, "Adding FOREGROUND_SERVICE_MICROPHONE permission request")
221
232
  permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
222
233
  }
@@ -243,7 +254,8 @@ class ExpoAudioStreamModule : Module(), EventSender {
243
254
  permissions.add(Manifest.permission.READ_PHONE_STATE)
244
255
  }
245
256
 
246
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
257
+ // Only check foreground service permission when background audio is enabled
258
+ if (isForegroundServiceMicRequired()) {
247
259
  permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
248
260
  }
249
261
 
@@ -768,7 +780,8 @@ class ExpoAudioStreamModule : Module(), EventSender {
768
780
  permissionUtils,
769
781
  audioDataEncoder,
770
782
  this,
771
- enablePhoneStateHandling
783
+ enablePhoneStateHandling,
784
+ enableBackgroundAudio
772
785
  )
773
786
  audioProcessor = AudioProcessor(filesDir)
774
787
  }
@@ -8,7 +8,7 @@ import android.Manifest
8
8
  import android.util.Log
9
9
 
10
10
  class PermissionUtils(private val context: Context) {
11
- fun checkRecordingPermission(): Boolean {
11
+ fun checkRecordingPermission(enableBackgroundAudio: Boolean = true): Boolean {
12
12
  val hasRecordPermission = ContextCompat.checkSelfPermission(
13
13
  context,
14
14
  Manifest.permission.RECORD_AUDIO
@@ -16,16 +16,20 @@ class PermissionUtils(private val context: Context) {
16
16
 
17
17
  Log.d(Constants.TAG, "RECORD_AUDIO permission: $hasRecordPermission")
18
18
 
19
- // Check for foreground service permission on Android 14+
20
- val hasForegroundService = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
19
+ // Check for foreground service permission on Android 14+ only if background audio is enabled
20
+ val hasForegroundService = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && enableBackgroundAudio) {
21
21
  val result = ContextCompat.checkSelfPermission(
22
22
  context,
23
23
  Manifest.permission.FOREGROUND_SERVICE_MICROPHONE
24
24
  ) == PackageManager.PERMISSION_GRANTED
25
- Log.d(Constants.TAG, "FOREGROUND_SERVICE_MICROPHONE permission: $result (Android 14+)")
25
+ Log.d(Constants.TAG, "FOREGROUND_SERVICE_MICROPHONE permission: $result (Android 14+, background audio enabled)")
26
26
  result
27
27
  } else {
28
- Log.d(Constants.TAG, "FOREGROUND_SERVICE_MICROPHONE not required (Android < 14)")
28
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
29
+ Log.d(Constants.TAG, "FOREGROUND_SERVICE_MICROPHONE not required (background audio disabled)")
30
+ } else {
31
+ Log.d(Constants.TAG, "FOREGROUND_SERVICE_MICROPHONE not required (Android < 14)")
32
+ }
29
33
  true
30
34
  }
31
35
 
@@ -34,6 +38,11 @@ class PermissionUtils(private val context: Context) {
34
38
  return result
35
39
  }
36
40
 
41
+ // Overload the original method for backward compatibility
42
+ fun checkRecordingPermission(): Boolean {
43
+ return checkRecordingPermission(true)
44
+ }
45
+
37
46
  fun checkNotificationPermission(): Boolean {
38
47
  val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
39
48
  val hasPermission = ContextCompat.checkSelfPermission(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-studio",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "Comprehensive audio processing library for React Native and Expo with recording, analysis, visualization, and streaming capabilities across iOS, Android, and web",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",