@siteed/expo-audio-stream 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -222
- package/build/index.d.ts +11 -15
- package/build/index.js +44 -14
- package/package.json +49 -110
- package/src/index.ts +18 -32
- package/CHANGELOG.md +0 -206
- package/android/build.gradle +0 -105
- package/android/src/main/AndroidManifest.xml +0 -27
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -166
- package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -131
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -103
- package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -435
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -2235
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -1437
- package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -152
- package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +0 -1099
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -21
- package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -739
- package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
- package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
- package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
- package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
- package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
- package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
- package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
- package/android/src/main/res/drawable/ic_microphone.xml +0 -13
- package/android/src/main/res/drawable/ic_pause.xml +0 -10
- package/android/src/main/res/drawable/ic_play.xml +0 -10
- package/android/src/main/res/drawable/ic_stop.xml +0 -10
- package/android/src/main/res/layout/notification_recording.xml +0 -37
- package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
- package/app.plugin.js +0 -1
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -179
- 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 -68
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
- package/build/AudioAnalysis/extractAudioAnalysis.js +0 -203
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
- package/build/AudioAnalysis/extractAudioData.d.ts +0 -3
- package/build/AudioAnalysis/extractAudioData.d.ts.map +0 -1
- package/build/AudioAnalysis/extractAudioData.js +0 -5
- package/build/AudioAnalysis/extractAudioData.js.map +0 -1
- package/build/AudioAnalysis/extractMelSpectrogram.d.ts +0 -14
- package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
- package/build/AudioAnalysis/extractMelSpectrogram.js +0 -85
- package/build/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
- package/build/AudioAnalysis/extractPreview.d.ts +0 -11
- package/build/AudioAnalysis/extractPreview.d.ts.map +0 -1
- package/build/AudioAnalysis/extractPreview.js +0 -25
- package/build/AudioAnalysis/extractPreview.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 -11
- package/build/AudioAnalysis/extractWaveform.js.map +0 -1
- package/build/AudioRecorder.provider.d.ts +0 -11
- package/build/AudioRecorder.provider.d.ts.map +0 -1
- package/build/AudioRecorder.provider.js +0 -37
- 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 -532
- 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 -59
- package/build/ExpoAudioStream.web.d.ts.map +0 -1
- package/build/ExpoAudioStream.web.js +0 -285
- 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 -693
- package/build/ExpoAudioStreamModule.js.map +0 -1
- package/build/WebRecorder.web.d.ts +0 -119
- package/build/WebRecorder.web.d.ts.map +0 -1
- package/build/WebRecorder.web.js +0 -436
- 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 -26
- package/build/events.d.ts.map +0 -1
- package/build/events.js +0 -21
- package/build/events.js.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/trimAudio.d.ts +0 -25
- package/build/trimAudio.d.ts.map +0 -1
- package/build/trimAudio.js +0 -67
- package/build/trimAudio.js.map +0 -1
- package/build/useAudioRecorder.d.ts +0 -21
- package/build/useAudioRecorder.d.ts.map +0 -1
- package/build/useAudioRecorder.js +0 -427
- package/build/useAudioRecorder.js.map +0 -1
- package/build/utils/BlobFix.d.ts +0 -9
- package/build/utils/BlobFix.d.ts.map +0 -1
- package/build/utils/BlobFix.js +0 -498
- package/build/utils/BlobFix.js.map +0 -1
- package/build/utils/audioProcessing.d.ts +0 -24
- package/build/utils/audioProcessing.d.ts.map +0 -1
- package/build/utils/audioProcessing.js +0 -133
- package/build/utils/audioProcessing.js.map +0 -1
- package/build/utils/concatenateBuffers.d.ts +0 -8
- package/build/utils/concatenateBuffers.d.ts.map +0 -1
- package/build/utils/concatenateBuffers.js +0 -21
- package/build/utils/concatenateBuffers.js.map +0 -1
- package/build/utils/convertPCMToFloat32.d.ts +0 -13
- package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
- package/build/utils/convertPCMToFloat32.js +0 -120
- 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 -26
- package/build/utils/getWavFileInfo.d.ts.map +0 -1
- package/build/utils/getWavFileInfo.js +0 -92
- package/build/utils/getWavFileInfo.js.map +0 -1
- package/build/utils/writeWavHeader.d.ts +0 -49
- package/build/utils/writeWavHeader.d.ts.map +0 -1
- package/build/utils/writeWavHeader.js +0 -91
- 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 -828
- 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 -157
- package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
- package/expo-module.config.json +0 -9
- package/ios/AudioAnalysisData.swift +0 -74
- package/ios/AudioNotificationManager.swift +0 -135
- package/ios/AudioProcessingHelpers.swift +0 -743
- package/ios/AudioProcessor.swift +0 -1313
- package/ios/AudioStreamError.swift +0 -7
- package/ios/AudioStreamManager.swift +0 -1708
- package/ios/AudioStreamManagerDelegate.swift +0 -16
- package/ios/DataPoint.swift +0 -54
- package/ios/DecodingConfig.swift +0 -47
- package/ios/ExpoAudioStream.podspec +0 -27
- package/ios/ExpoAudioStreamModule.swift +0 -805
- package/ios/FFT.swift +0 -62
- package/ios/Features.swift +0 -95
- package/ios/Logger.swift +0 -7
- package/ios/NotificationExtension.swift +0 -15
- package/ios/RecordingResult.swift +0 -22
- package/ios/RecordingSettings.swift +0 -265
- package/ios/WaveformExtractor.swift +0 -105
- package/plugin/build/index.d.ts +0 -21
- package/plugin/build/index.js +0 -191
- package/plugin/src/index.ts +0 -278
- package/plugin/tsconfig.json +0 -10
- package/plugin/tsconfig.tsbuildinfo +0 -1
- package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -202
- package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -333
- package/src/AudioAnalysis/extractAudioData.ts +0 -6
- package/src/AudioAnalysis/extractMelSpectrogram.ts +0 -144
- package/src/AudioAnalysis/extractPreview.ts +0 -34
- package/src/AudioAnalysis/extractWaveform.ts +0 -22
- package/src/AudioRecorder.provider.tsx +0 -54
- package/src/ExpoAudioStream.native.ts +0 -6
- package/src/ExpoAudioStream.types.ts +0 -641
- package/src/ExpoAudioStream.web.ts +0 -359
- package/src/ExpoAudioStreamModule.ts +0 -967
- package/src/WebRecorder.web.ts +0 -580
- package/src/constants.ts +0 -18
- package/src/events.ts +0 -60
- package/src/trimAudio.ts +0 -90
- package/src/useAudioRecorder.tsx +0 -620
- package/src/utils/BlobFix.ts +0 -559
- package/src/utils/audioProcessing.ts +0 -205
- package/src/utils/concatenateBuffers.ts +0 -24
- package/src/utils/convertPCMToFloat32.ts +0 -170
- package/src/utils/encodingToBitDepth.ts +0 -18
- package/src/utils/getWavFileInfo.ts +0 -132
- package/src/utils/writeWavHeader.ts +0 -114
- package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
- package/src/workers/inlineAudioWebWorker.web.tsx +0 -156
package/ios/FFT.swift
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// FFT.swift
|
|
3
|
-
// Pods
|
|
4
|
-
//
|
|
5
|
-
// Created by Arthur Breton on 20/2/2025.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
import Accelerate
|
|
9
|
-
|
|
10
|
-
class FFT {
|
|
11
|
-
private let length: Int
|
|
12
|
-
private var setup: vDSP_DFT_Setup?
|
|
13
|
-
|
|
14
|
-
init(_ length: Int) {
|
|
15
|
-
self.length = length
|
|
16
|
-
self.setup = vDSP_DFT_zop_CreateSetup(
|
|
17
|
-
nil,
|
|
18
|
-
vDSP_Length(length),
|
|
19
|
-
vDSP_DFT_Direction.FORWARD
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
deinit {
|
|
24
|
-
if let setup = setup {
|
|
25
|
-
vDSP_DFT_DestroySetup(setup)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
func realForward(_ data: inout [Float]) {
|
|
30
|
-
var realIn = data
|
|
31
|
-
var imagIn = [Float](repeating: 0.0, count: length)
|
|
32
|
-
var realOut = [Float](repeating: 0.0, count: length)
|
|
33
|
-
var imagOut = [Float](repeating: 0.0, count: length)
|
|
34
|
-
|
|
35
|
-
// Perform FFT
|
|
36
|
-
vDSP_DFT_Execute(setup!,
|
|
37
|
-
&realIn,
|
|
38
|
-
&imagIn,
|
|
39
|
-
&realOut,
|
|
40
|
-
&imagOut)
|
|
41
|
-
|
|
42
|
-
// Ensure data array has enough space for both real and imaginary parts
|
|
43
|
-
if data.count < 2 * length {
|
|
44
|
-
data.append(contentsOf: [Float](repeating: 0.0, count: 2 * length - data.count))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Combine real and imaginary parts
|
|
48
|
-
for i in 0..<length {
|
|
49
|
-
let j = i * 2
|
|
50
|
-
data[j] = realOut[i]
|
|
51
|
-
data[j + 1] = imagOut[i]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
func processSegment(_ segment: [Float]) -> [Float] {
|
|
56
|
-
var fftData = segment.count < length ?
|
|
57
|
-
segment + [Float](repeating: 0, count: length - segment.count) :
|
|
58
|
-
Array(segment.prefix(length))
|
|
59
|
-
realForward(&fftData)
|
|
60
|
-
return fftData
|
|
61
|
-
}
|
|
62
|
-
}
|
package/ios/Features.swift
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Features.swift
|
|
3
|
-
// ExpoAudioStream
|
|
4
|
-
//
|
|
5
|
-
// Created by Arthur Breton on 23/6/2024.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
import Foundation
|
|
9
|
-
|
|
10
|
-
public struct Features {
|
|
11
|
-
var energy: Float
|
|
12
|
-
var mfcc: [Float]
|
|
13
|
-
var rms: Float
|
|
14
|
-
var minAmplitude: Float
|
|
15
|
-
var maxAmplitude: Float
|
|
16
|
-
var zcr: Float
|
|
17
|
-
var spectralCentroid: Float
|
|
18
|
-
var spectralFlatness: Float
|
|
19
|
-
var spectralRollOff: Float?
|
|
20
|
-
var spectralBandwidth: Float?
|
|
21
|
-
var chromagram: [Float]?
|
|
22
|
-
var tempo: Float?
|
|
23
|
-
var hnr: Float?
|
|
24
|
-
var melSpectrogram: [Float]?
|
|
25
|
-
var spectralContrast: [Float]?
|
|
26
|
-
var tonnetz: [Float]?
|
|
27
|
-
var pitch: Float?
|
|
28
|
-
var crc32: UInt32?
|
|
29
|
-
|
|
30
|
-
init(
|
|
31
|
-
energy: Float = 0,
|
|
32
|
-
mfcc: [Float] = [],
|
|
33
|
-
rms: Float = 0,
|
|
34
|
-
minAmplitude: Float = 0,
|
|
35
|
-
maxAmplitude: Float = 0,
|
|
36
|
-
zcr: Float = 0,
|
|
37
|
-
spectralCentroid: Float = 0,
|
|
38
|
-
spectralFlatness: Float = 0,
|
|
39
|
-
spectralRollOff: Float? = nil,
|
|
40
|
-
spectralBandwidth: Float? = nil,
|
|
41
|
-
chromagram: [Float]? = nil,
|
|
42
|
-
tempo: Float? = nil,
|
|
43
|
-
hnr: Float? = nil,
|
|
44
|
-
melSpectrogram: [Float]? = nil,
|
|
45
|
-
spectralContrast: [Float]? = nil,
|
|
46
|
-
tonnetz: [Float]? = nil,
|
|
47
|
-
pitch: Float? = nil,
|
|
48
|
-
crc32: UInt32? = nil
|
|
49
|
-
) {
|
|
50
|
-
self.energy = energy
|
|
51
|
-
self.mfcc = mfcc
|
|
52
|
-
self.rms = rms
|
|
53
|
-
self.minAmplitude = minAmplitude
|
|
54
|
-
self.maxAmplitude = maxAmplitude
|
|
55
|
-
self.zcr = zcr
|
|
56
|
-
self.spectralCentroid = spectralCentroid
|
|
57
|
-
self.spectralFlatness = spectralFlatness
|
|
58
|
-
self.spectralRollOff = spectralRollOff
|
|
59
|
-
self.spectralBandwidth = spectralBandwidth
|
|
60
|
-
self.chromagram = chromagram
|
|
61
|
-
self.tempo = tempo
|
|
62
|
-
self.hnr = hnr
|
|
63
|
-
self.melSpectrogram = melSpectrogram
|
|
64
|
-
self.spectralContrast = spectralContrast
|
|
65
|
-
self.tonnetz = tonnetz
|
|
66
|
-
self.pitch = pitch
|
|
67
|
-
self.crc32 = crc32
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
extension Features {
|
|
72
|
-
func toDictionary() -> [String: Any] {
|
|
73
|
-
var dict: [String: Any] = [
|
|
74
|
-
"energy": energy,
|
|
75
|
-
"mfcc": mfcc,
|
|
76
|
-
"rms": rms,
|
|
77
|
-
"minAmplitude": minAmplitude,
|
|
78
|
-
"maxAmplitude": maxAmplitude,
|
|
79
|
-
"zcr": zcr,
|
|
80
|
-
"spectralCentroid": spectralCentroid,
|
|
81
|
-
"spectralFlatness": spectralFlatness,
|
|
82
|
-
"spectralRollOff": spectralRollOff ?? 0,
|
|
83
|
-
"spectralBandwidth": spectralBandwidth ?? 0,
|
|
84
|
-
"chromagram": chromagram ?? [],
|
|
85
|
-
"tempo": tempo ?? 0,
|
|
86
|
-
"hnr": hnr ?? 0,
|
|
87
|
-
"melSpectrogram": melSpectrogram ?? [],
|
|
88
|
-
"spectralContrast": spectralContrast ?? [],
|
|
89
|
-
"tonnetz": tonnetz ?? [],
|
|
90
|
-
"pitch": pitch ?? 0,
|
|
91
|
-
"crc32": crc32 ?? 0
|
|
92
|
-
]
|
|
93
|
-
return dict
|
|
94
|
-
}
|
|
95
|
-
}
|
package/ios/Logger.swift
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// NotificationExtension.swift
|
|
3
|
-
// Pods
|
|
4
|
-
//
|
|
5
|
-
// Created by Arthur Breton on 27/10/2024.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import Foundation
|
|
10
|
-
|
|
11
|
-
extension Notification.Name {
|
|
12
|
-
static let pauseRecording = Notification.Name("PAUSE_RECORDING")
|
|
13
|
-
static let resumeRecording = Notification.Name("RESUME_RECORDING")
|
|
14
|
-
static let notificationActionTriggered = Notification.Name("notificationActionTriggered")
|
|
15
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// RecordingResult.swift
|
|
2
|
-
|
|
3
|
-
struct RecordingResult {
|
|
4
|
-
var fileUri: String
|
|
5
|
-
var filename: String
|
|
6
|
-
var mimeType: String
|
|
7
|
-
var duration: Int64
|
|
8
|
-
var size: Int64
|
|
9
|
-
var channels: Int
|
|
10
|
-
var bitDepth: Int
|
|
11
|
-
var sampleRate: Double
|
|
12
|
-
var compression: CompressedRecordingInfo?
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
struct StartRecordingResult {
|
|
16
|
-
var fileUri: String
|
|
17
|
-
var mimeType: String
|
|
18
|
-
var channels: Int
|
|
19
|
-
var bitDepth: Int
|
|
20
|
-
var sampleRate: Double
|
|
21
|
-
var compression: CompressedRecordingInfo?
|
|
22
|
-
}
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
// RecordingSettings.swift
|
|
2
|
-
|
|
3
|
-
import AVFoundation
|
|
4
|
-
|
|
5
|
-
struct NotificationAction {
|
|
6
|
-
var title: String
|
|
7
|
-
var identifier: String
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
struct IOSAudioSessionConfig {
|
|
11
|
-
var category: AVAudioSession.Category
|
|
12
|
-
var mode: AVAudioSession.Mode
|
|
13
|
-
var categoryOptions: AVAudioSession.CategoryOptions
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
struct IOSNotificationConfig {
|
|
17
|
-
var categoryIdentifier: String?
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
struct CompressedRecordingInfo {
|
|
21
|
-
var compressedFileUri: String
|
|
22
|
-
var mimeType: String
|
|
23
|
-
var bitrate: Int
|
|
24
|
-
var format: String
|
|
25
|
-
var size: Int64 = 0 // Add size with default value
|
|
26
|
-
|
|
27
|
-
static func validate(format: String, bitrate: Int) -> Result<(String, Int), Error> {
|
|
28
|
-
// Validate format
|
|
29
|
-
guard ["aac", "opus"].contains(format.lowercased()) else {
|
|
30
|
-
return .failure(RecordingError.unsupportedFormat(format))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Adjust bitrate based on format
|
|
34
|
-
let adjustedBitrate: Int
|
|
35
|
-
if format.lowercased() == "aac" {
|
|
36
|
-
// Standard AAC bitrates (bps)
|
|
37
|
-
let standardAACBitrates = [32000, 48000, 64000, 96000, 128000, 160000, 192000, 256000, 320000]
|
|
38
|
-
adjustedBitrate = standardAACBitrates.min(by: { abs($0 - bitrate) < abs($1 - bitrate) }) ?? 128000
|
|
39
|
-
} else {
|
|
40
|
-
// For Opus, allow lower bitrates (especially good for voice)
|
|
41
|
-
// Typical Opus voice bitrates: 8-24 kbps, music: 32-128 kbps
|
|
42
|
-
adjustedBitrate = min(max(bitrate, 8000), 320000)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return .success((format, adjustedBitrate))
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
struct NotificationConfig {
|
|
50
|
-
var title: String?
|
|
51
|
-
var text: String?
|
|
52
|
-
var icon: String?
|
|
53
|
-
var ios: IOSNotificationConfig?
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
struct IOSConfig {
|
|
57
|
-
var audioSession: IOSAudioSessionConfig?
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
enum RecordingError: Error {
|
|
61
|
-
case unsupportedFormat(String)
|
|
62
|
-
case invalidBitrate(Int)
|
|
63
|
-
case invalidOutputDirectory(String)
|
|
64
|
-
|
|
65
|
-
var localizedDescription: String {
|
|
66
|
-
switch self {
|
|
67
|
-
case .unsupportedFormat(let format):
|
|
68
|
-
return "Unsupported compression format: \(format). iOS only supports AAC."
|
|
69
|
-
case .invalidBitrate(let bitrate):
|
|
70
|
-
return "Invalid bitrate: \(bitrate). Must be between 8000 and 960000 bps."
|
|
71
|
-
case .invalidOutputDirectory(let directory):
|
|
72
|
-
return "Invalid output directory: \(directory). Directory does not exist, is not a directory, or is not writable."
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
struct RecordingSettings {
|
|
78
|
-
// Core recording settings
|
|
79
|
-
var sampleRate: Double
|
|
80
|
-
var desiredSampleRate: Double
|
|
81
|
-
var numberOfChannels: Int = 1
|
|
82
|
-
var bitDepth: Int = 16
|
|
83
|
-
var interval: Int?
|
|
84
|
-
var intervalAnalysis: Int?
|
|
85
|
-
|
|
86
|
-
// Feature flags
|
|
87
|
-
var keepAwake: Bool = true
|
|
88
|
-
var showNotification: Bool = false
|
|
89
|
-
var enableProcessing: Bool = false
|
|
90
|
-
|
|
91
|
-
// Remove pointsPerSecond and algorithm
|
|
92
|
-
var featureOptions: [String: Bool]? = ["rms": true, "zcr": true]
|
|
93
|
-
|
|
94
|
-
// iOS-specific configuration
|
|
95
|
-
var ios: IOSConfig?
|
|
96
|
-
|
|
97
|
-
// Notification configuration
|
|
98
|
-
var notification: NotificationConfig?
|
|
99
|
-
|
|
100
|
-
let enableCompressedOutput: Bool
|
|
101
|
-
let compressedFormat: String // "aac" or "opus"
|
|
102
|
-
let compressedBitRate: Int
|
|
103
|
-
|
|
104
|
-
let autoResumeAfterInterruption: Bool
|
|
105
|
-
|
|
106
|
-
var outputDirectory: String? = nil
|
|
107
|
-
var filename: String? = nil
|
|
108
|
-
|
|
109
|
-
// Update default to 100ms
|
|
110
|
-
var segmentDurationMs: Int = 100 // Default 100ms segments
|
|
111
|
-
|
|
112
|
-
static func fromDictionary(_ dict: [String: Any]) -> Result<RecordingSettings, Error> {
|
|
113
|
-
// Extract compression settings
|
|
114
|
-
let compression = dict["compression"] as? [String: Any]
|
|
115
|
-
let enableCompressedOutput = compression?["enabled"] as? Bool ?? false
|
|
116
|
-
let compressedFormat = (compression?["format"] as? String)?.lowercased() ?? "opus"
|
|
117
|
-
let compressedBitRate = compression?["bitrate"] as? Int ?? 24000
|
|
118
|
-
|
|
119
|
-
// Validate compression settings if enabled
|
|
120
|
-
if enableCompressedOutput {
|
|
121
|
-
// Validate format and bitrate
|
|
122
|
-
if case .failure(let error) = CompressedRecordingInfo.validate(
|
|
123
|
-
format: compressedFormat,
|
|
124
|
-
bitrate: compressedBitRate
|
|
125
|
-
) {
|
|
126
|
-
return .failure(error)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Create settings
|
|
131
|
-
var settings = RecordingSettings(
|
|
132
|
-
sampleRate: dict["sampleRate"] as? Double ?? 44100.0,
|
|
133
|
-
desiredSampleRate: dict["desiredSampleRate"] as? Double ?? 44100.0,
|
|
134
|
-
enableCompressedOutput: enableCompressedOutput,
|
|
135
|
-
compressedFormat: compressedFormat,
|
|
136
|
-
compressedBitRate: compressedBitRate,
|
|
137
|
-
autoResumeAfterInterruption: dict["autoResumeAfterInterruption"] as? Bool ?? false
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
// Parse core settings
|
|
141
|
-
settings.numberOfChannels = dict["channels"] as? Int ?? 1
|
|
142
|
-
settings.bitDepth = dict["bitDepth"] as? Int ?? 16
|
|
143
|
-
settings.interval = dict["interval"] as? Int
|
|
144
|
-
settings.intervalAnalysis = dict["intervalAnalysis"] as? Int
|
|
145
|
-
|
|
146
|
-
// Parse feature flags
|
|
147
|
-
settings.keepAwake = dict["keepAwake"] as? Bool ?? true
|
|
148
|
-
settings.showNotification = dict["showNotification"] as? Bool ?? false
|
|
149
|
-
settings.enableProcessing = dict["enableProcessing"] as? Bool ?? false
|
|
150
|
-
|
|
151
|
-
settings.featureOptions = dict["features"] as? [String: Bool]
|
|
152
|
-
|
|
153
|
-
// Update segmentDurationMs parsing
|
|
154
|
-
settings.segmentDurationMs = dict["segmentDurationMs"] as? Int ?? 100
|
|
155
|
-
|
|
156
|
-
// Parse iOS-specific config
|
|
157
|
-
if let iosDict = dict["ios"] as? [String: Any],
|
|
158
|
-
let audioSessionDict = iosDict["audioSession"] as? [String: Any] {
|
|
159
|
-
|
|
160
|
-
// Map category
|
|
161
|
-
let category: AVAudioSession.Category
|
|
162
|
-
if let categoryStr = audioSessionDict["category"] as? String {
|
|
163
|
-
switch categoryStr {
|
|
164
|
-
case "Ambient": category = .ambient
|
|
165
|
-
case "SoloAmbient": category = .soloAmbient
|
|
166
|
-
case "Playback": category = .playback
|
|
167
|
-
case "Record": category = .record
|
|
168
|
-
case "PlayAndRecord": category = .playAndRecord
|
|
169
|
-
case "MultiRoute": category = .multiRoute
|
|
170
|
-
default: category = .record
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
category = .record
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Map mode
|
|
177
|
-
let mode: AVAudioSession.Mode
|
|
178
|
-
if let modeStr = audioSessionDict["mode"] as? String {
|
|
179
|
-
switch modeStr {
|
|
180
|
-
case "Default": mode = .default
|
|
181
|
-
case "VoiceChat": mode = .voiceChat
|
|
182
|
-
case "VideoChat": mode = .videoChat
|
|
183
|
-
case "GameChat": mode = .gameChat
|
|
184
|
-
case "VideoRecording": mode = .videoRecording
|
|
185
|
-
case "Measurement": mode = .measurement
|
|
186
|
-
case "MoviePlayback": mode = .moviePlayback
|
|
187
|
-
case "SpokenAudio": mode = .spokenAudio
|
|
188
|
-
default: mode = .default
|
|
189
|
-
}
|
|
190
|
-
} else {
|
|
191
|
-
mode = .default
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Map category options
|
|
195
|
-
var categoryOptions: AVAudioSession.CategoryOptions = []
|
|
196
|
-
if let optionsArray = audioSessionDict["categoryOptions"] as? [String] {
|
|
197
|
-
for option in optionsArray {
|
|
198
|
-
switch option {
|
|
199
|
-
case "MixWithOthers": categoryOptions.insert(.mixWithOthers)
|
|
200
|
-
case "DuckOthers": categoryOptions.insert(.duckOthers)
|
|
201
|
-
case "InterruptSpokenAudioAndMixWithOthers": categoryOptions.insert(.interruptSpokenAudioAndMixWithOthers)
|
|
202
|
-
case "AllowBluetooth": categoryOptions.insert(.allowBluetooth)
|
|
203
|
-
case "AllowBluetoothA2DP": categoryOptions.insert(.allowBluetoothA2DP)
|
|
204
|
-
case "AllowAirPlay": categoryOptions.insert(.allowAirPlay)
|
|
205
|
-
case "DefaultToSpeaker": categoryOptions.insert(.defaultToSpeaker)
|
|
206
|
-
default: break
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
settings.ios = IOSConfig(audioSession: IOSAudioSessionConfig(
|
|
212
|
-
category: category,
|
|
213
|
-
mode: mode,
|
|
214
|
-
categoryOptions: categoryOptions
|
|
215
|
-
))
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Parse notification config
|
|
219
|
-
if let notificationDict = dict["notification"] as? [String: Any] {
|
|
220
|
-
var notificationConfig = NotificationConfig()
|
|
221
|
-
notificationConfig.title = notificationDict["title"] as? String
|
|
222
|
-
notificationConfig.text = notificationDict["text"] as? String
|
|
223
|
-
notificationConfig.icon = notificationDict["icon"] as? String
|
|
224
|
-
|
|
225
|
-
// Parse iOS-specific notification config
|
|
226
|
-
if let iosNotificationDict = notificationDict["ios"] as? [String: Any] {
|
|
227
|
-
notificationConfig.ios = IOSNotificationConfig(
|
|
228
|
-
categoryIdentifier: iosNotificationDict["categoryIdentifier"] as? String
|
|
229
|
-
)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
settings.notification = notificationConfig
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Parse output settings (they remain nil if not provided)
|
|
236
|
-
if let directory = dict["outputDirectory"] as? String {
|
|
237
|
-
// Only validate if a custom directory is provided
|
|
238
|
-
let fileManager = FileManager.default
|
|
239
|
-
var isDirectory: ObjCBool = false
|
|
240
|
-
|
|
241
|
-
// Clean up the directory path by removing file:// protocol if present
|
|
242
|
-
let cleanDirectory = directory.replacingOccurrences(of: "file://", with: "")
|
|
243
|
-
.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
|
244
|
-
.replacingOccurrences(of: "//", with: "/")
|
|
245
|
-
|
|
246
|
-
if !fileManager.fileExists(atPath: cleanDirectory, isDirectory: &isDirectory) {
|
|
247
|
-
return .failure(RecordingError.invalidOutputDirectory("Directory does not exist: \(cleanDirectory)"))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if !isDirectory.boolValue {
|
|
251
|
-
return .failure(RecordingError.invalidOutputDirectory("Path is not a directory: \(cleanDirectory)"))
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if !fileManager.isWritableFile(atPath: cleanDirectory) {
|
|
255
|
-
return .failure(RecordingError.invalidOutputDirectory("Directory is not writable: \(cleanDirectory)"))
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
settings.outputDirectory = cleanDirectory
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
settings.filename = dict["filename"] as? String
|
|
262
|
-
|
|
263
|
-
return .success(settings)
|
|
264
|
-
}
|
|
265
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// WaveformExtractor.swift
|
|
2
|
-
|
|
3
|
-
import Accelerate
|
|
4
|
-
import AVFoundation
|
|
5
|
-
|
|
6
|
-
/// This class is responsible for extracting waveform data from an audio file.
|
|
7
|
-
public class WaveformExtractor {
|
|
8
|
-
public private(set) var audioFile: AVAudioFile?
|
|
9
|
-
private var result: (Any) -> Void
|
|
10
|
-
private var reject: (String, String) -> Void
|
|
11
|
-
private var waveformData = Array<Float>()
|
|
12
|
-
private var progress: Float = 0.0
|
|
13
|
-
private var channelCount: Int = 1
|
|
14
|
-
private var currentProgress: Float = 0.0
|
|
15
|
-
private let extractionQueue = DispatchQueue(label: "WaveformExtractor", attributes: .concurrent)
|
|
16
|
-
private var _abortWaveformExtraction: Bool = false
|
|
17
|
-
|
|
18
|
-
/// Indicates whether the waveform extraction process should be aborted.
|
|
19
|
-
public var abortWaveformExtraction: Bool {
|
|
20
|
-
get { _abortWaveformExtraction }
|
|
21
|
-
set { _abortWaveformExtraction = newValue }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/// Initializes the waveform extractor with an audio file URL, resolve, and reject callbacks.
|
|
25
|
-
///
|
|
26
|
-
/// - Parameters:
|
|
27
|
-
/// - url: The URL of the audio file to be read.
|
|
28
|
-
/// - resolve: The callback to be called on successful extraction.
|
|
29
|
-
/// - reject: The callback to be called on extraction failure.
|
|
30
|
-
public init(url: URL, resolve: @escaping (Any) -> Void, reject: @escaping (String, String) -> Void) throws {
|
|
31
|
-
self.audioFile = try AVAudioFile(forReading: url)
|
|
32
|
-
self.result = resolve
|
|
33
|
-
self.reject = reject
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
deinit {
|
|
37
|
-
audioFile = nil
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// Extracts the waveform data from the audio file.
|
|
41
|
-
///
|
|
42
|
-
/// - Parameters:
|
|
43
|
-
/// - numberOfSamples: The number of samples to extract for the waveform.
|
|
44
|
-
/// - offset: The offset to start reading from.
|
|
45
|
-
/// - length: The length of the audio to read.
|
|
46
|
-
/// - Returns: A 2D array of floats where each sub-array represents waveform data for a specific channel.
|
|
47
|
-
public func extractWaveform(numberOfSamples: Int?, offset: Int? = 0, length: UInt? = nil) -> [[Float]]? {
|
|
48
|
-
guard let audioFile = audioFile else { return nil }
|
|
49
|
-
|
|
50
|
-
let numberOfSamples = max(1, numberOfSamples ?? 100)
|
|
51
|
-
let totalFrameCount = AVAudioFrameCount(audioFile.length)
|
|
52
|
-
var framesPerBuffer = totalFrameCount / AVAudioFrameCount(numberOfSamples)
|
|
53
|
-
|
|
54
|
-
guard let rmsBuffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: AVAudioFrameCount(framesPerBuffer)) else { return nil }
|
|
55
|
-
|
|
56
|
-
channelCount = Int(audioFile.processingFormat.channelCount)
|
|
57
|
-
var data = Array(repeating: [Float](repeating: 0, count: numberOfSamples), count: channelCount)
|
|
58
|
-
|
|
59
|
-
var startFrame: AVAudioFramePosition = offset == nil ? audioFile.framePosition : Int64(offset! * Int(framesPerBuffer))
|
|
60
|
-
var end = numberOfSamples
|
|
61
|
-
if let length = length {
|
|
62
|
-
end = Int(length)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
for i in 0..<end {
|
|
66
|
-
if abortWaveformExtraction {
|
|
67
|
-
audioFile.framePosition = startFrame
|
|
68
|
-
abortWaveformExtraction = false
|
|
69
|
-
return nil
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
do {
|
|
73
|
-
audioFile.framePosition = startFrame
|
|
74
|
-
try audioFile.read(into: rmsBuffer, frameCount: framesPerBuffer)
|
|
75
|
-
} catch {
|
|
76
|
-
reject("AUDIO_READ_ERROR", "Couldn't read into buffer")
|
|
77
|
-
return nil
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
guard let floatData = rmsBuffer.floatChannelData else { return nil }
|
|
81
|
-
|
|
82
|
-
for channel in 0..<channelCount {
|
|
83
|
-
var rms: Float = 0.0
|
|
84
|
-
vDSP_rmsqv(floatData[channel], 1, &rms, vDSP_Length(rmsBuffer.frameLength))
|
|
85
|
-
data[channel][i] = rms
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
currentProgress += 1
|
|
89
|
-
progress = currentProgress / Float(numberOfSamples)
|
|
90
|
-
|
|
91
|
-
startFrame += AVAudioFramePosition(framesPerBuffer)
|
|
92
|
-
if startFrame + AVAudioFramePosition(framesPerBuffer) > AVAudioFramePosition(totalFrameCount) {
|
|
93
|
-
framesPerBuffer = totalFrameCount - AVAudioFrameCount(startFrame)
|
|
94
|
-
if framesPerBuffer <= 0 { break }
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return data
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/// Cancels the waveform extraction process.
|
|
102
|
-
public func cancel() {
|
|
103
|
-
abortWaveformExtraction = true
|
|
104
|
-
}
|
|
105
|
-
}
|
package/plugin/build/index.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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;
|