@siteed/expo-audio-stream 0.1.0 → 0.2.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.
@@ -9,15 +9,44 @@ import Foundation
9
9
  import AVFoundation
10
10
 
11
11
  struct RecordingSettings {
12
- var sampleRate: Double = 48000.0
12
+ var sampleRate: Double
13
13
  var numberOfChannels: Int = 1
14
14
  var bitDepth: Int = 16
15
15
  }
16
16
 
17
+ // Helper to convert to little-endian byte array
18
+ extension UInt32 {
19
+ var littleEndianBytes: [UInt8] {
20
+ let value = self.littleEndian
21
+ return [UInt8(value & 0xff), UInt8((value >> 8) & 0xff), UInt8((value >> 16) & 0xff), UInt8((value >> 24) & 0xff)]
22
+ }
23
+ }
24
+
25
+ extension UInt16 {
26
+ var littleEndianBytes: [UInt8] {
27
+ let value = self.littleEndian
28
+ return [UInt8(value & 0xff), UInt8((value >> 8) & 0xff)]
29
+ }
30
+ }
31
+
32
+
33
+ struct RecordingResult {
34
+ var fileUri: String
35
+ var mimeType: String
36
+ var duration: Int64
37
+ var size: Int64
38
+ }
39
+
17
40
  protocol AudioStreamManagerDelegate: AnyObject {
18
41
  func audioStreamManager(_ manager: AudioStreamManager, didReceiveAudioData data: Data, recordingTime: TimeInterval, totalDataSize: Int64)
19
42
  }
20
43
 
44
+ enum AudioStreamError: Error {
45
+ case audioSessionSetupFailed(String)
46
+ case fileCreationFailed(URL)
47
+ case audioProcessingError(String)
48
+ }
49
+
21
50
  class AudioStreamManager: NSObject {
22
51
  private let audioEngine = AVAudioEngine()
23
52
  private var inputNode: AVAudioInputNode {
@@ -33,7 +62,9 @@ class AudioStreamManager: NSObject {
33
62
  private var isPaused = false
34
63
  private var pausedDuration = 0
35
64
  private var fileManager = FileManager.default
65
+ private var recordingSettings: RecordingSettings?
36
66
  internal var recordingUUID: UUID?
67
+ internal var mimeType: String = "audio/wav"
37
68
  weak var delegate: AudioStreamManagerDelegate? // Define the delegate here
38
69
 
39
70
  override init() {
@@ -52,85 +83,205 @@ class AudioStreamManager: NSObject {
52
83
  }
53
84
  }
54
85
 
86
+ @objc func handleAudioSessionInterruption(notification: Notification) {
87
+ guard let info = notification.userInfo,
88
+ let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
89
+ let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
90
+ return
91
+ }
92
+
93
+ if type == .began {
94
+ // Pause your audio recording
95
+ } else if type == .ended {
96
+ if let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt {
97
+ let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
98
+ if options.contains(.shouldResume) {
99
+ // Resume your audio recording
100
+ try? AVAudioSession.sharedInstance().setActive(true)
101
+ }
102
+ }
103
+ }
104
+ }
105
+
55
106
  private func createRecordingFile() -> URL? {
56
107
  let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
57
108
  recordingUUID = UUID()
58
- let fileName = "\(recordingUUID!.uuidString).pcm"
109
+ let fileName = "\(recordingUUID!.uuidString).wav"
59
110
  let fileURL = documentsDirectory.appendingPathComponent(fileName)
60
- fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil)
61
- print("Recording file created at:", fileURL.path)
62
-
111
+
112
+ if fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) {
113
+ do {
114
+ let fileHandle = try FileHandle(forWritingTo: fileURL)
115
+ let wavHeader = createWavHeader(dataSize: 0) // Initially set data size to 0
116
+ fileHandle.write(wavHeader)
117
+ fileHandle.closeFile()
118
+ print("Recording file with header created at:", fileURL.path)
119
+ } catch {
120
+ print("Failed to write WAV header: \(error.localizedDescription)")
121
+ return nil
122
+ }
123
+ }
63
124
  return fileURL
64
125
  }
65
126
 
