@siteed/expo-audio-stream 1.0.3 → 1.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/README.md +26 -175
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +47 -7
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +1 -0
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +5 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +12 -3
- package/build/index.js +8 -7
- package/ios/AudioProcessor.swift +7 -5
- package/ios/AudioStreamManager.swift +1 -0
- package/ios/ExpoAudioStream.podspec +1 -1
- package/ios/ExpoAudioStreamModule.swift +36 -0
- package/ios/RecordingResult.swift +1 -0
- package/package.json +95 -65
- package/src/AudioAnalysis/AudioAnalysis.types.ts +59 -60
- package/src/AudioAnalysis/extractAudioAnalysis.ts +132 -121
- package/src/AudioAnalysis/extractWaveform.ts +18 -18
- package/src/AudioRecorder.provider.tsx +53 -53
- package/src/ExpoAudioStream.native.ts +2 -2
- package/src/ExpoAudioStream.types.ts +59 -53
- package/src/ExpoAudioStream.web.ts +231 -205
- package/src/ExpoAudioStreamModule.ts +22 -15
- package/src/WebRecorder.web.ts +407 -390
- package/src/constants.ts +11 -11
- package/src/events.ts +27 -13
- package/src/index.ts +17 -15
- package/src/logger.ts +15 -19
- package/src/useAudioRecorder.tsx +394 -389
- package/src/utils/BlobFix.ts +550 -0
- package/src/utils/concatenateBuffers.ts +24 -0
- package/src/utils/convertPCMToFloat32.ts +72 -45
- package/src/utils/encodingToBitDepth.ts +14 -14
- package/src/utils/getWavFileInfo.ts +106 -99
- package/src/utils/writeWavHeader.ts +50 -45
- package/src/workers/InlineFeaturesExtractor.web.tsx +296 -286
- package/src/workers/inlineAudioWebWorker.web.tsx +230 -222
- package/.eslintrc.js +0 -2
- package/.size-limit.json +0 -6
- package/android/.gradle/8.1.1/checksums/checksums.lock +0 -0
- package/android/.gradle/8.1.1/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.1.1/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.1.1/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.1.1/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.1.1/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/app.plugin.js +0 -1
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -76
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
- package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -4
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
- package/build/AudioAnalysis/extractAudioAnalysis.js +0 -101
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
- package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
- package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
- package/build/AudioAnalysis/extractWaveform.js +0 -14
- package/build/AudioAnalysis/extractWaveform.js.map +0 -1
- package/build/AudioRecorder.provider.d.ts +0 -23
- package/build/AudioRecorder.provider.d.ts.map +0 -1
- package/build/AudioRecorder.provider.js +0 -36
- package/build/AudioRecorder.provider.js.map +0 -1
- package/build/ExpoAudioStream.native.d.ts +0 -3
- package/build/ExpoAudioStream.native.d.ts.map +0 -1
- package/build/ExpoAudioStream.native.js +0 -6
- package/build/ExpoAudioStream.native.js.map +0 -1
- package/build/ExpoAudioStream.types.d.ts +0 -60
- package/build/ExpoAudioStream.types.d.ts.map +0 -1
- package/build/ExpoAudioStream.types.js +0 -2
- package/build/ExpoAudioStream.types.js.map +0 -1
- package/build/ExpoAudioStream.web.d.ts +0 -42
- package/build/ExpoAudioStream.web.d.ts.map +0 -1
- package/build/ExpoAudioStream.web.js +0 -185
- package/build/ExpoAudioStream.web.js.map +0 -1
- package/build/ExpoAudioStreamModule.d.ts +0 -3
- package/build/ExpoAudioStreamModule.d.ts.map +0 -1
- package/build/ExpoAudioStreamModule.js +0 -18
- package/build/ExpoAudioStreamModule.js.map +0 -1
- package/build/WebRecorder.web.d.ts +0 -51
- package/build/WebRecorder.web.d.ts.map +0 -1
- package/build/WebRecorder.web.js +0 -288
- package/build/WebRecorder.web.js.map +0 -1
- package/build/constants.d.ts +0 -11
- package/build/constants.d.ts.map +0 -1
- package/build/constants.js +0 -14
- package/build/constants.js.map +0 -1
- package/build/events.d.ts +0 -6
- package/build/events.d.ts.map +0 -1
- package/build/events.js +0 -15
- package/build/events.js.map +0 -1
- package/build/index.d.ts +0 -10
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/logger.d.ts +0 -9
- package/build/logger.d.ts.map +0 -1
- package/build/logger.js +0 -17
- package/build/logger.js.map +0 -1
- package/build/useAudioRecorder.d.ts +0 -37
- package/build/useAudioRecorder.d.ts.map +0 -1
- package/build/useAudioRecorder.js +0 -271
- package/build/useAudioRecorder.js.map +0 -1
- package/build/utils/convertPCMToFloat32.d.ts +0 -11
- package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
- package/build/utils/convertPCMToFloat32.js +0 -41
- package/build/utils/convertPCMToFloat32.js.map +0 -1
- package/build/utils/encodingToBitDepth.d.ts +0 -5
- package/build/utils/encodingToBitDepth.d.ts.map +0 -1
- package/build/utils/encodingToBitDepth.js +0 -13
- package/build/utils/encodingToBitDepth.js.map +0 -1
- package/build/utils/getWavFileInfo.d.ts +0 -25
- package/build/utils/getWavFileInfo.d.ts.map +0 -1
- package/build/utils/getWavFileInfo.js +0 -89
- package/build/utils/getWavFileInfo.js.map +0 -1
- package/build/utils/writeWavHeader.d.ts +0 -9
- package/build/utils/writeWavHeader.d.ts.map +0 -1
- package/build/utils/writeWavHeader.js +0 -41
- package/build/utils/writeWavHeader.js.map +0 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
- package/build/workers/InlineFeaturesExtractor.web.js +0 -303
- package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
- package/build/workers/inlineAudioWebWorker.web.js +0 -243
- package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
- package/expo-module.config.json +0 -18
- package/plugin/build/index.d.ts +0 -5
- package/plugin/build/index.js +0 -28
- package/plugin/src/index.ts +0 -53
- package/plugin/tsconfig.json +0 -14
- package/publish.sh +0 -8
- package/tsconfig.json +0 -9
package/README.md
CHANGED
|
@@ -1,186 +1,37 @@
|
|
|
1
1
|
# @siteed/expo-audio-stream
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://kandi.openweaver.com/typescript/siteed/expo-audio-stream)
|
|
4
|
+
[](https://www.npmjs.com/package/@siteed/expo-audio-stream)
|
|
5
|
+
[](https://www.npmjs.com/package/@siteed/expo-audio-stream)
|
|
6
|
+
[](https://www.npmjs.com/package/@siteed/expo-audio-stream)
|
|
4
7
|
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- Real-time audio streaming across iOS, Android, and web.
|
|
8
|
-
- Configurable intervals for audio buffer receipt.
|
|
9
|
-
- Automated microphone permissions setup in managed Expo projects.
|
|
10
|
-
- IOS is automatically setup to handle background audio recording.
|
|
11
|
-
- Listeners for audio data events with detailed event payloads.
|
|
12
|
-
- Utility functions for recording control and file management.
|
|
13
|
-
|
|
14
|
-
## Playground Example Application
|
|
15
|
-
|
|
16
|
-
The project comes with a fully functional example application that demonstrates how to use the library in a real-world scenario.
|
|
17
|
-
|
|
18
|
-

|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
To try it:
|
|
22
|
-
```bash
|
|
23
|
-
git clone https://github.com/deeeed/expo-audio-stream.git
|
|
24
|
-
cd expo-audio-stream
|
|
25
|
-
yarn
|
|
26
|
-
yarn playground ios
|
|
27
|
-
yarn playground android
|
|
28
|
-
yarn playground web
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Installation
|
|
32
|
-
|
|
33
|
-
To install `@siteed/expo-audio-stream`, add it to your project using npm or Yarn:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm install @siteed/expo-audio-stream
|
|
37
|
-
# or
|
|
38
|
-
yarn add @siteed/expo-audio-stream
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
### Configuring with app.json
|
|
43
|
-
|
|
44
|
-
To ensure expo-audio-stream works correctly with Expo, you must add it as a plugin in your app.json configuration file.
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"expo": {
|
|
49
|
-
"plugins": ["@siteed/expo-audio-stream"]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Make sure to run `npx expo prebuild` after adding the plugin to your app.json file.
|
|
55
|
-
|
|
56
|
-
## Usage
|
|
57
|
-
|
|
58
|
-
This library provides two hooks: `useAudioRecorder` for standalone use and `useSharedAudioRecorder` for accessing shared recording state within a React context.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### Standalone Recording
|
|
62
|
-
|
|
63
|
-
The `playground/` folder contains a fully functional React Native application demonstrating how to integrate and use `useAudioRecorder` from `@siteed/expo-audio-stream`. This includes starting and stopping recordings, handling permissions, and processing live audio data.
|
|
64
8
|
|
|
9
|
+
<div align="center">
|
|
10
|
+
<h2 align="center">
|
|
11
|
+
<br />
|
|
12
|
+
<strong>Comprehensive library designed to facilitate real-time audio processing and streaming across iOS, Android, and web platforms.
|
|
13
|
+
<br />
|
|
14
|
+
<br />
|
|
15
|
+
<a href="https://deeeed.github.io/expo-audio-stream/playground/">
|
|
16
|
+
<img src="../../docs/demo.gif" alt="Screenshot Playground">
|
|
17
|
+
</a>
|
|
18
|
+
</h2>
|
|
19
|
+
</div>
|
|
65
20
|
|
|
66
|
-
|
|
21
|
+
**Give it a GitHub star 🌟, if you found this repo useful.**
|
|
22
|
+
[](https://github.com/deeeed/expo-audio-stream)
|
|
67
23
|
|
|
68
|
-
```tsx
|
|
69
|
-
import {
|
|
70
|
-
useAudioRecorder,
|
|
71
|
-
AudioStreamResult,
|
|
72
|
-
} from '@siteed/expo-audio-stream';
|
|
73
24
|
|
|
74
|
-
|
|
75
|
-
const { startRecording, stopRecording, duration, size, isRecording } = useAudioRecorder({
|
|
76
|
-
debug: true,
|
|
77
|
-
onAudioStream: (audioData: Blob) => {
|
|
78
|
-
console.log(`audio event`,audioData);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const handleStart = async () => {
|
|
83
|
-
const { granted } = await Audio.requestPermissionsAsync();
|
|
84
|
-
if (granted) {
|
|
85
|
-
const fileUri = await startRecording({interval: 500});
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const handleStop = async () => {
|
|
90
|
-
const result: AudioStreamResult = await stopRecording();
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const renderRecording = () => (
|
|
94
|
-
<View>
|
|
95
|
-
<Text>Duration: {duration} ms</Text>
|
|
96
|
-
<Text>Size: {size} bytes</Text>
|
|
97
|
-
<Button title="Stop Recording" onPress={handleStop} />
|
|
98
|
-
</View>
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const renderStopped = () => (
|
|
102
|
-
<View>
|
|
103
|
-
<Button title="Start Recording" onPress={handleStart} />
|
|
104
|
-
</View>
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<View>
|
|
109
|
-
<Button title="Request Permission" onPress={() => Audio.requestPermissionsAsync()} />
|
|
110
|
-
{isRecording ? renderRecording() : renderStopped()}
|
|
111
|
-
</View>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
The library also exposes an `addAudioEventListener` function that provides an `AudioEventPayload` object that you can subscribe to:
|
|
117
|
-
```tsx
|
|
118
|
-
import { addAudioEventListener } from '@siteed/expo-audio-stream';
|
|
119
|
-
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
const subscribe = addAudioEventListener(async ({fileUri, deltaSize, totalSize, from, streamUuid, encoded, mimeType, buffer}) => {
|
|
122
|
-
log(`Received audio event:`, {fileUri, deltaSize, totalSize, mimeType, from, streamUuid, encodedLength: encoded?.length, bufferLength: buffer?.length})
|
|
123
|
-
});
|
|
124
|
-
return () => subscribe.remove();
|
|
125
|
-
}, []);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Shared Recording
|
|
129
|
-
|
|
130
|
-
To facilitate state sharing across multiple components or screens, useSharedAudioRecorder can be used. It should be wrapped in a AudioRecorderProvider context provider to ensure state is managed at a higher level and shared appropriately.
|
|
131
|
-
|
|
132
|
-
#### Shared Recording Usage
|
|
133
|
-
|
|
134
|
-
```tsx
|
|
135
|
-
import { AudioRecorderProvider, useSharedAudioRecorder } from '@siteed/expo-audio-stream';
|
|
136
|
-
|
|
137
|
-
export default function ParentComponent() {
|
|
138
|
-
return (
|
|
139
|
-
<AudioRecorderProvider>
|
|
140
|
-
<ChildComponent />
|
|
141
|
-
</AudioRecorderProvider>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function ChildComponent() {
|
|
146
|
-
const {
|
|
147
|
-
startRecording,
|
|
148
|
-
isRecording
|
|
149
|
-
} = useSharedAudioRecorder();
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<View>
|
|
153
|
-
<Text>{isRecording ? "Recording..." : "Ready to record"}</Text>
|
|
154
|
-
<Button title="Toggle Recording" onPress={startRecording} />
|
|
155
|
-
</View>
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Add Event Listener
|
|
161
|
-
|
|
162
|
-
You can also add an event listener to receive detailed audio event payloads, which is crucial for both standalone and shared usage scenarios.
|
|
163
|
-
|
|
164
|
-
```tsx
|
|
165
|
-
import { useEffect } from 'react';
|
|
166
|
-
import { addAudioEventListener } from '@siteed/expo-audio-stream';
|
|
167
|
-
|
|
168
|
-
function App() {
|
|
169
|
-
useEffect(() => {
|
|
170
|
-
const subscription = addAudioEventListener(event => {
|
|
171
|
-
console.log("Audio event received:", event);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
return () => subscription.remove();
|
|
175
|
-
}, []);
|
|
25
|
+
## Features
|
|
176
26
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
27
|
+
- Real-time audio streaming across iOS, Android, and web.
|
|
28
|
+
- Configurable intervals for audio buffer receipt.
|
|
29
|
+
- Automated microphone permissions setup in managed Expo projects.
|
|
30
|
+
- Background audio recording on iOS.
|
|
31
|
+
- Audio features extraction during recording.
|
|
32
|
+
- Consistent WAV PCM recording format across all platforms.
|
|
180
33
|
|
|
181
|
-
##
|
|
34
|
+
## Documentation
|
|
182
35
|
|
|
183
|
-
|
|
184
|
-
- on web, it usually records in opus but it depends on the browser configuration.
|
|
36
|
+
For detailed documentation, please refer to the [Getting Started Guide](https://deeeed.github.io/expo-audio-stream/docs/).
|
|
185
37
|
|
|
186
|
-
If you want to process the audio livestream directly, I recommend having another encoding step to align the audio format across platforms.
|
|
@@ -56,13 +56,53 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
56
56
|
return null
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
val
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
// Read the WAV header
|
|
60
|
+
val riffHeader = String(fileData.sliceArray(0..3))
|
|
61
|
+
if (riffHeader != "RIFF") {
|
|
62
|
+
Log.e("AudioProcessor", "Invalid RIFF header")
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
val format = String(fileData.sliceArray(8..11))
|
|
67
|
+
if (format != "WAVE") {
|
|
68
|
+
Log.e("AudioProcessor", "Invalid WAVE format")
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
var offset = 12
|
|
73
|
+
var dataSize = 0
|
|
74
|
+
var sampleRate = 0
|
|
75
|
+
var channels = 0
|
|
76
|
+
var bitDepth = 0
|
|
77
|
+
|
|
78
|
+
// Parse chunks until we find the 'data' chunk
|
|
79
|
+
while (offset < fileData.size - 8) {
|
|
80
|
+
val chunkId = String(fileData.sliceArray(offset until offset + 4))
|
|
81
|
+
val chunkSize = ByteBuffer.wrap(fileData.sliceArray(offset + 4 until offset + 8)).order(ByteOrder.LITTLE_ENDIAN).int
|
|
82
|
+
|
|
83
|
+
when (chunkId) {
|
|
84
|
+
"fmt " -> {
|
|
85
|
+
channels = ByteBuffer.wrap(fileData.sliceArray(offset + 10 until offset + 12)).order(ByteOrder.LITTLE_ENDIAN).short.toInt()
|
|
86
|
+
sampleRate = ByteBuffer.wrap(fileData.sliceArray(offset + 12 until offset + 16)).order(ByteOrder.LITTLE_ENDIAN).int
|
|
87
|
+
bitDepth = ByteBuffer.wrap(fileData.sliceArray(offset + 22 until offset + 24)).order(ByteOrder.LITTLE_ENDIAN).short.toInt()
|
|
88
|
+
}
|
|
89
|
+
"data" -> {
|
|
90
|
+
dataSize = chunkSize
|
|
91
|
+
offset += 8 // Skip chunk ID and size
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
offset += chunkSize + 8 // Move to the next chunk
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (dataSize == 0) {
|
|
100
|
+
Log.e("AudioProcessor", "No data chunk found in WAV file")
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
63
103
|
|
|
64
104
|
val audioData = if (skipWavHeader) {
|
|
65
|
-
fileData.sliceArray(
|
|
105
|
+
fileData.sliceArray(offset until offset + dataSize)
|
|
66
106
|
} else {
|
|
67
107
|
fileData
|
|
68
108
|
}
|
|
@@ -158,8 +198,8 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
158
198
|
val rms = features.rms
|
|
159
199
|
val silent = rms < 0.01
|
|
160
200
|
val dB = if (featureOptions["dB"] == true) 20 * log10(rms.toDouble()).toFloat() else 0f
|
|
161
|
-
minAmplitude = min(minAmplitude,
|
|
162
|
-
maxAmplitude = max(maxAmplitude,
|
|
201
|
+
minAmplitude = min(minAmplitude, localMinAmplitude)
|
|
202
|
+
maxAmplitude = max(maxAmplitude, localMaxAmplitude)
|
|
163
203
|
|
|
164
204
|
val bytesPerSample = bitDepth / 8
|
|
165
205
|
val startPosition = start * bytesPerSample * config.channels
|
|
@@ -330,6 +330,7 @@ class AudioRecorderManager(
|
|
|
330
330
|
// Create result bundle
|
|
331
331
|
val result = bundleOf(
|
|
332
332
|
"fileUri" to audioFile?.toURI().toString(),
|
|
333
|
+
"filename" to audioFile?.name,
|
|
333
334
|
"durationMs" to duration,
|
|
334
335
|
"channels" to recordingConfig.channels,
|
|
335
336
|
"bitDepth" to when (recordingConfig.encoding) {
|
|
@@ -9,5 +9,10 @@ object Constants {
|
|
|
9
9
|
const val DEFAULT_INTERVAL = 1000L
|
|
10
10
|
const val MIN_INTERVAL = 100L // Minimum interval in ms for emitting audio data
|
|
11
11
|
const val WAV_HEADER_SIZE = 44
|
|
12
|
+
const val RIFF_HEADER = 0x52494646 // "RIFF"
|
|
13
|
+
const val WAVE_HEADER = 0x57415645 // "WAVE"
|
|
14
|
+
const val FMT_CHUNK_ID = 0x666d7420 // "fmt "
|
|
15
|
+
const val DATA_CHUNK_ID = 0x64617461 // "data"
|
|
16
|
+
const val INFO_CHUNK_ID = 0x494E464F // "info"
|
|
12
17
|
const val TAG = "AudioRecorderModule"
|
|
13
18
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package net.siteed.audiostream
|
|
2
2
|
|
|
3
|
+
import android.Manifest
|
|
3
4
|
import android.os.Build
|
|
4
5
|
import android.os.Bundle
|
|
5
6
|
import android.util.Log
|
|
@@ -7,6 +8,7 @@ import androidx.annotation.RequiresApi
|
|
|
7
8
|
import expo.modules.kotlin.Promise
|
|
8
9
|
import expo.modules.kotlin.modules.Module
|
|
9
10
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
11
|
+
import expo.modules.interfaces.permissions.Permissions
|
|
10
12
|
|
|
11
13
|
class ExpoAudioStreamModule() : Module(), EventSender {
|
|
12
14
|
private lateinit var audioRecorderManager: AudioRecorderManager
|
|
@@ -44,18 +46,17 @@ class ExpoAudioStreamModule() : Module(), EventSender {
|
|
|
44
46
|
audioRecorderManager.pauseRecording(promise)
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
|
|
48
49
|
AsyncFunction("extractAudioAnalysis") { options: Map<String, Any>, promise: Promise ->
|
|
49
50
|
val fileUri = options["fileUri"] as? String
|
|
50
51
|
val pointsPerSecond = (options["pointsPerSecond"] as? Double) ?: 20.0
|
|
51
|
-
val algorithm = options["algorithm"] as? String ?: "
|
|
52
|
+
val algorithm = options["algorithm"] as? String ?: "peak"
|
|
52
53
|
val featuresMap = options["features"] as? Map<*, *>
|
|
53
54
|
val features = featuresMap?.filterKeys { it is String }
|
|
54
55
|
?.filterValues { it is Boolean }
|
|
55
56
|
?.mapKeys { it.key as String }
|
|
56
57
|
?.mapValues { it.value as Boolean }
|
|
57
58
|
?: emptyMap()
|
|
58
|
-
val skipWavHeader = (options["skipWavHeader"] as? Boolean) ?:
|
|
59
|
+
val skipWavHeader = (options["skipWavHeader"] as? Boolean) ?: true
|
|
59
60
|
|
|
60
61
|
if (fileUri == null) {
|
|
61
62
|
promise.reject("INVALID_ARGUMENTS", "fileUri is required", null)
|
|
@@ -94,6 +95,14 @@ class ExpoAudioStreamModule() : Module(), EventSender {
|
|
|
94
95
|
AsyncFunction("stopRecording") { promise: Promise ->
|
|
95
96
|
audioRecorderManager.stopRecording(promise)
|
|
96
97
|
}
|
|
98
|
+
|
|
99
|
+
AsyncFunction("requestPermissionsAsync") { promise: Promise ->
|
|
100
|
+
Permissions.askForPermissionsWithPermissionsManager(appContext.permissions, promise, Manifest.permission.RECORD_AUDIO)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
AsyncFunction("getPermissionsAsync") { promise: Promise ->
|
|
104
|
+
Permissions.getPermissionsWithPermissionsManager(appContext.permissions, promise, Manifest.permission.RECORD_AUDIO)
|
|
105
|
+
}
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
private fun initializeManager() {
|
package/build/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { extractAudioAnalysis } from
|
|
3
|
-
import { AudioRecorderProvider, useSharedAudioRecorder, } from
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export
|
|
2
|
+
import { extractAudioAnalysis } from './AudioAnalysis/extractAudioAnalysis';
|
|
3
|
+
import { AudioRecorderProvider, useSharedAudioRecorder, } from './AudioRecorder.provider';
|
|
4
|
+
import ExpoAudioStreamModule from './ExpoAudioStreamModule';
|
|
5
|
+
import { useAudioRecorder } from './useAudioRecorder';
|
|
6
|
+
export * from './utils/convertPCMToFloat32';
|
|
7
|
+
export * from './utils/getWavFileInfo';
|
|
8
|
+
export * from './utils/writeWavHeader';
|
|
9
|
+
export { AudioRecorderProvider, ExpoAudioStreamModule, extractAudioAnalysis, useAudioRecorder, useSharedAudioRecorder, };
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
package/ios/AudioProcessor.swift
CHANGED
|
@@ -202,12 +202,14 @@ public class AudioProcessor {
|
|
|
202
202
|
updateSegmentData(channelData: channelData, index: i, sumSquares: &sumSquares, zeroCrossings: &zeroCrossings, prevValue: &prevValue, localMinAmplitude: &localMinAmplitude, localMaxAmplitude: &localMaxAmplitude, segmentData: &segmentData)
|
|
203
203
|
|
|
204
204
|
if (i + 1) % pointInterval == 0 || i == length - 1 {
|
|
205
|
-
|
|
205
|
+
var features = computeFeatures(segmentData: segmentData, sampleRate: sampleRate, sumSquares: sumSquares, zeroCrossings: zeroCrossings, segmentLength: (i % pointInterval) + 1, featureOptions: featureOptions)
|
|
206
|
+
features.minAmplitude = localMinAmplitude
|
|
207
|
+
features.maxAmplitude = localMaxAmplitude
|
|
206
208
|
let rms = features.rms
|
|
207
209
|
let silent = rms < 0.01
|
|
208
210
|
let dB = featureOptions["dB"] == true ? 20 * log10(rms) : 0
|
|
209
|
-
minAmplitude = min(minAmplitude,
|
|
210
|
-
maxAmplitude = max(maxAmplitude,
|
|
211
|
+
minAmplitude = min(minAmplitude, localMinAmplitude)
|
|
212
|
+
maxAmplitude = max(maxAmplitude, localMaxAmplitude)
|
|
211
213
|
|
|
212
214
|
let segmentSize = segmentData.count
|
|
213
215
|
let segmentDuration = Float(segmentSize) / sampleRate
|
|
@@ -294,8 +296,8 @@ public class AudioProcessor {
|
|
|
294
296
|
energy: energy,
|
|
295
297
|
mfcc: mfcc,
|
|
296
298
|
rms: rms,
|
|
297
|
-
minAmplitude: 0,
|
|
298
|
-
maxAmplitude: 0,
|
|
299
|
+
minAmplitude: 0, // computed before and will be overwritten
|
|
300
|
+
maxAmplitude: 0, // computed before and will be overwritten
|
|
299
301
|
zcr: zcr,
|
|
300
302
|
spectralCentroid: spectralCentroid,
|
|
301
303
|
spectralFlatness: spectralFlatness,
|
|
@@ -197,6 +197,7 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
197
197
|
// Convert RecordingResult to a dictionary
|
|
198
198
|
let resultDict: [String: Any] = [
|
|
199
199
|
"fileUri": recordingResult.fileUri,
|
|
200
|
+
"filename": recordingResult.filename,
|
|
200
201
|
"durationMs": recordingResult.duration,
|
|
201
202
|
"size": recordingResult.size,
|
|
202
203
|
"channels": recordingResult.channels,
|
|
@@ -224,6 +225,41 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
|
|
|
224
225
|
Function("clearAudioFiles") {
|
|
225
226
|
clearAudioFiles()
|
|
226
227
|
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
/// Requests audio recording permissions.
|
|
231
|
+
///
|
|
232
|
+
/// - Parameters:
|
|
233
|
+
/// - promise: A promise to resolve with the permission status or reject with an error.
|
|
234
|
+
/// - Returns: Promise to be resolved with the permission status.
|
|
235
|
+
AsyncFunction("requestPermissionsAsync") { (promise: Promise) in
|
|
236
|
+
AVAudioSession.sharedInstance().requestRecordPermission { granted in
|
|
237
|
+
if granted {
|
|
238
|
+
promise.resolve(["status": "granted"])
|
|
239
|
+
} else {
|
|
240
|
+
promise.resolve(["status": "denied"])
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Gets the current audio recording permissions.
|
|
246
|
+
///
|
|
247
|
+
/// - Parameters:
|
|
248
|
+
/// - promise: A promise to resolve with the permission status or reject with an error.
|
|
249
|
+
/// - Returns: Promise to be resolved with the permission status.
|
|
250
|
+
AsyncFunction("getPermissionsAsync") { (promise: Promise) in
|
|
251
|
+
let permissionStatus = AVAudioSession.sharedInstance().recordPermission
|
|
252
|
+
switch permissionStatus {
|
|
253
|
+
case .granted:
|
|
254
|
+
promise.resolve(["status": "granted"])
|
|
255
|
+
case .denied:
|
|
256
|
+
promise.resolve(["status": "denied"])
|
|
257
|
+
case .undetermined:
|
|
258
|
+
promise.resolve(["status": "undetermined"])
|
|
259
|
+
@unknown default:
|
|
260
|
+
promise.reject("UNKNOWN_ERROR", "Unknown permission status")
|
|
261
|
+
}
|
|
262
|
+
}
|
|
227
263
|
}
|
|
228
264
|
|
|
229
265
|
/// Handles the reception of audio data from the AudioStreamManager.
|
package/package.json
CHANGED
|
@@ -1,67 +1,97 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
2
|
+
"name": "@siteed/expo-audio-stream",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "stream audio crossplatform",
|
|
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#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/deeeed/expo-audio-stream.git",
|
|
13
|
+
"directory": "packages/expo-audio-stream"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/deeeed/expo-audio-stream/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react-native",
|
|
20
|
+
"expo",
|
|
21
|
+
"expo-audio-stream",
|
|
22
|
+
"ExpoAudioStream"
|
|
23
|
+
],
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"android",
|
|
27
|
+
"ios",
|
|
28
|
+
"cpp",
|
|
29
|
+
"generated",
|
|
30
|
+
"README.md",
|
|
31
|
+
"package.json",
|
|
32
|
+
"*.podspec",
|
|
33
|
+
"!ios/build",
|
|
34
|
+
"!android/build",
|
|
35
|
+
"!android/gradle",
|
|
36
|
+
"!android/gradlew",
|
|
37
|
+
"!android/gradlew.bat",
|
|
38
|
+
"!android/local.properties",
|
|
39
|
+
"!**/__tests__",
|
|
40
|
+
"!**/__fixtures__",
|
|
41
|
+
"!**/__mocks__",
|
|
42
|
+
"!**/.*"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "expo-module build",
|
|
46
|
+
"clean": "expo-module clean",
|
|
47
|
+
"lint": "expo-module lint",
|
|
48
|
+
"test": "expo-module test",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"docgen": "typedoc src/index.ts --plugin typedoc-plugin-markdown --readme none --out ../../documentation_site/docs/api-reference/API",
|
|
51
|
+
"prepare": "expo-module prepare",
|
|
52
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
53
|
+
"expo-module": "expo-module",
|
|
54
|
+
"open:ios": "open -a \"Xcode\" ../../apps/playground/ios",
|
|
55
|
+
"open:android": "open -a \"Android Studio\" ../../apps/playground/android",
|
|
56
|
+
"size": "bundle-size && size-limit",
|
|
57
|
+
"release": "./publish.sh"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@expo/config-plugins": "^7.9.1",
|
|
61
|
+
"@size-limit/preset-big-lib": "^11.1.4",
|
|
62
|
+
"@types/jest": "^29.5.12",
|
|
63
|
+
"@types/node": "^20.12.7",
|
|
64
|
+
"@types/react": "^18.0.25",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
|
66
|
+
"@typescript-eslint/parser": "^7.7.0",
|
|
67
|
+
"bundle-size": "^1.1.5",
|
|
68
|
+
"eslint": "^8.56.0",
|
|
69
|
+
"eslint-config-prettier": "^9.1.0",
|
|
70
|
+
"eslint-config-universe": "^12.0.0",
|
|
71
|
+
"eslint-plugin-import": "^2.29.1",
|
|
72
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
73
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
74
|
+
"eslint-plugin-react": "^7.34.1",
|
|
75
|
+
"expo-module-scripts": "^3.5.2",
|
|
76
|
+
"expo-modules-core": "^1.12.19",
|
|
77
|
+
"jest": "^29.7.0",
|
|
78
|
+
"prettier": "^3.2.5",
|
|
79
|
+
"react-native": "^0.74.4",
|
|
80
|
+
"size-limit": "^11.1.4",
|
|
81
|
+
"typedoc": "^0.26.5",
|
|
82
|
+
"typedoc-plugin-markdown": "^4.2.3",
|
|
83
|
+
"typescript": "^5.5.4"
|
|
84
|
+
},
|
|
85
|
+
"peerDependencies": {
|
|
86
|
+
"expo": "*",
|
|
87
|
+
"react": "*",
|
|
88
|
+
"react-native": "*"
|
|
89
|
+
},
|
|
90
|
+
"publishConfig": {
|
|
91
|
+
"access": "public",
|
|
92
|
+
"registry": "https://registry.npmjs.org"
|
|
93
|
+
},
|
|
94
|
+
"dependencies": {
|
|
95
|
+
"@siteed/react-native-logger": "^0.9.3"
|
|
96
|
+
}
|
|
67
97
|
}
|