@siteed/expo-audio-studio 2.1.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.
Files changed (188) hide show
  1. package/CHANGELOG.md +210 -0
  2. package/LICENSE +21 -0
  3. package/README.md +269 -0
  4. package/android/build.gradle +105 -0
  5. package/android/src/main/AndroidManifest.xml +27 -0
  6. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +166 -0
  7. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +9 -0
  8. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +131 -0
  9. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +103 -0
  10. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +435 -0
  11. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +2235 -0
  12. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +1437 -0
  13. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +166 -0
  14. package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +1099 -0
  15. package/android/src/main/java/net/siteed/audiostream/Constants.kt +21 -0
  16. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +7 -0
  17. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +739 -0
  18. package/android/src/main/java/net/siteed/audiostream/FFT.kt +99 -0
  19. package/android/src/main/java/net/siteed/audiostream/Features.kt +98 -0
  20. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +70 -0
  21. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +59 -0
  22. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +59 -0
  23. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +205 -0
  24. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +19 -0
  25. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +159 -0
  26. package/android/src/main/res/drawable/ic_default_action_icon.xml +16 -0
  27. package/android/src/main/res/drawable/ic_microphone.xml +13 -0
  28. package/android/src/main/res/drawable/ic_pause.xml +10 -0
  29. package/android/src/main/res/drawable/ic_play.xml +10 -0
  30. package/android/src/main/res/drawable/ic_stop.xml +10 -0
  31. package/android/src/main/res/layout/notification_recording.xml +37 -0
  32. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
  33. package/app.plugin.js +1 -0
  34. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +179 -0
  35. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
  36. package/build/AudioAnalysis/AudioAnalysis.types.js +3 -0
  37. package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  38. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +68 -0
  39. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
  40. package/build/AudioAnalysis/extractAudioAnalysis.js +203 -0
  41. package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  42. package/build/AudioAnalysis/extractAudioData.d.ts +3 -0
  43. package/build/AudioAnalysis/extractAudioData.d.ts.map +1 -0
  44. package/build/AudioAnalysis/extractAudioData.js +5 -0
  45. package/build/AudioAnalysis/extractAudioData.js.map +1 -0
  46. package/build/AudioAnalysis/extractMelSpectrogram.d.ts +14 -0
  47. package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +1 -0
  48. package/build/AudioAnalysis/extractMelSpectrogram.js +85 -0
  49. package/build/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
  50. package/build/AudioAnalysis/extractPreview.d.ts +11 -0
  51. package/build/AudioAnalysis/extractPreview.d.ts.map +1 -0
  52. package/build/AudioAnalysis/extractPreview.js +25 -0
  53. package/build/AudioAnalysis/extractPreview.js.map +1 -0
  54. package/build/AudioAnalysis/extractWaveform.d.ts +8 -0
  55. package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -0
  56. package/build/AudioAnalysis/extractWaveform.js +11 -0
  57. package/build/AudioAnalysis/extractWaveform.js.map +1 -0
  58. package/build/AudioRecorder.provider.d.ts +11 -0
  59. package/build/AudioRecorder.provider.d.ts.map +1 -0
  60. package/build/AudioRecorder.provider.js +37 -0
  61. package/build/AudioRecorder.provider.js.map +1 -0
  62. package/build/ExpoAudioStream.native.d.ts +3 -0
  63. package/build/ExpoAudioStream.native.d.ts.map +1 -0
  64. package/build/ExpoAudioStream.native.js +6 -0
  65. package/build/ExpoAudioStream.native.js.map +1 -0
  66. package/build/ExpoAudioStream.types.d.ts +532 -0
  67. package/build/ExpoAudioStream.types.d.ts.map +1 -0
  68. package/build/ExpoAudioStream.types.js +2 -0
  69. package/build/ExpoAudioStream.types.js.map +1 -0
  70. package/build/ExpoAudioStream.web.d.ts +59 -0
  71. package/build/ExpoAudioStream.web.d.ts.map +1 -0
  72. package/build/ExpoAudioStream.web.js +285 -0
  73. package/build/ExpoAudioStream.web.js.map +1 -0
  74. package/build/ExpoAudioStreamModule.d.ts +3 -0
  75. package/build/ExpoAudioStreamModule.d.ts.map +1 -0
  76. package/build/ExpoAudioStreamModule.js +693 -0
  77. package/build/ExpoAudioStreamModule.js.map +1 -0
  78. package/build/WebRecorder.web.d.ts +119 -0
  79. package/build/WebRecorder.web.d.ts.map +1 -0
  80. package/build/WebRecorder.web.js +436 -0
  81. package/build/WebRecorder.web.js.map +1 -0
  82. package/build/constants.d.ts +11 -0
  83. package/build/constants.d.ts.map +1 -0
  84. package/build/constants.js +14 -0
  85. package/build/constants.js.map +1 -0
  86. package/build/events.d.ts +26 -0
  87. package/build/events.d.ts.map +1 -0
  88. package/build/events.js +21 -0
  89. package/build/events.js.map +1 -0
  90. package/build/index.d.ts +15 -0
  91. package/build/index.d.ts.map +1 -0
  92. package/build/index.js +14 -0
  93. package/build/index.js.map +1 -0
  94. package/build/trimAudio.d.ts +25 -0
  95. package/build/trimAudio.d.ts.map +1 -0
  96. package/build/trimAudio.js +67 -0
  97. package/build/trimAudio.js.map +1 -0
  98. package/build/useAudioRecorder.d.ts +21 -0
  99. package/build/useAudioRecorder.d.ts.map +1 -0
  100. package/build/useAudioRecorder.js +427 -0
  101. package/build/useAudioRecorder.js.map +1 -0
  102. package/build/utils/BlobFix.d.ts +9 -0
  103. package/build/utils/BlobFix.d.ts.map +1 -0
  104. package/build/utils/BlobFix.js +498 -0
  105. package/build/utils/BlobFix.js.map +1 -0
  106. package/build/utils/audioProcessing.d.ts +24 -0
  107. package/build/utils/audioProcessing.d.ts.map +1 -0
  108. package/build/utils/audioProcessing.js +133 -0
  109. package/build/utils/audioProcessing.js.map +1 -0
  110. package/build/utils/concatenateBuffers.d.ts +8 -0
  111. package/build/utils/concatenateBuffers.d.ts.map +1 -0
  112. package/build/utils/concatenateBuffers.js +21 -0
  113. package/build/utils/concatenateBuffers.js.map +1 -0
  114. package/build/utils/convertPCMToFloat32.d.ts +13 -0
  115. package/build/utils/convertPCMToFloat32.d.ts.map +1 -0
  116. package/build/utils/convertPCMToFloat32.js +120 -0
  117. package/build/utils/convertPCMToFloat32.js.map +1 -0
  118. package/build/utils/encodingToBitDepth.d.ts +5 -0
  119. package/build/utils/encodingToBitDepth.d.ts.map +1 -0
  120. package/build/utils/encodingToBitDepth.js +13 -0
  121. package/build/utils/encodingToBitDepth.js.map +1 -0
  122. package/build/utils/getWavFileInfo.d.ts +26 -0
  123. package/build/utils/getWavFileInfo.d.ts.map +1 -0
  124. package/build/utils/getWavFileInfo.js +92 -0
  125. package/build/utils/getWavFileInfo.js.map +1 -0
  126. package/build/utils/writeWavHeader.d.ts +49 -0
  127. package/build/utils/writeWavHeader.d.ts.map +1 -0
  128. package/build/utils/writeWavHeader.js +91 -0
  129. package/build/utils/writeWavHeader.js.map +1 -0
  130. package/build/workers/InlineFeaturesExtractor.web.d.ts +2 -0
  131. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
  132. package/build/workers/InlineFeaturesExtractor.web.js +828 -0
  133. package/build/workers/InlineFeaturesExtractor.web.js.map +1 -0
  134. package/build/workers/inlineAudioWebWorker.web.d.ts +2 -0
  135. package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
  136. package/build/workers/inlineAudioWebWorker.web.js +157 -0
  137. package/build/workers/inlineAudioWebWorker.web.js.map +1 -0
  138. package/expo-module.config.json +9 -0
  139. package/ios/AudioAnalysisData.swift +74 -0
  140. package/ios/AudioNotificationManager.swift +135 -0
  141. package/ios/AudioProcessingHelpers.swift +743 -0
  142. package/ios/AudioProcessor.swift +1313 -0
  143. package/ios/AudioStreamError.swift +7 -0
  144. package/ios/AudioStreamManager.swift +1708 -0
  145. package/ios/AudioStreamManagerDelegate.swift +16 -0
  146. package/ios/DataPoint.swift +54 -0
  147. package/ios/DecodingConfig.swift +47 -0
  148. package/ios/ExpoAudioStream.podspec +27 -0
  149. package/ios/ExpoAudioStreamModule.swift +805 -0
  150. package/ios/FFT.swift +62 -0
  151. package/ios/Features.swift +95 -0
  152. package/ios/Logger.swift +7 -0
  153. package/ios/NotificationExtension.swift +15 -0
  154. package/ios/RecordingResult.swift +22 -0
  155. package/ios/RecordingSettings.swift +265 -0
  156. package/ios/WaveformExtractor.swift +105 -0
  157. package/package.json +128 -0
  158. package/plugin/build/index.d.ts +21 -0
  159. package/plugin/build/index.js +192 -0
  160. package/plugin/src/index.ts +279 -0
  161. package/plugin/tsconfig.json +10 -0
  162. package/plugin/tsconfig.tsbuildinfo +1 -0
  163. package/src/AudioAnalysis/AudioAnalysis.types.ts +202 -0
  164. package/src/AudioAnalysis/extractAudioAnalysis.ts +333 -0
  165. package/src/AudioAnalysis/extractAudioData.ts +6 -0
  166. package/src/AudioAnalysis/extractMelSpectrogram.ts +144 -0
  167. package/src/AudioAnalysis/extractPreview.ts +34 -0
  168. package/src/AudioAnalysis/extractWaveform.ts +22 -0
  169. package/src/AudioRecorder.provider.tsx +54 -0
  170. package/src/ExpoAudioStream.native.ts +6 -0
  171. package/src/ExpoAudioStream.types.ts +641 -0
  172. package/src/ExpoAudioStream.web.ts +359 -0
  173. package/src/ExpoAudioStreamModule.ts +967 -0
  174. package/src/WebRecorder.web.ts +580 -0
  175. package/src/constants.ts +18 -0
  176. package/src/events.ts +60 -0
  177. package/src/index.ts +36 -0
  178. package/src/trimAudio.ts +90 -0
  179. package/src/useAudioRecorder.tsx +620 -0
  180. package/src/utils/BlobFix.ts +559 -0
  181. package/src/utils/audioProcessing.ts +205 -0
  182. package/src/utils/concatenateBuffers.ts +24 -0
  183. package/src/utils/convertPCMToFloat32.ts +170 -0
  184. package/src/utils/encodingToBitDepth.ts +18 -0
  185. package/src/utils/getWavFileInfo.ts +132 -0
  186. package/src/utils/writeWavHeader.ts +114 -0
  187. package/src/workers/InlineFeaturesExtractor.web.tsx +827 -0
  188. package/src/workers/inlineAudioWebWorker.web.tsx +156 -0
