@siteed/expo-audio-stream 1.9.2 → 1.11.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 +43 -1
- package/README.md +4 -0
- package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +89 -33
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +213 -1
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +5 -1
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +8 -0
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +3 -1
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -1
- package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +8 -0
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/utils/BlobFix.d.ts.map +1 -1
- package/build/utils/convertPCMToFloat32.d.ts +2 -2
- package/build/utils/convertPCMToFloat32.d.ts.map +1 -1
- package/build/utils/encodingToBitDepth.d.ts.map +1 -1
- package/build/utils/getWavFileInfo.d.ts.map +1 -1
- package/ios/AudioStreamManager.swift +55 -14
- package/ios/AudioStreamManagerDelegate.swift +1 -0
- package/ios/ExpoAudioStreamModule.swift +16 -4
- package/ios/RecordingSettings.swift +4 -1
- package/package.json +2 -1
- package/plugin/build/index.d.ts +6 -1
- package/plugin/build/index.js +43 -38
- package/plugin/src/index.ts +75 -50
- package/src/ExpoAudioStream.types.ts +20 -0
package/plugin/src/index.ts
CHANGED
|
@@ -16,89 +16,112 @@ function debugLog(message: string, ...args: unknown[]): void {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
interface AudioStreamPluginOptions {
|
|
20
|
+
enablePhoneStateHandling?: boolean
|
|
21
|
+
enableNotifications?: boolean
|
|
22
|
+
enableBackgroundAudio?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
|
|
26
|
+
config: ExpoConfig,
|
|
27
|
+
props: AudioStreamPluginOptions | void
|
|
28
|
+
) => {
|
|
29
|
+
// Default options if pluginOptions is undefined (void)
|
|
30
|
+
const options: AudioStreamPluginOptions = {
|
|
31
|
+
enablePhoneStateHandling: true,
|
|
32
|
+
enableNotifications: true,
|
|
33
|
+
enableBackgroundAudio: true,
|
|
34
|
+
...(props || {}),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
enablePhoneStateHandling,
|
|
39
|
+
enableNotifications,
|
|
40
|
+
enableBackgroundAudio,
|
|
41
|
+
} = options
|
|
42
|
+
|
|
43
|
+
debugLog('📱 Configuring Recording Permissions Plugin...', options)
|
|
21
44
|
|
|
22
45
|
// iOS Configuration
|
|
23
46
|
config = withInfoPlist(config as any, (config) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Existing microphone permission
|
|
47
|
+
// Base microphone permission (always required)
|
|
27
48
|
config.modResults['NSMicrophoneUsageDescription'] =
|
|
28
49
|
config.modResults['NSMicrophoneUsageDescription'] ||
|
|
29
50
|
MICROPHONE_USAGE
|
|
30
51
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
config.modResults['NSUserNotificationAlertStyle'] = 'alert'
|
|
52
|
+
if (enableNotifications) {
|
|
53
|
+
config.modResults['NSUserNotificationsUsageDescription'] =
|
|
54
|
+
NOTIFICATION_USAGE
|
|
55
|
+
config.modResults['NSUserNotificationAlertStyle'] = 'alert'
|
|
56
|
+
}
|
|
37
57
|
|
|
38
|
-
// Background modes
|
|
39
58
|
const existingBackgroundModes =
|
|
40
59
|
config.modResults.UIBackgroundModes || []
|
|
41
|
-
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
enableBackgroundAudio &&
|
|
63
|
+
!existingBackgroundModes.includes('audio')
|
|
64
|
+
) {
|
|
42
65
|
existingBackgroundModes.push('audio')
|
|
43
66
|
}
|
|
44
|
-
if (!existingBackgroundModes.includes('remote-notification')) {
|
|
45
|
-
existingBackgroundModes.push('remote-notification')
|
|
46
|
-
}
|
|
47
|
-
config.modResults.UIBackgroundModes = existingBackgroundModes
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
if (enablePhoneStateHandling) {
|
|
69
|
+
if (!existingBackgroundModes.includes('voip')) {
|
|
70
|
+
existingBackgroundModes.push('voip')
|
|
71
|
+
}
|
|
72
|
+
const existingCapabilities = (config.modResults
|
|
73
|
+
.UIRequiredDeviceCapabilities || []) as string[]
|
|
74
|
+
if (!existingCapabilities.includes('telephony')) {
|
|
75
|
+
existingCapabilities.push('telephony')
|
|
76
|
+
}
|
|
77
|
+
config.modResults.UIRequiredDeviceCapabilities =
|
|
78
|
+
existingCapabilities
|
|
79
|
+
}
|
|
50
80
|
|
|
81
|
+
config.modResults.UIBackgroundModes = existingBackgroundModes
|
|
51
82
|
return config
|
|
52
83
|
})
|
|
53
84
|
|
|
54
85
|
// Android Configuration
|
|
55
86
|
config = withAndroidManifest(config as any, (config) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const androidManifest = config.modResults
|
|
59
|
-
if (!androidManifest.manifest) {
|
|
60
|
-
console.error(`${LOG_PREFIX} ❌ Android Manifest is null - plugin cannot continue`)
|
|
61
|
-
return config
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Add xmlns:android attribute to manifest
|
|
65
|
-
androidManifest.manifest.$ = {
|
|
66
|
-
...androidManifest.manifest.$,
|
|
67
|
-
'xmlns:android': 'http://schemas.android.com/apk/res/android',
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Ensure permissions array exists
|
|
71
|
-
if (!androidManifest.manifest['uses-permission']) {
|
|
72
|
-
androidManifest.manifest['uses-permission'] = []
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const { addPermission } = AndroidConfig.Permissions
|
|
76
|
-
|
|
77
|
-
debugLog('📋 Existing Android permissions:',
|
|
78
|
-
androidManifest.manifest['uses-permission']?.map(p => p.$?.['android:name']) || [])
|
|
79
|
-
|
|
80
|
-
const permissionsToAdd = [
|
|
87
|
+
const basePermissions = [
|
|
81
88
|
'android.permission.RECORD_AUDIO',
|
|
82
|
-
'android.permission.FOREGROUND_SERVICE',
|
|
83
|
-
'android.permission.FOREGROUND_SERVICE_MICROPHONE',
|
|
84
89
|
'android.permission.WAKE_LOCK',
|
|
85
|
-
'android.permission.POST_NOTIFICATIONS',
|
|
86
90
|
]
|
|
87
91
|
|
|
92
|
+
const optionalPermissions = [
|
|
93
|
+
enableNotifications && 'android.permission.POST_NOTIFICATIONS',
|
|
94
|
+
enablePhoneStateHandling && 'android.permission.READ_PHONE_STATE',
|
|
95
|
+
enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE',
|
|
96
|
+
enableBackgroundAudio &&
|
|
97
|
+
'android.permission.FOREGROUND_SERVICE_MICROPHONE',
|
|
98
|
+
].filter(Boolean) as string[]
|
|
99
|
+
|
|
100
|
+
const permissionsToAdd = [...basePermissions, ...optionalPermissions]
|
|
101
|
+
|
|
102
|
+
debugLog(
|
|
103
|
+
'📋 Existing Android permissions:',
|
|
104
|
+
config.modResults.manifest['uses-permission']?.map(
|
|
105
|
+
(p) => p.$?.['android:name']
|
|
106
|
+
) || []
|
|
107
|
+
)
|
|
108
|
+
|
|
88
109
|
debugLog('➕ Adding Android permissions:', permissionsToAdd)
|
|
89
110
|
|
|
111
|
+
const { addPermission } = AndroidConfig.Permissions
|
|
112
|
+
|
|
90
113
|
// Add each permission only if it doesn't exist
|
|
91
114
|
permissionsToAdd.forEach((permission) => {
|
|
92
|
-
const existingPermission =
|
|
115
|
+
const existingPermission = config.modResults.manifest[
|
|
93
116
|
'uses-permission'
|
|
94
117
|
]?.find((p) => p.$?.['android:name'] === permission)
|
|
95
118
|
if (!existingPermission) {
|
|
96
|
-
addPermission(
|
|
119
|
+
addPermission(config.modResults, permission)
|
|
97
120
|
}
|
|
98
121
|
})
|
|
99
122
|
|
|
100
123
|
// Get the main application node
|
|
101
|
-
const mainApplication =
|
|
124
|
+
const mainApplication = config.modResults.manifest.application?.[0]
|
|
102
125
|
if (mainApplication) {
|
|
103
126
|
debugLog('📱 Configuring Android application components...')
|
|
104
127
|
|
|
@@ -163,7 +186,9 @@ const withRecordingPermission: ConfigPlugin = (config: ExpoConfig) => {
|
|
|
163
186
|
|
|
164
187
|
debugLog('✅ AudioRecordingService configured')
|
|
165
188
|
} else {
|
|
166
|
-
console.error(
|
|
189
|
+
console.error(
|
|
190
|
+
`${LOG_PREFIX} ❌ Main application node not found in Android Manifest`
|
|
191
|
+
)
|
|
167
192
|
}
|
|
168
193
|
|
|
169
194
|
return config
|
|
@@ -118,6 +118,19 @@ export interface IOSConfig {
|
|
|
118
118
|
audioSession?: AudioSessionConfig
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// Add new type for interruption reasons
|
|
122
|
+
export type RecordingInterruptionReason =
|
|
123
|
+
| 'audioFocusLoss'
|
|
124
|
+
| 'audioFocusGain'
|
|
125
|
+
| 'phoneCall'
|
|
126
|
+
| 'phoneCallEnded'
|
|
127
|
+
|
|
128
|
+
// Add new interface for interruption events
|
|
129
|
+
export interface RecordingInterruptionEvent {
|
|
130
|
+
reason: RecordingInterruptionReason
|
|
131
|
+
isPaused: boolean
|
|
132
|
+
}
|
|
133
|
+
|
|
121
134
|
export interface RecordingConfig {
|
|
122
135
|
// Sample rate for recording (16000, 44100, or 48000 Hz)
|
|
123
136
|
sampleRate?: SampleRate
|
|
@@ -169,6 +182,12 @@ export interface RecordingConfig {
|
|
|
169
182
|
format: 'aac' | 'opus' | 'mp3'
|
|
170
183
|
bitrate?: number
|
|
171
184
|
}
|
|
185
|
+
|
|
186
|
+
// Whether to automatically resume recording after an interruption (default is false)
|
|
187
|
+
autoResumeAfterInterruption?: boolean
|
|
188
|
+
|
|
189
|
+
// Optional callback to handle recording interruptions
|
|
190
|
+
onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void
|
|
172
191
|
}
|
|
173
192
|
|
|
174
193
|
export interface NotificationConfig {
|
|
@@ -249,4 +268,5 @@ export interface UseAudioRecorderState {
|
|
|
249
268
|
size: number // Size in bytes of the recorded audio
|
|
250
269
|
compression?: CompressionInfo
|
|
251
270
|
analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag
|
|
271
|
+
onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void
|
|
252
272
|
}
|