127
+ private func createWavHeader(dataSize: Int) -> Data {
128
+ var header = Data()
129
+
130
+ let sampleRate = UInt32(recordingSettings!.sampleRate)
131
+ let channels = UInt32(recordingSettings!.numberOfChannels)
132
+ let bitDepth = UInt32(recordingSettings!.bitDepth)
133
+
134
+ // Calculate byteRate
135
+ let byteRate = sampleRate * channels * (bitDepth / 8)
136
+
137
+ // "RIFF" chunk descriptor
138
+ header.append(contentsOf: "RIFF".utf8)
139
+ header.append(contentsOf: UInt32(36 + dataSize).littleEndianBytes)
140
+ header.append(contentsOf: "WAVE".utf8)
141
+
142
+ // "fmt " sub-chunk
143
+ header.append(contentsOf: "fmt ".utf8)
144
+ header.append(contentsOf: UInt32(16).littleEndianBytes) // PCM format requires 16 bytes for the fmt sub-chunk
145
+ header.append(contentsOf: UInt16(1).littleEndianBytes) // Audio format 1 for PCM
146
+ header.append(contentsOf: UInt16(channels).littleEndianBytes)
147
+ header.append(contentsOf: sampleRate.littleEndianBytes)
148
+ header.append(contentsOf: byteRate.littleEndianBytes) // byteRate
149
+ header.append(contentsOf: UInt16(channels * (bitDepth / 8)).littleEndianBytes) // blockAlign
150
+ header.append(contentsOf: UInt16(bitDepth).littleEndianBytes) // bits per sample
151
+
152
+ // "data" sub-chunk
153
+ header.append(contentsOf: "data".utf8)
154
+ header.append(contentsOf: UInt32(dataSize).littleEndianBytes) // Sub-chunk data size
155
+
156
+ return header
157
+ }
158
+
159
+
66
160
  func getStatus() -> [String: Any] {
67
161
  let currentTime = Date()
68
162
  let totalRecordedTime = startTime != nil ? Int(currentTime.timeIntervalSince(startTime!)) - pausedDuration : 0
69
163
  return [
70
- "duration": totalRecordedTime,
164
+ "duration": totalRecordedTime * 1000,
71
165
  "isRecording": isRecording,
72
166
  "isPaused": isPaused,
167
+ "mimeType": mimeType,
73
168
  "size": totalDataSize,
74
169
  "interval": emissionInterval
75
170
  ]
76
171
  }
77
172
 
78
- func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) {
79
- guard !isRecording else { return }
80
-
81
- emissionInterval = max(100.0, Double(intervalMilliseconds)) / 1000.0 // Convert ms to seconds, ensure minimum of 100 ms
82
- lastEmissionTime = Date() // Reset last emission time
173
+ func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) -> String? {
174
+ guard !isRecording else {
175
+ print("Debug: Recording is already in progress.")
176
+ return nil
177
+ }
178
+
179
+ emissionInterval = max(100.0, Double(intervalMilliseconds)) / 1000.0
180
+ lastEmissionTime = Date()
181
+ recordingSettings = settings
83
182
 
84
- // Configure audio session for the desired sample rate and channel count
85
183
  let session = AVAudioSession.sharedInstance()
86
184
  do {
185
+ print("Debug: Configuring audio session with sample rate: \(settings.sampleRate) Hz")
87
186
  try session.setPreferredSampleRate(settings.sampleRate)
88
187
  try session.setPreferredIOBufferDuration(1024 / settings.sampleRate)
89
188
  try session.setCategory(.playAndRecord)
90
189
  try session.setActive(true)
190
+ print("Debug: Audio session activated successfully.")
91
191
  } catch {
92
- print("Failed to set up audio session: \(error)")
93
- return
192
+ print("Error: Failed to set up audio session with preferred settings: \(error.localizedDescription)")
193
+ return nil
194
+ }
195
+
196
+ NotificationCenter.default.addObserver(self, selector: #selector(handleAudioSessionInterruption), name: AVAudioSession.interruptionNotification, object: nil)
197
+
198
+ guard let channelLayout = AVAudioChannelLayout(layoutTag: settings.numberOfChannels == 1 ? kAudioChannelLayoutTag_Mono : kAudioChannelLayoutTag_Stereo) else {
199
+ print("Error: Failed to create channel layout.")
200
+ return nil
94
201
  }
95
-
96
- // Create an audio format with specified or default settings
97
- let channelLayout = AVAudioChannelLayout(layoutTag: settings.numberOfChannels == 1 ? kAudioChannelLayoutTag_Mono : kAudioChannelLayoutTag_Stereo) ?? AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_Stereo)!
98
202
  let errorFormat = AVAudioFormat(standardFormatWithSampleRate: settings.sampleRate, channelLayout: channelLayout)
99
-
100
- // Create an audio format with default settings
101
- let format = audioEngine.inputNode.inputFormat(forBus: 0)
102
-
103
- // Debugging statements
104
- print("Desired Sample Rate:", settings.sampleRate)
105
- print("Channel Layout:", channelLayout.description)
106
- print("Created Audio Format Sample Rate: \(format.sampleRate) channelLayout: \(format.channelLayout) channelCount: \(format.channelCount)")
107
- print("Error Audio Format Sample Rate: \(errorFormat.sampleRate) channel Layout: \(errorFormat.channelLayout) channelCount: \(errorFormat.channelCount)")
108
- print("Hardware Format Sample Rate:", audioEngine.inputNode.inputFormat(forBus: 0).sampleRate)
109
-
110
- // Install tap on the input node and handle audio buffer
203
+
111
204
  audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: errorFormat) { [weak self] (buffer, time) in
112
- guard let self = self, let fileURL = self.recordingFileURL else { return }
205
+ guard let self = self, let fileURL = self.recordingFileURL else {
206
+ print("Error: File URL or self is nil during buffer processing.")
207
+ return
208
+ }
113
209
  self.processAudioBuffer(buffer, fileURL: fileURL)
114
210
  }
