@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 +5 -1
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +8 -6
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +17 -4
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +14 -5
- package/package.json +1 -1
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.
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|