@siteed/expo-audio-studio 2.12.3 → 2.13.0
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/build.gradle +11 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +266 -42
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +55 -1
- package/app.plugin.js +3 -1
- package/build/cjs/AudioDeviceManager.js +225 -40
- package/build/cjs/AudioDeviceManager.js.map +1 -1
- package/build/cjs/hooks/useAudioDevices.js +30 -5
- package/build/cjs/hooks/useAudioDevices.js.map +1 -1
- package/build/cjs/useAudioRecorder.js +52 -8
- package/build/cjs/useAudioRecorder.js.map +1 -1
- package/build/esm/AudioDeviceManager.js +225 -40
- package/build/esm/AudioDeviceManager.js.map +1 -1
- package/build/esm/hooks/useAudioDevices.js +31 -6
- package/build/esm/hooks/useAudioDevices.js.map +1 -1
- package/build/esm/useAudioRecorder.js +53 -9
- package/build/esm/useAudioRecorder.js.map +1 -1
- package/build/types/AudioDeviceManager.d.ts +78 -2
- package/build/types/AudioDeviceManager.d.ts.map +1 -1
- package/build/types/hooks/useAudioDevices.d.ts +1 -0
- package/build/types/hooks/useAudioDevices.d.ts.map +1 -1
- package/build/types/useAudioRecorder.d.ts.map +1 -1
- package/ios/AudioDeviceManager.swift +21 -9
- package/ios/ExpoAudioStreamModule.swift +33 -1
- package/package.json +8 -6
- package/plugin/build/index.cjs +194 -0
- package/plugin/build/index.d.cts +1 -0
- package/plugin/build/index.js +7 -6
- package/plugin/src/index.ts +8 -8
- package/src/AudioDeviceManager.ts +286 -59
- package/src/hooks/useAudioDevices.ts +39 -6
- package/src/useAudioRecorder.tsx +102 -9
|
@@ -18,7 +18,7 @@ private let audioDeviceTypeWiredHeadphones = "wired_headphones"
|
|
|
18
18
|
private let audioDeviceTypeSpeaker = "speaker"
|
|
19
19
|
private let audioDeviceTypeUnknown = "unknown"
|
|
20
20
|
|
|
21
|
-
public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
21
|
+
public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate, AudioDeviceManagerDelegate {
|
|
22
22
|
private var streamManager = AudioStreamManager()
|
|
23
23
|
private let notificationCenter = UNUserNotificationCenter.current()
|
|
24
24
|
private let notificationIdentifier = "audio_recording_notification"
|
|
@@ -41,11 +41,30 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
41
41
|
OnCreate {
|
|
42
42
|
Logger.debug("ExpoAudioStreamModule", "Module created, setting delegate and starting device monitoring.")
|
|
43
43
|
streamManager.delegate = self
|
|
44
|
+
// Set up device manager delegate to emit device change events
|
|
45
|
+
deviceManager.delegate = self
|
|
46
|
+
|
|
47
|
+
// Listen for device connection notifications (minimal addition)
|
|
48
|
+
NotificationCenter.default.addObserver(
|
|
49
|
+
forName: NSNotification.Name("DeviceConnected"),
|
|
50
|
+
object: nil,
|
|
51
|
+
queue: .main
|
|
52
|
+
) { [weak self] notification in
|
|
53
|
+
if let deviceId = notification.userInfo?["deviceId"] as? String {
|
|
54
|
+
Logger.debug("ExpoAudioStreamModule", "Device connected: \(deviceId)")
|
|
55
|
+
self?.sendEvent(deviceChangedEvent, [
|
|
56
|
+
"type": "deviceConnected",
|
|
57
|
+
"deviceId": deviceId
|
|
58
|
+
])
|
|
59
|
+
}
|
|
60
|
+
}
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
OnDestroy {
|
|
47
64
|
Logger.debug("ExpoAudioStreamModule", "Module destroyed, stopping device monitoring.")
|
|
48
65
|
_ = streamManager.stopRecording()
|
|
66
|
+
// Clear device manager delegate
|
|
67
|
+
deviceManager.delegate = nil
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
/// Extracts audio analysis data from an audio file.
|
|
@@ -980,4 +999,17 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
980
999
|
Logger.error("ExpoAudioStreamModule", "Delegate: didFailWithError: \(error)")
|
|
981
1000
|
sendEvent(errorEvent, [ "message": error ])
|
|
982
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
// MARK: - AudioDeviceManagerDelegate
|
|
1004
|
+
|
|
1005
|
+
/// Handles device disconnection events from the AudioDeviceManager
|
|
1006
|
+
func audioDeviceManager(_ manager: AudioDeviceManager, didDetectDisconnectionOfDevice deviceId: String) {
|
|
1007
|
+
Logger.debug("ExpoAudioStreamModule", "Device disconnected: \(deviceId)")
|
|
1008
|
+
|
|
1009
|
+
// Emit device change event to match Android implementation
|
|
1010
|
+
sendEvent(deviceChangedEvent, [
|
|
1011
|
+
"type": "deviceDisconnected",
|
|
1012
|
+
"deviceId": deviceId
|
|
1013
|
+
])
|
|
1014
|
+
}
|
|
983
1015
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteed/expo-audio-studio",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
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
|
"type": "commonjs",
|
|
@@ -74,11 +74,12 @@
|
|
|
74
74
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
75
75
|
"build:esm": "tsc -p tsconfig.esm.json",
|
|
76
76
|
"build:types": "tsc -p tsconfig.types.json",
|
|
77
|
-
"build:plugin": "tsc --
|
|
77
|
+
"build:plugin": "tsc --project plugin/tsconfig.json && cp plugin/build/index.js plugin/build/index.cjs",
|
|
78
78
|
"build:plugin:dev": "expo-module build plugin",
|
|
79
79
|
"build:dev": "expo-module build",
|
|
80
80
|
"clean": "expo-module clean && rimraf build plugin/build",
|
|
81
81
|
"lint": "expo-module lint",
|
|
82
|
+
"lint:fix": "expo-module lint --fix",
|
|
82
83
|
"test": "expo-module test",
|
|
83
84
|
"test:android": "yarn test:android:unit && yarn test:android:instrumented",
|
|
84
85
|
"test:android:unit": "cd ../../apps/playground/android && ./gradlew :siteed-expo-audio-studio:test",
|
|
@@ -100,6 +101,9 @@
|
|
|
100
101
|
"agent:test:integration": "yarn test:android:instrumented",
|
|
101
102
|
"agent:compilation:check": "yarn typecheck && yarn build"
|
|
102
103
|
},
|
|
104
|
+
"dependencies": {
|
|
105
|
+
"expo-modules-core": "~2.3.13"
|
|
106
|
+
},
|
|
103
107
|
"devDependencies": {
|
|
104
108
|
"@expo/config-plugins": "~10.0.0",
|
|
105
109
|
"@expo/npm-proofread": "^1.0.1",
|
|
@@ -122,7 +126,8 @@
|
|
|
122
126
|
"expo-module-scripts": "^4.1.7",
|
|
123
127
|
"jest": "^29.7.0",
|
|
124
128
|
"prettier": "^3.2.5",
|
|
125
|
-
"react
|
|
129
|
+
"react": "19.0.0",
|
|
130
|
+
"react-native": "0.79.3",
|
|
126
131
|
"rimraf": "^6.0.1",
|
|
127
132
|
"size-limit": "^11.1.4",
|
|
128
133
|
"ts-node": "^10.9.2",
|
|
@@ -138,8 +143,5 @@
|
|
|
138
143
|
"publishConfig": {
|
|
139
144
|
"access": "public",
|
|
140
145
|
"registry": "https://registry.npmjs.org"
|
|
141
|
-
},
|
|
142
|
-
"dependencies": {
|
|
143
|
-
"expo-modules-core": "~2.3.13"
|
|
144
146
|
}
|
|
145
147
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
4
|
+
const MICROPHONE_USAGE = 'Allow $(PRODUCT_NAME) to access your microphone';
|
|
5
|
+
const NOTIFICATION_USAGE = 'Show recording notifications and controls';
|
|
6
|
+
const LOG_PREFIX = '[@siteed/expo-audio-studio]';
|
|
7
|
+
function debugLog(message, ...args) {
|
|
8
|
+
if (process.env.EXPO_DEBUG) {
|
|
9
|
+
console.log(`${LOG_PREFIX} ${message}`, ...args);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const withRecordingPermission = (config, props) => {
|
|
13
|
+
const options = {
|
|
14
|
+
enablePhoneStateHandling: true, // Default to true for backward compatibility
|
|
15
|
+
enableNotifications: true,
|
|
16
|
+
enableBackgroundAudio: true,
|
|
17
|
+
enableDeviceDetection: true, // Default to true for backward compatibility
|
|
18
|
+
iosBackgroundModes: {
|
|
19
|
+
useVoIP: false,
|
|
20
|
+
useAudio: false,
|
|
21
|
+
useProcessing: false,
|
|
22
|
+
useLocation: false,
|
|
23
|
+
useExternalAccessory: false,
|
|
24
|
+
},
|
|
25
|
+
iosConfig: {
|
|
26
|
+
microphoneUsageDescription: MICROPHONE_USAGE,
|
|
27
|
+
notificationUsageDescription: NOTIFICATION_USAGE,
|
|
28
|
+
},
|
|
29
|
+
...(props || {}),
|
|
30
|
+
};
|
|
31
|
+
const { enablePhoneStateHandling, enableNotifications, enableBackgroundAudio, enableDeviceDetection, } = options;
|
|
32
|
+
debugLog('📱 Configuring Recording Permissions Plugin...', options);
|
|
33
|
+
// iOS Configuration
|
|
34
|
+
config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
35
|
+
// Always set the microphone usage description from options first
|
|
36
|
+
config.modResults['NSMicrophoneUsageDescription'] =
|
|
37
|
+
options.iosConfig?.microphoneUsageDescription ||
|
|
38
|
+
config.modResults['NSMicrophoneUsageDescription'] ||
|
|
39
|
+
MICROPHONE_USAGE;
|
|
40
|
+
if (enableNotifications) {
|
|
41
|
+
config.modResults['NSUserNotificationsUsageDescription'] =
|
|
42
|
+
options.iosConfig?.notificationUsageDescription ||
|
|
43
|
+
config.modResults['NSUserNotificationsUsageDescription'] ||
|
|
44
|
+
NOTIFICATION_USAGE;
|
|
45
|
+
config.modResults['NSUserNotificationAlertStyle'] = 'alert';
|
|
46
|
+
}
|
|
47
|
+
const existingBackgroundModes = config.modResults.UIBackgroundModes || [];
|
|
48
|
+
// If background audio is enabled with useAudio, add the audio background mode
|
|
49
|
+
if (options.iosBackgroundModes?.useAudio === true &&
|
|
50
|
+
enableBackgroundAudio === true &&
|
|
51
|
+
!existingBackgroundModes.includes('audio')) {
|
|
52
|
+
// Add 'audio' background mode - REQUIRED for background recording
|
|
53
|
+
existingBackgroundModes.push('audio');
|
|
54
|
+
debugLog('✅ Added audio background mode for iOS background recording');
|
|
55
|
+
// Also ensure processing mode is recommended
|
|
56
|
+
if (options.iosBackgroundModes?.useProcessing !== true) {
|
|
57
|
+
console.warn(`${LOG_PREFIX} Warning: Background audio recording works best with both 'audio' and 'processing' background modes. Consider enabling 'useProcessing' in iosBackgroundModes.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (options.iosBackgroundModes?.useVoIP === true &&
|
|
61
|
+
enablePhoneStateHandling === true) {
|
|
62
|
+
if (!existingBackgroundModes.includes('voip')) {
|
|
63
|
+
existingBackgroundModes.push('voip');
|
|
64
|
+
}
|
|
65
|
+
const existingCapabilities = (config.modResults
|
|
66
|
+
.UIRequiredDeviceCapabilities || []);
|
|
67
|
+
if (!existingCapabilities.includes('telephony')) {
|
|
68
|
+
existingCapabilities.push('telephony');
|
|
69
|
+
}
|
|
70
|
+
config.modResults.UIRequiredDeviceCapabilities =
|
|
71
|
+
existingCapabilities;
|
|
72
|
+
}
|
|
73
|
+
// Add additional background modes only if explicitly set to true
|
|
74
|
+
if (options.iosBackgroundModes?.useProcessing === true) {
|
|
75
|
+
if (!existingBackgroundModes.includes('processing')) {
|
|
76
|
+
existingBackgroundModes.push('processing');
|
|
77
|
+
}
|
|
78
|
+
// Add processing info if enabled
|
|
79
|
+
// Note: We keep the 'audiostream' namespace for native modules to maintain compatibility
|
|
80
|
+
config.modResults.BGTaskSchedulerPermittedIdentifiers = [
|
|
81
|
+
'com.siteed.audiostream.processing',
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
if (options.iosBackgroundModes?.useLocation === true) {
|
|
85
|
+
if (!existingBackgroundModes.includes('location')) {
|
|
86
|
+
existingBackgroundModes.push('location');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (options.iosBackgroundModes?.useExternalAccessory === true) {
|
|
90
|
+
if (!existingBackgroundModes.includes('external-accessory')) {
|
|
91
|
+
existingBackgroundModes.push('external-accessory');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Configure background processing info if enabled
|
|
95
|
+
if (options.iosConfig?.backgroundProcessingTitle) {
|
|
96
|
+
config.modResults.BGProcessingTaskTitle =
|
|
97
|
+
options.iosConfig.backgroundProcessingTitle;
|
|
98
|
+
}
|
|
99
|
+
// Configure audio session behavior
|
|
100
|
+
if (options.iosConfig?.allowBackgroundAudioControls) {
|
|
101
|
+
config.modResults.UIBackgroundModes = [
|
|
102
|
+
...existingBackgroundModes,
|
|
103
|
+
'remote-notification',
|
|
104
|
+
];
|
|
105
|
+
config.modResults.MPNowPlayingInfoPropertyPlaybackRate = true;
|
|
106
|
+
}
|
|
107
|
+
config.modResults.UIBackgroundModes = existingBackgroundModes;
|
|
108
|
+
return config;
|
|
109
|
+
});
|
|
110
|
+
// Android Configuration
|
|
111
|
+
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
112
|
+
const basePermissions = [
|
|
113
|
+
'android.permission.RECORD_AUDIO',
|
|
114
|
+
'android.permission.WAKE_LOCK',
|
|
115
|
+
];
|
|
116
|
+
const optionalPermissions = [
|
|
117
|
+
enableNotifications && 'android.permission.POST_NOTIFICATIONS',
|
|
118
|
+
enablePhoneStateHandling && 'android.permission.READ_PHONE_STATE', // Only add if enabled
|
|
119
|
+
enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE',
|
|
120
|
+
enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE_MICROPHONE',
|
|
121
|
+
// Device detection permissions (only if enabled)
|
|
122
|
+
enableDeviceDetection && 'android.permission.BLUETOOTH',
|
|
123
|
+
enableDeviceDetection && 'android.permission.BLUETOOTH_CONNECT',
|
|
124
|
+
enableDeviceDetection && 'android.permission.USB_PERMISSION',
|
|
125
|
+
].filter(Boolean);
|
|
126
|
+
const permissionsToAdd = [...basePermissions, ...optionalPermissions];
|
|
127
|
+
debugLog('📋 Existing Android permissions:', config.modResults.manifest['uses-permission']?.map((p) => p.$?.['android:name']) || []);
|
|
128
|
+
debugLog('➕ Adding Android permissions:', permissionsToAdd);
|
|
129
|
+
// Add each permission only if it doesn't exist
|
|
130
|
+
permissionsToAdd.forEach((permission) => {
|
|
131
|
+
config_plugins_1.AndroidConfig.Permissions.addPermission(config.modResults, permission);
|
|
132
|
+
});
|
|
133
|
+
// Get the main application node
|
|
134
|
+
const mainApplication = config.modResults.manifest.application?.[0];
|
|
135
|
+
if (mainApplication) {
|
|
136
|
+
debugLog('📱 Configuring Android application components...');
|
|
137
|
+
// Add RecordingActionReceiver
|
|
138
|
+
if (!mainApplication.receiver) {
|
|
139
|
+
mainApplication.receiver = [];
|
|
140
|
+
}
|
|
141
|
+
const receiverConfig = {
|
|
142
|
+
$: {
|
|
143
|
+
'android:name': '.RecordingActionReceiver',
|
|
144
|
+
'android:exported': 'false',
|
|
145
|
+
},
|
|
146
|
+
'intent-filter': [
|
|
147
|
+
{
|
|
148
|
+
action: [
|
|
149
|
+
{ $: { 'android:name': 'PAUSE_RECORDING' } },
|
|
150
|
+
{ $: { 'android:name': 'RESUME_RECORDING' } },
|
|
151
|
+
{ $: { 'android:name': 'STOP_RECORDING' } },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
const receiverIndex = mainApplication.receiver.findIndex((receiver) => receiver.$?.['android:name'] === '.RecordingActionReceiver');
|
|
157
|
+
if (receiverIndex >= 0) {
|
|
158
|
+
mainApplication.receiver[receiverIndex] = receiverConfig;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
mainApplication.receiver.push(receiverConfig);
|
|
162
|
+
}
|
|
163
|
+
debugLog('✅ RecordingActionReceiver configured');
|
|
164
|
+
// Add AudioRecordingService
|
|
165
|
+
if (!mainApplication.service) {
|
|
166
|
+
mainApplication.service = [];
|
|
167
|
+
}
|
|
168
|
+
const serviceConfig = {
|
|
169
|
+
$: {
|
|
170
|
+
'android:name': '.AudioRecordingService',
|
|
171
|
+
'android:enabled': 'true',
|
|
172
|
+
'android:exported': 'false',
|
|
173
|
+
'android:foregroundServiceType': 'microphone',
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
const serviceIndex = mainApplication.service.findIndex((service) => service.$?.['android:name'] === '.AudioRecordingService');
|
|
177
|
+
if (serviceIndex >= 0) {
|
|
178
|
+
mainApplication.service[serviceIndex] = serviceConfig;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
mainApplication.service.push(serviceConfig);
|
|
182
|
+
}
|
|
183
|
+
debugLog('✅ AudioRecordingService configured');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
console.error(`${LOG_PREFIX} ❌ Main application node not found in Android Manifest`);
|
|
187
|
+
}
|
|
188
|
+
return config;
|
|
189
|
+
});
|
|
190
|
+
debugLog('✨ Recording Permissions Plugin configuration completed');
|
|
191
|
+
return config;
|
|
192
|
+
};
|
|
193
|
+
// Export as default
|
|
194
|
+
exports.default = withRecordingPermission;
|
package/plugin/build/index.d.cts
CHANGED
package/plugin/build/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const withRecordingPermission = (config, props) => {
|
|
|
14
14
|
enablePhoneStateHandling: true, // Default to true for backward compatibility
|
|
15
15
|
enableNotifications: true,
|
|
16
16
|
enableBackgroundAudio: true,
|
|
17
|
+
enableDeviceDetection: true, // Default to true for backward compatibility
|
|
17
18
|
iosBackgroundModes: {
|
|
18
19
|
useVoIP: false,
|
|
19
20
|
useAudio: false,
|
|
@@ -27,7 +28,7 @@ const withRecordingPermission = (config, props) => {
|
|
|
27
28
|
},
|
|
28
29
|
...(props || {}),
|
|
29
30
|
};
|
|
30
|
-
const { enablePhoneStateHandling, enableNotifications, enableBackgroundAudio, } = options;
|
|
31
|
+
const { enablePhoneStateHandling, enableNotifications, enableBackgroundAudio, enableDeviceDetection, } = options;
|
|
31
32
|
debugLog('📱 Configuring Recording Permissions Plugin...', options);
|
|
32
33
|
// iOS Configuration
|
|
33
34
|
config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
@@ -117,17 +118,17 @@ const withRecordingPermission = (config, props) => {
|
|
|
117
118
|
enablePhoneStateHandling && 'android.permission.READ_PHONE_STATE', // Only add if enabled
|
|
118
119
|
enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE',
|
|
119
120
|
enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE_MICROPHONE',
|
|
121
|
+
// Device detection permissions (only if enabled)
|
|
122
|
+
enableDeviceDetection && 'android.permission.BLUETOOTH',
|
|
123
|
+
enableDeviceDetection && 'android.permission.BLUETOOTH_CONNECT',
|
|
124
|
+
enableDeviceDetection && 'android.permission.USB_PERMISSION',
|
|
120
125
|
].filter(Boolean);
|
|
121
126
|
const permissionsToAdd = [...basePermissions, ...optionalPermissions];
|
|
122
127
|
debugLog('📋 Existing Android permissions:', config.modResults.manifest['uses-permission']?.map((p) => p.$?.['android:name']) || []);
|
|
123
128
|
debugLog('➕ Adding Android permissions:', permissionsToAdd);
|
|
124
|
-
const { addPermission } = config_plugins_1.AndroidConfig.Permissions;
|
|
125
129
|
// Add each permission only if it doesn't exist
|
|
126
130
|
permissionsToAdd.forEach((permission) => {
|
|
127
|
-
|
|
128
|
-
if (!existingPermission) {
|
|
129
|
-
addPermission(config.modResults, permission);
|
|
130
|
-
}
|
|
131
|
+
config_plugins_1.AndroidConfig.Permissions.addPermission(config.modResults, permission);
|
|
131
132
|
});
|
|
132
133
|
// Get the main application node
|
|
133
134
|
const mainApplication = config.modResults.manifest.application?.[0];
|
package/plugin/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ interface AudioStreamPluginOptions {
|
|
|
20
20
|
enablePhoneStateHandling?: boolean // Controls READ_PHONE_STATE permission
|
|
21
21
|
enableNotifications?: boolean
|
|
22
22
|
enableBackgroundAudio?: boolean
|
|
23
|
+
enableDeviceDetection?: boolean // Controls Bluetooth and USB permissions for device change detection
|
|
23
24
|
iosBackgroundModes?: {
|
|
24
25
|
useVoIP?: boolean
|
|
25
26
|
useAudio?: boolean
|
|
@@ -43,6 +44,7 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
|
|
|
43
44
|
enablePhoneStateHandling: true, // Default to true for backward compatibility
|
|
44
45
|
enableNotifications: true,
|
|
45
46
|
enableBackgroundAudio: true,
|
|
47
|
+
enableDeviceDetection: true, // Default to true for backward compatibility
|
|
46
48
|
iosBackgroundModes: {
|
|
47
49
|
useVoIP: false,
|
|
48
50
|
useAudio: false,
|
|
@@ -61,6 +63,7 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
|
|
|
61
63
|
enablePhoneStateHandling,
|
|
62
64
|
enableNotifications,
|
|
63
65
|
enableBackgroundAudio,
|
|
66
|
+
enableDeviceDetection,
|
|
64
67
|
} = options
|
|
65
68
|
|
|
66
69
|
debugLog('📱 Configuring Recording Permissions Plugin...', options)
|
|
@@ -175,6 +178,10 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
|
|
|
175
178
|
enablePhoneStateHandling && 'android.permission.READ_PHONE_STATE', // Only add if enabled
|
|
176
179
|
enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE',
|
|
177
180
|
enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE_MICROPHONE',
|
|
181
|
+
// Device detection permissions (only if enabled)
|
|
182
|
+
enableDeviceDetection && 'android.permission.BLUETOOTH',
|
|
183
|
+
enableDeviceDetection && 'android.permission.BLUETOOTH_CONNECT',
|
|
184
|
+
enableDeviceDetection && 'android.permission.USB_PERMISSION',
|
|
178
185
|
].filter(Boolean) as string[]
|
|
179
186
|
|
|
180
187
|
const permissionsToAdd = [...basePermissions, ...optionalPermissions]
|
|
@@ -188,16 +195,9 @@ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
|
|
|
188
195
|
|
|
189
196
|
debugLog('➕ Adding Android permissions:', permissionsToAdd)
|
|
190
197
|
|
|
191
|
-
const { addPermission } = AndroidConfig.Permissions
|
|
192
|
-
|
|
193
198
|
// Add each permission only if it doesn't exist
|
|
194
199
|
permissionsToAdd.forEach((permission) => {
|
|
195
|
-
|
|
196
|
-
'uses-permission'
|
|
197
|
-
]?.find((p) => p.$?.['android:name'] === permission)
|
|
198
|
-
if (!existingPermission) {
|
|
199
|
-
addPermission(config.modResults, permission)
|
|
200
|
-
}
|
|
200
|
+
AndroidConfig.Permissions.addPermission(config.modResults, permission)
|
|
201
201
|
})
|
|
202
202
|
|
|
203
203
|
// Get the main application node
|