115
211
 
116
212
  recordingFileURL = createRecordingFile()
213
+ if recordingFileURL == nil {
214
+ print("Error: Failed to create recording file.")
215
+ return nil
216
+ }
217
+
117
218
  do {
118
219
  startTime = Date()
119
220
  try audioEngine.start()
120
221
  isRecording = true
222
+ print("Debug: Recording started successfully.")
223
+ return recordingFileURL?.absoluteString
121
224
  } catch {
122
- print("Could not start the audio engine: \(error)")
225
+ print("Error: Could not start the audio engine: \(error.localizedDescription)")
123
226
  isRecording = false
227
+ return nil
124
228
  }
125
229
  }
230
+
126
231
 
127
- func stopRecording() {
232
+ func stopRecording() -> RecordingResult? {
128
233
  audioEngine.stop()
129
234
  audioEngine.inputNode.removeTap(onBus: 0)
130
235
  isRecording = false
131
- recordingFileURL = nil // Optionally reset or handle the finalization of the file
132
- print("Recording stopped.")
133
-
236
+
237
+ guard let fileURL = recordingFileURL, let startTime = startTime else {
238
+ print("Recording or file URL is nil.")
239
+ return nil
240
+ }
241
+
242
+ let endTime = Date()
243
+ let duration = Int64(endTime.timeIntervalSince(startTime) * 1000) - Int64(pausedDuration * 1000)
244
+
245
+ // Calculate the total size of audio data written to the file
246
+ let filePath = fileURL.path
247
+ do {
248
+ let fileAttributes = try FileManager.default.attributesOfItem(atPath: filePath)
249
+ let fileSize = fileAttributes[FileAttributeKey.size] as? Int64 ?? 0
250
+
251
+ // Update the WAV header with the correct file size
252
+ updateWavHeader(fileURL: fileURL, totalDataSize: fileSize - 44) // Subtract the header size to get audio data size
253
+
254
+ let result = RecordingResult(fileUri: fileURL.absoluteString, mimeType: mimeType, duration: duration, size: fileSize)
255
+ recordingFileURL = nil // Reset for next recording
256
+ return result
257
+ } catch {
258
+ print("Failed to fetch file attributes: \(error)")
259
+ return nil
260
+ }
261
+ }
262
+
263
+ private func updateWavHeader(fileURL: URL, totalDataSize: Int64) {
264
+ do {
265
+ let fileHandle = try FileHandle(forUpdating: fileURL)
266
+ defer { fileHandle.closeFile() }
267
+
268
+ // Calculate sizes
269
+ let fileSize = totalDataSize + 44 - 8 // Total file size minus 8 bytes for 'RIFF' and size field itself
270
+ let dataSize = totalDataSize // Size of the 'data' sub-chunk
271
+
272
+ // Update RIFF chunk size at offset 4
273
+ fileHandle.seek(toFileOffset: 4)
274
+ let fileSizeBytes = UInt32(fileSize).littleEndianBytes
275
+ fileHandle.write(Data(fileSizeBytes))
276
+
277
+ // Update data chunk size at offset 40
278
+ fileHandle.seek(toFileOffset: 40)
279
+ let dataSizeBytes = UInt32(dataSize).littleEndianBytes
280
+ fileHandle.write(Data(dataSizeBytes))
281
+
282
+ } catch let error {
283
+ print("Error updating WAV header: \(error)")
284
+ }
134
285
  }
135
286
 
136
287
  private func processAudioBuffer(_ buffer: AVAudioPCMBuffer, fileURL: URL) {
@@ -146,18 +297,19 @@ class AudioStreamManager: NSObject {
146
297
  }
147
298
  let data = Data(bytes: bufferData, count: Int(audioData.mDataByteSize))
148
299
 
300
+ print("Writing data size: \(data.count) bytes") // Debug: Check the size of data being written
149
301
  fileHandle.seekToEndOfFile()
150
302
  fileHandle.write(data)
151
303
  fileHandle.closeFile()
152
304
 
153
305
  totalDataSize += Int64(data.count)
154
-
306
+ print("Total data size written: \(totalDataSize) bytes") // Debug: Check total data written
307
+
155
308
  let currentTime = Date()
156
309
  if let lastEmissionTime = lastEmissionTime, currentTime.timeIntervalSince(lastEmissionTime) >= emissionInterval {
157
310
  if let startTime = startTime {
158
311
  let recordingTime = currentTime.timeIntervalSince(startTime)
159
312
  print("Emitting data: Recording time \(recordingTime) seconds, Data size \(totalDataSize) bytes")
160
- print("delegate", self.delegate)
161
313
  self.delegate?.audioStreamManager(self, didReceiveAudioData: data, recordingTime: recordingTime, totalDataSize: totalDataSize)
162
314
  self.lastEmissionTime = currentTime // Update last emission time
163
315
  self.lastEmittedSize = totalDataSize
@@ -1,10 +1,11 @@
1
1
  import ExpoModulesCore
2
+ import AVFoundation
2
3
 
3
4
  let audioDataEvent: String = "AudioData"
4
5
 
5
6
  public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
6
7
  private var streamManager = AudioStreamManager()
7
-
8
+
8
9
  public func definition() -> ModuleDefinition {
9
10
  Name("ExpoAudioStream")
10
11
 
@@ -15,7 +16,7 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
15
16
  print("Setting streamManager delegate")
16
17
  streamManager.delegate = self
17
18
  }
18
-
19
+
19
20
  AsyncFunction("startRecording") { (options: [String: Any], promise: Promise) in
20
21
  self.checkMicrophonePermission { granted in
21
22
  guard granted else {
@@ -24,15 +25,15 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
24
25
  }
25
26
 
26
27
  // Extract settings from provided options, using default values if necessary
27
- let sampleRate = options["sampleRate"] as? Double ?? 48000.0
28
- let numberOfChannels = options["channelConfig"] as? Int ?? 1
29
- let bitDepth = options["audioFormat"] as? Int ?? 16
28
+ let sampleRate = options["sampleRate"] as? Double ?? 16000.0 // it fails if not 48000, why?
29
+ let numberOfChannels = options["channelConfig"] as? Int ?? 1 // Mono channel configuration
30
+ let bitDepth = options["audioFormat"] as? Int ?? 16 // 16bits
30
31
  let interval = options["interval"] as? Int ?? 1000
31
32
 
32
33
  let settings = RecordingSettings(sampleRate: sampleRate, numberOfChannels: numberOfChannels, bitDepth: bitDepth)
33
- self.streamManager.startRecording(settings: settings, intervalMilliseconds: interval)
34
+ let url = self.streamManager.startRecording(settings: settings, intervalMilliseconds: interval)
34
35
 
35
- promise.resolve(nil)
36
+ promise.resolve(url)
36
37
  }
37
38
  }
38
39
 
@@ -41,8 +42,17 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
41
42
  }
42
43
 
43
44
  AsyncFunction("stopRecording") { (promise: Promise) in
44
- self.streamManager.stopRecording()
45
- promise.resolve(nil)
45
+ if let recordingResult = self.streamManager.stopRecording() {
46
+ // Convert RecordingResult to a dictionary
47
+ let resultDict: [String: Any] = [
48
+ "fileUri": recordingResult.fileUri,
49
+ "duration": recordingResult.duration,
50
+ "size": recordingResult.size
51
+ ]
52
+ promise.resolve(resultDict)
53
+ } else {
54
+ promise.reject("ERROR", "Failed to stop recording or no recording in progress.")
55
+ }
46
56
  }
47
57
 
48
58
  AsyncFunction("listAudioFiles") { (promise: Promise) in
@@ -56,7 +66,6 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
56
66
  }
57
67
 
58
68
  func audioStreamManager(_ manager: AudioStreamManager, didReceiveAudioData data: Data, recordingTime: TimeInterval, totalDataSize: Int64) {
59
- print("audioStreamManager debug sending data")
60
69
  guard let fileURL = manager.recordingFileURL else { return }
61
70
  let encodedData = data.base64EncodedString()
62
71
 
@@ -71,13 +80,13 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
71
80
  "encoded": encodedData,
72
81
  "deltaSize": deltaSize,
73
82
  "totalSize": fileSize,
83
+ "mimeType": manager.mimeType,
74
84
  "streamUuid": manager.recordingUUID?.uuidString ?? UUID().uuidString
75
85
  ]
76
86
 
77
87
  // Update the last emitted size for the next calculation
78
88
  manager.lastEmittedSize += Int64(deltaSize)
79
89
 
80
- print("Sending audio data", eventBody)
81
90
  // Emit the event to JavaScript
82
91
  sendEvent(audioDataEvent, eventBody)
83
92
  }
@@ -122,7 +131,7 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
122
131
 
123
132
  do {
124
133
  let files = try FileManager.default.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil)
125
- let audioFiles = files.filter { $0.pathExtension == "pcm" }.map { $0.lastPathComponent }
134
+ let audioFiles = files.filter { $0.pathExtension == "wav" }.map { $0.lastPathComponent }
126
135
  return audioFiles
127
136
  } catch {
128
137
  print("Error listing audio files:", error.localizedDescription)
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
8
8
  "author": "Arthur Breton <abreton@siteed.net> (https://github.com/deeeed)",
9
9
  "homepage": "https://github.com/deeeed/expo-audio-stream#readme",
10
- "repository": "https://github.com/deeeed/expo-audio-stream",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/deeeed/expo-audio-stream.git"
13
+ },
11
14
  "bugs": {
12
15
  "url": "https://github.com/deeeed/expo-audio-stream/issues"
13
16
  },
@@ -31,11 +34,13 @@
31
34
  },
