@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.
- package/CHANGELOG.md +210 -0
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/android/build.gradle +105 -0
- package/android/src/main/AndroidManifest.xml +27 -0
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +166 -0
- package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +9 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +131 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +103 -0
- package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +435 -0
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +2235 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +1437 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +166 -0
- package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +1099 -0
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +21 -0
- package/android/src/main/java/net/siteed/audiostream/EventSender.kt +7 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +739 -0
- package/android/src/main/java/net/siteed/audiostream/FFT.kt +99 -0
- package/android/src/main/java/net/siteed/audiostream/Features.kt +98 -0
- package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +70 -0
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +59 -0
- package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +59 -0
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +205 -0
- package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +19 -0
- package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +159 -0
- package/android/src/main/res/drawable/ic_default_action_icon.xml +16 -0
- package/android/src/main/res/drawable/ic_microphone.xml +13 -0
- package/android/src/main/res/drawable/ic_pause.xml +10 -0
- package/android/src/main/res/drawable/ic_play.xml +10 -0
- package/android/src/main/res/drawable/ic_stop.xml +10 -0
- package/android/src/main/res/layout/notification_recording.xml +37 -0
- package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
- package/app.plugin.js +1 -0
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +179 -0
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
- package/build/AudioAnalysis/AudioAnalysis.types.js +3 -0
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts +68 -0
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
- package/build/AudioAnalysis/extractAudioAnalysis.js +203 -0
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
- package/build/AudioAnalysis/extractAudioData.d.ts +3 -0
- package/build/AudioAnalysis/extractAudioData.d.ts.map +1 -0
- package/build/AudioAnalysis/extractAudioData.js +5 -0
- package/build/AudioAnalysis/extractAudioData.js.map +1 -0
- package/build/AudioAnalysis/extractMelSpectrogram.d.ts +14 -0
- package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +1 -0
- package/build/AudioAnalysis/extractMelSpectrogram.js +85 -0
- package/build/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
- package/build/AudioAnalysis/extractPreview.d.ts +11 -0
- package/build/AudioAnalysis/extractPreview.d.ts.map +1 -0
- package/build/AudioAnalysis/extractPreview.js +25 -0
- package/build/AudioAnalysis/extractPreview.js.map +1 -0
- package/build/AudioAnalysis/extractWaveform.d.ts +8 -0
- package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -0
- package/build/AudioAnalysis/extractWaveform.js +11 -0
- package/build/AudioAnalysis/extractWaveform.js.map +1 -0
- package/build/AudioRecorder.provider.d.ts +11 -0
- package/build/AudioRecorder.provider.d.ts.map +1 -0
- package/build/AudioRecorder.provider.js +37 -0
- package/build/AudioRecorder.provider.js.map +1 -0
- package/build/ExpoAudioStream.native.d.ts +3 -0
- package/build/ExpoAudioStream.native.d.ts.map +1 -0
- package/build/ExpoAudioStream.native.js +6 -0
- package/build/ExpoAudioStream.native.js.map +1 -0
- package/build/ExpoAudioStream.types.d.ts +532 -0
- package/build/ExpoAudioStream.types.d.ts.map +1 -0
- package/build/ExpoAudioStream.types.js +2 -0
- package/build/ExpoAudioStream.types.js.map +1 -0
- package/build/ExpoAudioStream.web.d.ts +59 -0
- package/build/ExpoAudioStream.web.d.ts.map +1 -0
- package/build/ExpoAudioStream.web.js +285 -0
- package/build/ExpoAudioStream.web.js.map +1 -0
- package/build/ExpoAudioStreamModule.d.ts +3 -0
- package/build/ExpoAudioStreamModule.d.ts.map +1 -0
- package/build/ExpoAudioStreamModule.js +693 -0
- package/build/ExpoAudioStreamModule.js.map +1 -0
- package/build/WebRecorder.web.d.ts +119 -0
- package/build/WebRecorder.web.d.ts.map +1 -0
- package/build/WebRecorder.web.js +436 -0
- package/build/WebRecorder.web.js.map +1 -0
- package/build/constants.d.ts +11 -0
- package/build/constants.d.ts.map +1 -0
- package/build/constants.js +14 -0
- package/build/constants.js.map +1 -0
- package/build/events.d.ts +26 -0
- package/build/events.d.ts.map +1 -0
- package/build/events.js +21 -0
- package/build/events.js.map +1 -0
- package/build/index.d.ts +15 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +14 -0
- package/build/index.js.map +1 -0
- package/build/trimAudio.d.ts +25 -0
- package/build/trimAudio.d.ts.map +1 -0
- package/build/trimAudio.js +67 -0
- package/build/trimAudio.js.map +1 -0
- package/build/useAudioRecorder.d.ts +21 -0
- package/build/useAudioRecorder.d.ts.map +1 -0
- package/build/useAudioRecorder.js +427 -0
- package/build/useAudioRecorder.js.map +1 -0
- package/build/utils/BlobFix.d.ts +9 -0
- package/build/utils/BlobFix.d.ts.map +1 -0
- package/build/utils/BlobFix.js +498 -0
- package/build/utils/BlobFix.js.map +1 -0
- package/build/utils/audioProcessing.d.ts +24 -0
- package/build/utils/audioProcessing.d.ts.map +1 -0
- package/build/utils/audioProcessing.js +133 -0
- package/build/utils/audioProcessing.js.map +1 -0
- package/build/utils/concatenateBuffers.d.ts +8 -0
- package/build/utils/concatenateBuffers.d.ts.map +1 -0
- package/build/utils/concatenateBuffers.js +21 -0
- package/build/utils/concatenateBuffers.js.map +1 -0
- package/build/utils/convertPCMToFloat32.d.ts +13 -0
- package/build/utils/convertPCMToFloat32.d.ts.map +1 -0
- package/build/utils/convertPCMToFloat32.js +120 -0
- package/build/utils/convertPCMToFloat32.js.map +1 -0
- package/build/utils/encodingToBitDepth.d.ts +5 -0
- package/build/utils/encodingToBitDepth.d.ts.map +1 -0
- package/build/utils/encodingToBitDepth.js +13 -0
- package/build/utils/encodingToBitDepth.js.map +1 -0
- package/build/utils/getWavFileInfo.d.ts +26 -0
- package/build/utils/getWavFileInfo.d.ts.map +1 -0
- package/build/utils/getWavFileInfo.js +92 -0
- package/build/utils/getWavFileInfo.js.map +1 -0
- package/build/utils/writeWavHeader.d.ts +49 -0
- package/build/utils/writeWavHeader.d.ts.map +1 -0
- package/build/utils/writeWavHeader.js +91 -0
- package/build/utils/writeWavHeader.js.map +1 -0
- package/build/workers/InlineFeaturesExtractor.web.d.ts +2 -0
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
- package/build/workers/InlineFeaturesExtractor.web.js +828 -0
- package/build/workers/InlineFeaturesExtractor.web.js.map +1 -0
- package/build/workers/inlineAudioWebWorker.web.d.ts +2 -0
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
- package/build/workers/inlineAudioWebWorker.web.js +157 -0
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/AudioAnalysisData.swift +74 -0
- package/ios/AudioNotificationManager.swift +135 -0
- package/ios/AudioProcessingHelpers.swift +743 -0
- package/ios/AudioProcessor.swift +1313 -0
- package/ios/AudioStreamError.swift +7 -0
- package/ios/AudioStreamManager.swift +1708 -0
- package/ios/AudioStreamManagerDelegate.swift +16 -0
- package/ios/DataPoint.swift +54 -0
- package/ios/DecodingConfig.swift +47 -0
- package/ios/ExpoAudioStream.podspec +27 -0
- package/ios/ExpoAudioStreamModule.swift +805 -0
- package/ios/FFT.swift +62 -0
- package/ios/Features.swift +95 -0
- package/ios/Logger.swift +7 -0
- package/ios/NotificationExtension.swift +15 -0
- package/ios/RecordingResult.swift +22 -0
- package/ios/RecordingSettings.swift +265 -0
- package/ios/WaveformExtractor.swift +105 -0
- package/package.json +128 -0
- package/plugin/build/index.d.ts +21 -0
- package/plugin/build/index.js +192 -0
- package/plugin/src/index.ts +279 -0
- package/plugin/tsconfig.json +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/src/AudioAnalysis/AudioAnalysis.types.ts +202 -0
- package/src/AudioAnalysis/extractAudioAnalysis.ts +333 -0
- package/src/AudioAnalysis/extractAudioData.ts +6 -0
- package/src/AudioAnalysis/extractMelSpectrogram.ts +144 -0
- package/src/AudioAnalysis/extractPreview.ts +34 -0
- package/src/AudioAnalysis/extractWaveform.ts +22 -0
- package/src/AudioRecorder.provider.tsx +54 -0
- package/src/ExpoAudioStream.native.ts +6 -0
- package/src/ExpoAudioStream.types.ts +641 -0
- package/src/ExpoAudioStream.web.ts +359 -0
- package/src/ExpoAudioStreamModule.ts +967 -0
- package/src/WebRecorder.web.ts +580 -0
- package/src/constants.ts +18 -0
- package/src/events.ts +60 -0
- package/src/index.ts +36 -0
- package/src/trimAudio.ts +90 -0
- package/src/useAudioRecorder.tsx +620 -0
- package/src/utils/BlobFix.ts +559 -0
- package/src/utils/audioProcessing.ts +205 -0
- package/src/utils/concatenateBuffers.ts +24 -0
- package/src/utils/convertPCMToFloat32.ts +170 -0
- package/src/utils/encodingToBitDepth.ts +18 -0
- package/src/utils/getWavFileInfo.ts +132 -0
- package/src/utils/writeWavHeader.ts +114 -0
- package/src/workers/InlineFeaturesExtractor.web.tsx +827 -0
- 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 @@
|
|
|
1
|
+
{"root":["./src/index.ts"],"version":"5.7.2"}
|