package/package.json ADDED
@@ -0,0 +1,128 @@
1
+ {
2
+ "name": "@siteed/expo-audio-studio",
3
+ "version": "2.1.1",
4
+ "description": "Comprehensive audio processing library for React Native and Expo with recording, analysis, visualization, and streaming capabilities across iOS, Android, and web",
5
+ "license": "MIT",
6
+ "main": "build/index.js",
7
+ "types": "build/index.d.ts",
8
+ "author": "Arthur Breton <abreton@siteed.net> (https://github.com/deeeed)",
9
+ "homepage": "https://github.com/deeeed/expo-audio-stream/blob/main/packages/expo-audio-studio/README.md",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/deeeed/expo-audio-stream.git",
13
+ "directory": "packages/expo-audio-studio"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/deeeed/expo-audio-stream/issues"
17
+ },
18
+ "keywords": [
19
+ "react-native",
20
+ "expo",
21
+ "audio",
22
+ "recording",
23
+ "audio-analysis",
24
+ "audio-processing",
25
+ "audio-visualization",
26
+ "waveform",
27
+ "spectrogram",
28
+ "mel-spectrogram",
29
+ "mfcc",
30
+ "audio-features",
31
+ "audio-compression",
32
+ "opus",
33
+ "aac",
34
+ "pcm",
35
+ "wav",
36
+ "cross-platform",
37
+ "background-recording",
38
+ "audio-trimming",
39
+ "dual-stream"
40
+ ],
41
+ "files": [
42
+ "src",
43
+ "android",
44
+ "ios",
45
+ "cpp",
46
+ "plugin",
47
+ "app.plugin.js",
48
+ "LICENSE",
49
+ "CHANGELOG.md",
50
+ "generated",
51
+ "expo-module.config.json",
52
+ "README.md",
53
+ "package.json",
54
+ "*.podspec",
55
+ "build",
56
+ "!ios/build",
57
+ "!android/build",
58
+ "!android/gradle",
59
+ "!android/gradlew",
60
+ "!android/gradlew.bat",
61
+ "!android/local.properties",
62
+ "!**/__tests__",
63
+ "!**/__fixtures__",
64
+ "!**/__mocks__",
65
+ "!**/.*"
66
+ ],
67
+ "scripts": {
68
+ "build": "tsc",
69
+ "build:plugin": "tsc --build plugin/tsconfig.json",
70
+ "build:plugin:dev": "expo-module build plugin",
71
+ "build:dev": "expo-module build",
72
+ "clean": "expo-module clean && rimraf plugin/build",
73
+ "lint": "expo-module lint",
74
+ "test": "expo-module test",
75
+ "typecheck": "tsc --noEmit",
76
+ "docgen": "typedoc src/index.ts --plugin typedoc-plugin-markdown --readme none --out ../../documentation_site/docs/api-reference/API",
77
+ "prepare": "yarn build && yarn build:plugin",
78
+ "prepublishOnly": "expo-module prepublishOnly",
79
+ "expo-module": "expo-module",
80
+ "open:ios": "open -a \"Xcode\" ../../apps/playground/ios",
81
+ "open:android": "open -a \"Android Studio\" ../../apps/playground/android",
82
+ "size": "bundle-size && size-limit",
83
+ "release": "./publish.sh"
84
+ },
85
+ "devDependencies": {
86
+ "@expo/config-plugins": "~9.0.0",
87
+ "@siteed/publisher": "^0.4.18",
88
+ "@size-limit/preset-big-lib": "^11.1.4",
89
+ "@types/jest": "^29.5.12",
90
+ "@types/node": "^20.12.7",
91
+ "@types/react": "~18.3.12",
92
+ "@typescript-eslint/eslint-plugin": "^7.7.0",
93
+ "@typescript-eslint/parser": "^7.7.0",
94
+ "bundle-size": "^1.1.5",
95
+ "eslint": "^8.56.0",
96
+ "eslint-config-prettier": "^9.1.0",
97
+ "eslint-config-universe": "^12.0.0",
98
+ "eslint-plugin-import": "^2.29.1",
99
+ "eslint-plugin-prettier": "^5.1.3",
100
+ "eslint-plugin-promise": "^6.1.1",
101
+ "eslint-plugin-react": "^7.34.1",
102
+ "expo": "^52.0.27",
103
+ "expo-module-scripts": "^4.0.2",
104
+ "jest": "^29.7.0",
105
+ "prettier": "^3.2.5",
106
+ "react-native": "0.76.6",
107
+ "rimraf": "^6.0.1",
108
+ "size-limit": "^11.1.4",
109
+ "ts-node": "^10.9.2",
110
+ "typedoc": "^0.27.4",
111
+ "typedoc-plugin-markdown": "^4.3.2",
112
+ "typescript": "~5.3.3"
113
+ },
114
+ "peerDependencies": {
115
+ "expo": "*",
116
+ "react": "*",
117
+ "react-native": "*"
118
+ },
119
+ "publishConfig": {
120
+ "access": "public",
121
+ "registry": "https://registry.npmjs.org"
122
+ },
123
+ "dependencies": {
124
+ "@siteed/design-system": "^0.35.1",
125
+ "crc-32": "^1.2.2",
126
+ "expo-modules-core": "~2.1.4"
127
+ }
128
+ }
@@ -0,0 +1,21 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ interface AudioStreamPluginOptions {
3
+ enablePhoneStateHandling?: boolean;
4
+ enableNotifications?: boolean;
5
+ enableBackgroundAudio?: boolean;
6
+ iosBackgroundModes?: {
7
+ useVoIP?: boolean;
8
+ useAudio?: boolean;
9
+ useProcessing?: boolean;
10
+ useLocation?: boolean;
11
+ useExternalAccessory?: boolean;
12
+ };
13
+ iosConfig?: {
14
+ allowBackgroundAudioControls?: boolean;
15
+ backgroundProcessingTitle?: string;
16
+ microphoneUsageDescription?: string;
17
+ notificationUsageDescription?: string;
18
+ };
19
+ }
20
+ declare const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions>;
21
+ export default withRecordingPermission;
@@ -0,0 +1,192 @@
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
+ iosBackgroundModes: {
18
+ useVoIP: false,
19
+ useAudio: false,
20
+ useProcessing: false,
21
+ useLocation: false,
22
+ useExternalAccessory: false,
23
+ },
24
+ iosConfig: {
25
+ microphoneUsageDescription: MICROPHONE_USAGE,
26
+ notificationUsageDescription: NOTIFICATION_USAGE,
27
+ },
28
+ ...(props || {}),
29
+ };
30
+ const { enablePhoneStateHandling, enableNotifications, enableBackgroundAudio, } = options;
31
+ debugLog('📱 Configuring Recording Permissions Plugin...', options);
32
+ // iOS Configuration
33
+ config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
34
+ // Always set the microphone usage description from options first
35
+ config.modResults['NSMicrophoneUsageDescription'] =
36
+ options.iosConfig?.microphoneUsageDescription ||
37
+ config.modResults['NSMicrophoneUsageDescription'] ||
38
+ MICROPHONE_USAGE;
39
+ if (enableNotifications) {
40
+ config.modResults['NSUserNotificationsUsageDescription'] =
41
+ options.iosConfig?.notificationUsageDescription ||
42
+ config.modResults['NSUserNotificationsUsageDescription'] ||
43
+ NOTIFICATION_USAGE;
44
+ config.modResults['NSUserNotificationAlertStyle'] = 'alert';
45
+ }
46
+ const existingBackgroundModes = config.modResults.UIBackgroundModes || [];
47
+ // Only add background modes if explicitly enabled and set to true
48
+ if (options.iosBackgroundModes?.useAudio === true &&
49
+ enableBackgroundAudio === true &&
50
+ !existingBackgroundModes.includes('audio')) {
51
+ // Don't automatically add 'audio' background mode as it's only for playback
52
+ // existingBackgroundModes.push('audio')
53
+ // Instead, ensure processing mode is used for background recording
54
+ if (options.iosBackgroundModes?.useProcessing !== true) {
55
+ console.warn(`${LOG_PREFIX} Warning: Background audio recording requires 'processing' background mode. Please enable 'useProcessing' in iosBackgroundModes.`);
56
+ }
57
+ }
58
+ if (options.iosBackgroundModes?.useVoIP === true &&
59
+ enablePhoneStateHandling === true) {
60
+ if (!existingBackgroundModes.includes('voip')) {
61
+ existingBackgroundModes.push('voip');
62
+ }
63
+ const existingCapabilities = (config.modResults
64
+ .UIRequiredDeviceCapabilities || []);
65
+ if (!existingCapabilities.includes('telephony')) {
66
+ existingCapabilities.push('telephony');
67
+ }
68
+ config.modResults.UIRequiredDeviceCapabilities =
69
+ existingCapabilities;
70
+ }
71
+ // Add additional background modes only if explicitly set to true
72
+ if (options.iosBackgroundModes?.useProcessing === true) {
73
+ if (!existingBackgroundModes.includes('processing')) {
74
+ existingBackgroundModes.push('processing');
75
+ }
76
+ // Add processing info if enabled
77
+ // Note: We keep the 'audiostream' namespace for native modules to maintain compatibility
78
+ config.modResults.BGTaskSchedulerPermittedIdentifiers = [
79
+ 'com.siteed.audiostream.processing',
80
+ ];
81
+ }
82
+ if (options.iosBackgroundModes?.useLocation === true) {
83
+ if (!existingBackgroundModes.includes('location')) {
84
+ existingBackgroundModes.push('location');
85
+ }
86
+ }
87
+ if (options.iosBackgroundModes?.useExternalAccessory === true) {
88
+ if (!existingBackgroundModes.includes('external-accessory')) {
89
+ existingBackgroundModes.push('external-accessory');
90
+ }
91
+ }
92
+ // Configure background processing info if enabled
93
+ if (options.iosConfig?.backgroundProcessingTitle) {
94
+ config.modResults.BGProcessingTaskTitle =
95
+ options.iosConfig.backgroundProcessingTitle;
96
+ }
97
+ // Configure audio session behavior
98
+ if (options.iosConfig?.allowBackgroundAudioControls) {
99
+ config.modResults.UIBackgroundModes = [
100
+ ...existingBackgroundModes,
101
+ 'remote-notification',
102
+ ];
103
+ config.modResults.MPNowPlayingInfoPropertyPlaybackRate = true;
104
+ }
105
+ config.modResults.UIBackgroundModes = existingBackgroundModes;
106
+ return config;
107
+ });
108
+ // Android Configuration
109
+ config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
110
+ const basePermissions = [
111
+ 'android.permission.RECORD_AUDIO',
112
+ 'android.permission.WAKE_LOCK',
113
+ ];
114
+ const optionalPermissions = [
115
+ enableNotifications && 'android.permission.POST_NOTIFICATIONS',
116
+ enablePhoneStateHandling && 'android.permission.READ_PHONE_STATE',
117
+ enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE',
118
+ enableBackgroundAudio &&
119
+ 'android.permission.FOREGROUND_SERVICE_MICROPHONE',
120
+ ].filter(Boolean);
121
+ const permissionsToAdd = [...basePermissions, ...optionalPermissions];
122
+ debugLog('📋 Existing Android permissions:', config.modResults.manifest['uses-permission']?.map((p) => p.$?.['android:name']) || []);
123
+ debugLog('➕ Adding Android permissions:', permissionsToAdd);
124
+ const { addPermission } = config_plugins_1.AndroidConfig.Permissions;
125
+ // Add each permission only if it doesn't exist
126
+ permissionsToAdd.forEach((permission) => {
127
+ const existingPermission = config.modResults.manifest['uses-permission']?.find((p) => p.$?.['android:name'] === permission);
128
+ if (!existingPermission) {
129
+ addPermission(config.modResults, permission);
130
+ }
131
+ });
132
+ // Get the main application node
133
+ const mainApplication = config.modResults.manifest.application?.[0];
134
+ if (mainApplication) {
135
+ debugLog('📱 Configuring Android application components...');
136
+ // Add RecordingActionReceiver
137
+ if (!mainApplication.receiver) {
138
+ mainApplication.receiver = [];
139
+ }
140
+ const receiverConfig = {
141
+ $: {
142
+ 'android:name': '.RecordingActionReceiver',
143
+ 'android:exported': 'false',
144
+ },
145
+ 'intent-filter': [
146
+ {
147
+ action: [
148
+ { $: { 'android:name': 'PAUSE_RECORDING' } },
149
+ { $: { 'android:name': 'RESUME_RECORDING' } },
150
+ { $: { 'android:name': 'STOP_RECORDING' } },
151
+ ],
152
+ },
153
+ ],
154
+ };
155
+ const receiverIndex = mainApplication.receiver.findIndex((receiver) => receiver.$?.['android:name'] === '.RecordingActionReceiver');
156
+ if (receiverIndex >= 0) {
157
+ mainApplication.receiver[receiverIndex] = receiverConfig;
158
+ }
159
+ else {
160
+ mainApplication.receiver.push(receiverConfig);
161
+ }
162
+ debugLog('✅ RecordingActionReceiver configured');
163
+ // Add AudioRecordingService
164
+ if (!mainApplication.service) {
165
+ mainApplication.service = [];
166
+ }
167
+ const serviceConfig = {
168
+ $: {
169
+ 'android:name': '.AudioRecordingService',
170
+ 'android:enabled': 'true',
171
+ 'android:exported': 'false',
172
+ 'android:foregroundServiceType': 'microphone',
173
+ },
174
+ };
175
+ const serviceIndex = mainApplication.service.findIndex((service) => service.$?.['android:name'] === '.AudioRecordingService');
176
+ if (serviceIndex >= 0) {
177
+ mainApplication.service[serviceIndex] = serviceConfig;
178
+ }
179
+ else {
180
+ mainApplication.service.push(serviceConfig);
181
+ }
182
+ debugLog('✅ AudioRecordingService configured');
183
+ }
184
+ else {
185
+ console.error(`${LOG_PREFIX} ❌ Main application node not found in Android Manifest`);
186
+ }
187
+ return config;
188
+ });
189
+ debugLog('✨ Recording Permissions Plugin configuration completed');
190
+ return config;
191
+ };
192
+ exports.default = withRecordingPermission;
@@ -0,0 +1,279 @@
1
+ import {
2
+ ConfigPlugin,
3
+ withAndroidManifest,
4
+ withInfoPlist,
5
+ AndroidConfig,
6
+ } from '@expo/config-plugins'
7
+ import { ExpoConfig } from '@expo/config-types'
8
+
9
+ const MICROPHONE_USAGE = 'Allow $(PRODUCT_NAME) to access your microphone'
10
+ const NOTIFICATION_USAGE = 'Show recording notifications and controls'
11
+ const LOG_PREFIX = '[@siteed/expo-audio-studio]'
12
+
13
+ function debugLog(message: string, ...args: unknown[]): void {
14
+ if (process.env.EXPO_DEBUG) {
15
+ console.log(`${LOG_PREFIX} ${message}`, ...args)
16
+ }
17
+ }
18
+
19
+ interface AudioStreamPluginOptions {
20
+ enablePhoneStateHandling?: boolean // Controls READ_PHONE_STATE permission
21
+ enableNotifications?: boolean
22
+ enableBackgroundAudio?: boolean
23
+ iosBackgroundModes?: {
24
+ useVoIP?: boolean
25
+ useAudio?: boolean
26
+ useProcessing?: boolean
27
+ useLocation?: boolean
28
+ useExternalAccessory?: boolean
29
+ }
30
+ iosConfig?: {
31
+ allowBackgroundAudioControls?: boolean
32
+ backgroundProcessingTitle?: string
33
+ microphoneUsageDescription?: string
34
+ notificationUsageDescription?: string
35
+ }
36
+ }
37
+
38
+ const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions> = (
39
+ config: ExpoConfig,
40
+ props: AudioStreamPluginOptions | void
41
+ ) => {
42
+ const options: AudioStreamPluginOptions = {
43
+ enablePhoneStateHandling: true, // Default to true for backward compatibility
44
+ enableNotifications: true,
45
+ enableBackgroundAudio: true,
46
+ iosBackgroundModes: {
47
+ useVoIP: false,
48
+ useAudio: false,
49
+ useProcessing: false,
50
+ useLocation: false,
51
+ useExternalAccessory: false,
52
+ },
53
+ iosConfig: {
54
+ microphoneUsageDescription: MICROPHONE_USAGE,
55
+ notificationUsageDescription: NOTIFICATION_USAGE,
56
+ },
57
+ ...(props || {}),
58
+ }
59
+
60
+ const {
61
+ enablePhoneStateHandling,
62
+ enableNotifications,
63
+ enableBackgroundAudio,
64
+ } = options
65
+
66
+ debugLog('📱 Configuring Recording Permissions Plugin...', options)
67
+
68
+ // iOS Configuration
69
+ config = withInfoPlist(config as any, (config) => {
70
+ // Always set the microphone usage description from options first
71
+ config.modResults['NSMicrophoneUsageDescription'] =
72
+ options.iosConfig?.microphoneUsageDescription ||
73
+ config.modResults['NSMicrophoneUsageDescription'] ||
74
+ MICROPHONE_USAGE
75
+
76
+ if (enableNotifications) {
77
+ config.modResults['NSUserNotificationsUsageDescription'] =
78
+ options.iosConfig?.notificationUsageDescription ||
79
+ config.modResults['NSUserNotificationsUsageDescription'] ||
80
+ NOTIFICATION_USAGE
81
+ config.modResults['NSUserNotificationAlertStyle'] = 'alert'
82
+ }
83
+
84
+ const existingBackgroundModes =
85
+ config.modResults.UIBackgroundModes || []
86
+
87
+ // Only add background modes if explicitly enabled and set to true
88
+ if (
89
+ options.iosBackgroundModes?.useAudio === true &&
90
+ enableBackgroundAudio === true &&
91
+ !existingBackgroundModes.includes('audio')
92
+ ) {
93
+ // Don't automatically add 'audio' background mode as it's only for playback
94
+ // existingBackgroundModes.push('audio')
95
+
96
+ // Instead, ensure processing mode is used for background recording
97
+ if (options.iosBackgroundModes?.useProcessing !== true) {
98
+ console.warn(
99
+ `${LOG_PREFIX} Warning: Background audio recording requires 'processing' background mode. Please enable 'useProcessing' in iosBackgroundModes.`
100
+ )
101
+ }
102
+ }
103
+
104
+ if (
105
+ options.iosBackgroundModes?.useVoIP === true &&
106
+ enablePhoneStateHandling === true
107
+ ) {
108
+ if (!existingBackgroundModes.includes('voip')) {
109
+ existingBackgroundModes.push('voip')
110
+ }
111
+ const existingCapabilities = (config.modResults
112
+ .UIRequiredDeviceCapabilities || []) as string[]
113
+ if (!existingCapabilities.includes('telephony')) {
114
+ existingCapabilities.push('telephony')
115
+ }
116
+ config.modResults.UIRequiredDeviceCapabilities =
117
+ existingCapabilities
118
+ }
119
+
120
+ // Add additional background modes only if explicitly set to true
121
+ if (options.iosBackgroundModes?.useProcessing === true) {
122
+ if (!existingBackgroundModes.includes('processing')) {
123
+ existingBackgroundModes.push('processing')
124
+ }
125
+ // Add processing info if enabled
126
+ // Note: We keep the 'audiostream' namespace for native modules to maintain compatibility
127
+ config.modResults.BGTaskSchedulerPermittedIdentifiers = [
128
+ 'com.siteed.audiostream.processing',
129
+ ]
130
+ }
131
+
132
+ if (options.iosBackgroundModes?.useLocation === true) {
133
+ if (!existingBackgroundModes.includes('location')) {
134
+ existingBackgroundModes.push('location')
135
+ }
136
+ }
137
+
138
+ if (options.iosBackgroundModes?.useExternalAccessory === true) {
139
+ if (!existingBackgroundModes.includes('external-accessory')) {
140
+ existingBackgroundModes.push('external-accessory')
141
+ }
142
+ }
143
+
144
+ // Configure background processing info if enabled
145
+ if (options.iosConfig?.backgroundProcessingTitle) {
146
+ config.modResults.BGProcessingTaskTitle =
147
+ options.iosConfig.backgroundProcessingTitle
148
+ }
149
+
150
+ // Configure audio session behavior
151
+ if (options.iosConfig?.allowBackgroundAudioControls) {
152
+ config.modResults.UIBackgroundModes = [
153
+ ...existingBackgroundModes,
154
+ 'remote-notification',
155
+ ]
156
+ config.modResults.MPNowPlayingInfoPropertyPlaybackRate = true
157
+ }
158
+
159
+ config.modResults.UIBackgroundModes = existingBackgroundModes
160
+ return config
161
+ })
162
+
163
+ // Android Configuration
164
+ config = withAndroidManifest(config as any, (config) => {
165
+ const basePermissions = [
166
+ 'android.permission.RECORD_AUDIO',
167
+ 'android.permission.WAKE_LOCK',
168
+ ]
169
+
170
+ const optionalPermissions = [
171
+ enableNotifications && 'android.permission.POST_NOTIFICATIONS',
172
+ enablePhoneStateHandling && 'android.permission.READ_PHONE_STATE',
173
+ enableBackgroundAudio && 'android.permission.FOREGROUND_SERVICE',
174
+ enableBackgroundAudio &&
175
+ 'android.permission.FOREGROUND_SERVICE_MICROPHONE',
176
+ ].filter(Boolean) as string[]
177
+
178
+ const permissionsToAdd = [...basePermissions, ...optionalPermissions]
179
+
180
+ debugLog(
181
+ '📋 Existing Android permissions:',
182
+ config.modResults.manifest['uses-permission']?.map(
183
+ (p) => p.$?.['android:name']
184
+ ) || []
185
+ )
186
+
187
+ debugLog('➕ Adding Android permissions:', permissionsToAdd)
188
+
189
+ const { addPermission } = AndroidConfig.Permissions
190
+
191
+ // Add each permission only if it doesn't exist
192
+ permissionsToAdd.forEach((permission) => {
193
+ const existingPermission = config.modResults.manifest[
194
+ 'uses-permission'
195
+ ]?.find((p) => p.$?.['android:name'] === permission)
196
+ if (!existingPermission) {
197
+ addPermission(config.modResults, permission)
198
+ }
199
+ })
200
+
201
+ // Get the main application node
202
+ const mainApplication = config.modResults.manifest.application?.[0]
203
+ if (mainApplication) {
204
+ debugLog('📱 Configuring Android application components...')
205
+
206
+ // Add RecordingActionReceiver
207
+ if (!mainApplication.receiver) {
208
+ mainApplication.receiver = []
209
+ }
210
+
211
+ const receiverConfig = {
212
+ $: {
213
+ 'android:name': '.RecordingActionReceiver',
214
+ 'android:exported': 'false' as const,
215
+ },
216
+ 'intent-filter': [
217
+ {
218
+ action: [
219
+ { $: { 'android:name': 'PAUSE_RECORDING' } },
220
+ { $: { 'android:name': 'RESUME_RECORDING' } },
221
+ { $: { 'android:name': 'STOP_RECORDING' } },
222
+ ],
223
+ },
224
+ ],
225
+ }
226
+
227
+ const receiverIndex = mainApplication.receiver.findIndex(
228
+ (receiver: any) =>
229
+ receiver.$?.['android:name'] === '.RecordingActionReceiver'
230
+ )
231
+
232
+ if (receiverIndex >= 0) {
233
+ mainApplication.receiver[receiverIndex] = receiverConfig
234
+ } else {
235
+ mainApplication.receiver.push(receiverConfig)
236
+ }
237
+
238
+ debugLog('✅ RecordingActionReceiver configured')
239
+
240
+ // Add AudioRecordingService
241
+ if (!mainApplication.service) {
242
+ mainApplication.service = []
243
+ }
244
+
245
+ const serviceConfig = {
246
+ $: {
247
+ 'android:name': '.AudioRecordingService',
248
+ 'android:enabled': 'true' as const,
249
+ 'android:exported': 'false' as const,
250
+ 'android:foregroundServiceType': 'microphone',
251
+ },
252
+ }
253
+
254
+ const serviceIndex = mainApplication.service.findIndex(
255
+ (service: any) =>
256
+ service.$?.['android:name'] === '.AudioRecordingService'
257
+ )
258
+
259
+ if (serviceIndex >= 0) {
260
+ mainApplication.service[serviceIndex] = serviceConfig
261
+ } else {
262
+ mainApplication.service.push(serviceConfig)
263
+ }
264
+
265
+ debugLog('✅ AudioRecordingService configured')
266
+ } else {
267
+ console.error(
268
+ `${LOG_PREFIX} ❌ Main application node not found in Android Manifest`
269
+ )
270
+ }
271
+
272
+ return config
273
+ })
274
+
275
+ debugLog('✨ Recording Permissions Plugin configuration completed')
276
+ return config as any
277
+ }
278
+
279
+ export default withRecordingPermission
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "expo-module-scripts/tsconfig.plugin",
3
+ "compilerOptions": {
4
+ "outDir": "build",
5
+ "lib": ["es2023", "dom"],
6
+ "rootDir": "src"
7
+ },
8
+ "include": ["./src"],
9
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"]
10
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/index.ts"],"version":"5.7.2"}