32
35
  "dependencies": {
33
36
  "base-64": "^1.0.0",
37
+ "debug": "^4.3.4",
34
38
  "expo-file-system": "^16.0.9"
35
39
  },
36
40
  "devDependencies": {
37
41
  "@expo/config-plugins": "^7.9.1",
38
42
  "@release-it/conventional-changelog": "^8.0.1",
43
+ "@types/debug": "^4.1.12",
39
44
  "@types/react": "^18.0.25",
40
45
  "@typescript-eslint/eslint-plugin": "^7.7.0",
41
46
  "@typescript-eslint/parser": "^7.7.0",
@@ -56,6 +61,7 @@
56
61
  "react-native": "*"
57
62
  },
58
63
  "publishConfig": {
59
- "access": "public"
64
+ "access": "public",
65
+ "registry": "https://registry.npmjs.org"
60
66
  }
61
67
  }
@@ -1,5 +1,5 @@
1
1
  import { ConfigPlugin } from "@expo/config-plugins";
2
2
  declare const _default: ConfigPlugin<{
3
- microphonePermission: string | false;
3
+ microphonePermission: string | false | undefined;
4
4
  }>;
5
5
  export default _default;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const config_plugins_1 = require("@expo/config-plugins");
4
- const pkg = require('../../package.json');
5
- const MICROPHONE_USAGE = 'Allow $(PRODUCT_NAME) to access your microphone';
4
+ const pkg = require("../../package.json");
5
+ const MICROPHONE_USAGE = "Allow $(PRODUCT_NAME) to access your microphone";
6
6
  const withRecordingPermission = (config, { microphonePermission }) => {
7
7
  config_plugins_1.IOSConfig.Permissions.createPermissionsPlugin({
8
8
  NSMicrophoneUsageDescription: MICROPHONE_USAGE,
@@ -10,8 +10,8 @@ const withRecordingPermission = (config, { microphonePermission }) => {
10
10
  NSMicrophoneUsageDescription: microphonePermission,
11
11
  });
12
12
  return config_plugins_1.AndroidConfig.Permissions.withPermissions(config, [
13
- microphonePermission !== false && 'android.permission.RECORD_AUDIO',
14
- 'android.permission.MODIFY_AUDIO_SETTINGS',
13
+ microphonePermission !== false && "android.permission.RECORD_AUDIO",
14
+ "android.permission.MODIFY_AUDIO_SETTINGS",
15
15
  ].filter(Boolean));
16
16
  };
17
17
  exports.default = (0, config_plugins_1.createRunOncePlugin)(withRecordingPermission, pkg.name, pkg.version);
@@ -2,13 +2,15 @@ import {
2
2
  AndroidConfig,
3
3
  ConfigPlugin,
4
4
  IOSConfig,
5
- createRunOncePlugin
5
+ createRunOncePlugin,
6
6
  } from "@expo/config-plugins";
7
7
 
8
- const pkg = require('../../package.json');
9
- const MICROPHONE_USAGE = 'Allow $(PRODUCT_NAME) to access your microphone';
8
+ const pkg = require("../../package.json");
9
+ const MICROPHONE_USAGE = "Allow $(PRODUCT_NAME) to access your microphone";
10
10
 
11
- const withRecordingPermission: ConfigPlugin<{ microphonePermission: string | false }> = (config, { microphonePermission }) => {
11
+ const withRecordingPermission: ConfigPlugin<{
12
+ microphonePermission: string | false | undefined;
13
+ }> = (config, { microphonePermission }) => {
12
14
  IOSConfig.Permissions.createPermissionsPlugin({
13
15
  NSMicrophoneUsageDescription: MICROPHONE_USAGE,
14
16
  })(config, {
@@ -18,10 +20,14 @@ const withRecordingPermission: ConfigPlugin<{ microphonePermission: string | fal
18
20
  return AndroidConfig.Permissions.withPermissions(
19
21
  config,
20
22
  [
21
- microphonePermission !== false && 'android.permission.RECORD_AUDIO',
22
- 'android.permission.MODIFY_AUDIO_SETTINGS',
23
- ].filter(Boolean) as string[]
23
+ microphonePermission !== false && "android.permission.RECORD_AUDIO",
24
+ "android.permission.MODIFY_AUDIO_SETTINGS",
25
+ ].filter(Boolean) as string[],
24
26
  );
25
27
  };
26
28
 
27
- export default createRunOncePlugin(withRecordingPermission, pkg.name, pkg.version);
29
+ export default createRunOncePlugin(
30
+ withRecordingPermission,
31
+ pkg.name,
32
+ pkg.version,
33
+ );
@@ -1,12 +1,20 @@
1
1
  export interface AudioEventPayload {
2
- encoded?: string,
3
- buffer?: Blob,
4
- fileUri: string,
5
- from: number,
6
- deltaSize: number,
7
- totalSize: number,
8
- streamUuid: string,
9
- };
2
+ encoded?: string;
3
+ buffer?: Blob;
4
+ fileUri: string;
5
+ from: number;
6
+ deltaSize: number;
7
+ totalSize: number;
8
+ mimeType: string;
9
+ streamUuid: string;
10
+ }
11
+
12
+ export interface AudioStreamResult {
13
+ fileUri: string;
14
+ duration: number;
15
+ size: number;
16
+ mimeType: string;
17
+ }
10
18
 
11
19
  export interface AudioStreamStatus {
12
20
  isRecording: boolean;
@@ -14,12 +22,13 @@ export interface AudioStreamStatus {
14
22
  duration: number;
15
23
  size: number;
16
24
  interval: number;
25
+ mimeType: string;
17
26
  }
18
27
 
19
28
  export interface RecordingOptions {
20
29
  // TODO align Android and IOS options
21
- sampleRate?: number;
22
- channelConfig?: number; // numberOfChannel
23
- audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)
30
+ // sampleRate?: number;
31
+ // channelConfig?: number; // numberOfChannel
32
+ // audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)
24
33
  interval?: number;
25
34
  }
@@ -1,5 +1,5 @@
1
- import { requireNativeModule } from 'expo-modules-core';
1
+ import { requireNativeModule } from "expo-modules-core";
2
2
 
3
3
  // It loads the native module object from the JSI or falls back to
4
4
  // the bridge module (from NativeModulesProxy) if the remote debugger is on.
5
- export default requireNativeModule('ExpoAudioStream');
5
+ export default requireNativeModule("ExpoAudioStream");