@siteed/expo-audio-stream 1.16.0 → 2.0.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/CHANGELOG.md +28 -1
- package/README.md +1 -1
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +68 -22
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +24 -0
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +836 -386
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +134 -23
- package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +35 -29
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +236 -96
- package/android/src/main/java/net/siteed/audiostream/FFT.kt +55 -0
- package/android/src/main/java/net/siteed/audiostream/Features.kt +49 -7
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +4 -4
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +55 -47
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts +60 -13
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -1
- package/build/AudioAnalysis/extractAudioAnalysis.js +147 -162
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +49 -3
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +2 -0
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +8 -1
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/ExpoAudioStreamModule.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.js +216 -12
- package/build/ExpoAudioStreamModule.js.map +1 -1
- package/build/WebRecorder.web.d.ts +67 -13
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +178 -173
- package/build/WebRecorder.web.js.map +1 -1
- package/build/index.d.ts +3 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/useAudioRecorder.d.ts.map +1 -1
- package/build/useAudioRecorder.js +12 -8
- package/build/useAudioRecorder.js.map +1 -1
- 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/workers/InlineFeaturesExtractor.web.d.ts +1 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -1
- package/build/workers/InlineFeaturesExtractor.web.js +692 -175
- package/build/workers/InlineFeaturesExtractor.web.js.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.js +3 -2
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
- package/ios/AudioAnalysisData.swift +51 -16
- package/ios/AudioProcessingHelpers.swift +710 -26
- package/ios/AudioProcessor.swift +334 -185
- package/ios/AudioStreamManager.swift +66 -22
- package/ios/DataPoint.swift +25 -12
- package/ios/DecodingConfig.swift +47 -0
- package/ios/ExpoAudioStreamModule.swift +189 -104
- package/ios/FFT.swift +62 -0
- package/ios/Features.swift +24 -3
- package/ios/RecordingSettings.swift +9 -7
- package/package.json +2 -1
- package/plugin/build/index.d.ts +2 -0
- package/plugin/build/index.js +10 -3
- package/plugin/src/index.ts +10 -1
- package/src/AudioAnalysis/AudioAnalysis.types.ts +68 -52
- package/src/AudioAnalysis/extractAudioAnalysis.ts +223 -219
- package/src/ExpoAudioStream.types.ts +57 -7
- package/src/ExpoAudioStream.web.ts +8 -1
- package/src/ExpoAudioStreamModule.ts +255 -10
- package/src/WebRecorder.web.ts +231 -243
- package/src/index.ts +5 -3
- package/src/useAudioRecorder.tsx +14 -10
- package/src/utils/audioProcessing.ts +205 -0
- package/src/workers/InlineFeaturesExtractor.web.tsx +692 -175
- package/src/workers/inlineAudioWebWorker.web.tsx +3 -2
|
@@ -44,17 +44,29 @@ class AudioStreamManager: NSObject {
|
|
|
44
44
|
private var wasIdleTimerDisabled: Bool = false // Track previous idle timer state
|
|
45
45
|
private var isWakeLockEnabled: Bool = false // Track current wake lock state
|
|
46
46
|
|
|
47
|
+
|
|
48
|
+
// Data emission for onAudioStream
|
|
47
49
|
internal var lastEmissionTime: Date?
|
|
48
50
|
internal var lastEmittedSize: Int64 = 0
|
|
49
51
|
internal var lastEmittedCompressedSize: Int64 = 0
|
|
50
|
-
private var emissionInterval: TimeInterval = 1.0 // Default to 1 second
|
|
51
52
|
private var totalDataSize: Int64 = 0
|
|
53
|
+
private var lastBufferTime: AVAudioTime?
|
|
54
|
+
private var accumulatedData = Data()
|
|
55
|
+
|
|
56
|
+
// Data emission for onAudioAnalysis
|
|
57
|
+
internal var lastEmissionTimeAnalysis: Date?
|
|
58
|
+
internal var lastEmittedSizeAnalysis: Int64 = 0
|
|
59
|
+
internal var lastEmittedCompressedSizeAnalysis: Int64 = 0
|
|
60
|
+
private var totalDataSizeAnalysis: Int64 = 0
|
|
61
|
+
private var lastBufferTimeAnalysis: AVAudioTime?
|
|
62
|
+
private var accumulatedAnalysisData = Data()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
52
66
|
private var fileManager = FileManager.default
|
|
53
67
|
internal var recordingSettings: RecordingSettings?
|
|
54
68
|
internal var recordingUUID: UUID?
|
|
55
69
|
internal var mimeType: String = "audio/wav"
|
|
56
|
-
private var lastBufferTime: AVAudioTime?
|
|
57
|
-
private var accumulatedData = Data()
|
|
58
70
|
private var recentData = [Float]() // This property stores the recent audio data
|
|
59
71
|
private var notificationUpdateTimer: Timer?
|
|
60
72
|
|
|
@@ -77,6 +89,10 @@ class AudioStreamManager: NSObject {
|
|
|
77
89
|
// Add property to track auto-resume preference
|
|
78
90
|
private var autoResumeAfterInterruption: Bool = false
|
|
79
91
|
|
|
92
|
+
// Add these properties
|
|
93
|
+
private var emissionInterval: TimeInterval = 1.0 // Default 1 second
|
|
94
|
+
private var emissionIntervalAnalysis: TimeInterval = 0.5 // Default 0.5 seconds
|
|
95
|
+
|
|
80
96
|
/// Initializes the AudioStreamManager
|
|
81
97
|
override init() {
|
|
82
98
|
super.init()
|
|
@@ -537,7 +553,8 @@ class AudioStreamManager: NSObject {
|
|
|
537
553
|
"isPaused": isPaused,
|
|
538
554
|
"mimeType": mimeType,
|
|
539
555
|
"size": totalDataSize,
|
|
540
|
-
"interval":
|
|
556
|
+
"interval": settings.interval,
|
|
557
|
+
"intervalAnalysis": settings.intervalAnalysis
|
|
541
558
|
]
|
|
542
559
|
|
|
543
560
|
// Add compression info if enabled
|
|
@@ -575,12 +592,11 @@ class AudioStreamManager: NSObject {
|
|
|
575
592
|
audioSession.currentRoute.outputs.contains { $0.portType == .builtInReceiver }
|
|
576
593
|
}
|
|
577
594
|
|
|
578
|
-
/// Starts a new audio recording with the specified settings
|
|
595
|
+
/// Starts a new audio recording with the specified settings.
|
|
579
596
|
/// - Parameters:
|
|
580
597
|
/// - settings: The recording settings to use.
|
|
581
|
-
/// - intervalMilliseconds: The interval in milliseconds for emitting audio data.
|
|
582
598
|
/// - Returns: A StartRecordingResult object if recording starts successfully, or nil otherwise.
|
|
583
|
-
func startRecording(settings: RecordingSettings
|
|
599
|
+
func startRecording(settings: RecordingSettings) -> StartRecordingResult? {
|
|
584
600
|
// Check for active call using the new method
|
|
585
601
|
if isPhoneCallActive() {
|
|
586
602
|
Logger.debug("Cannot start recording during an active phone call")
|
|
@@ -619,14 +635,19 @@ class AudioStreamManager: NSObject {
|
|
|
619
635
|
let session = AVAudioSession.sharedInstance()
|
|
620
636
|
var newSettings = settings
|
|
621
637
|
|
|
622
|
-
|
|
623
|
-
|
|
638
|
+
emissionInterval = max(100.0, Double(settings.interval ?? 1000)) / 1000.0
|
|
639
|
+
emissionIntervalAnalysis = max(100.0, Double(settings.intervalAnalysis ?? 500)) / 1000.0
|
|
624
640
|
lastEmissionTime = Date()
|
|
641
|
+
lastEmissionTimeAnalysis = Date()
|
|
625
642
|
accumulatedData.removeAll()
|
|
643
|
+
accumulatedAnalysisData.removeAll()
|
|
626
644
|
totalDataSize = 0
|
|
645
|
+
totalDataSizeAnalysis = 0
|
|
627
646
|
totalPausedDuration = 0
|
|
628
647
|
lastEmittedSize = 0
|
|
648
|
+
lastEmittedCompressedSizeAnalysis = 0
|
|
629
649
|
isPaused = false
|
|
650
|
+
|
|
630
651
|
|
|
631
652
|
// Create recording file first
|
|
632
653
|
recordingFileURL = createRecordingFile()
|
|
@@ -677,6 +698,12 @@ class AudioStreamManager: NSObject {
|
|
|
677
698
|
- mode: \(mode)
|
|
678
699
|
- options: \(options)
|
|
679
700
|
- keepAwake: \(settings.keepAwake)
|
|
701
|
+
- emission interval: \(emissionInterval * 1000)ms
|
|
702
|
+
- analysis interval: \(emissionIntervalAnalysis * 1000)ms
|
|
703
|
+
- sample rate: \(settings.sampleRate)Hz
|
|
704
|
+
- channels: \(settings.numberOfChannels)
|
|
705
|
+
- bit depth: \(settings.bitDepth)-bit
|
|
706
|
+
- compression enabled: \(settings.enableCompressedOutput)
|
|
680
707
|
""")
|
|
681
708
|
|
|
682
709
|
try session.setPreferredSampleRate(settings.sampleRate)
|
|
@@ -860,7 +887,7 @@ class AudioStreamManager: NSObject {
|
|
|
860
887
|
}
|
|
861
888
|
|
|
862
889
|
/// Pauses the current audio recording.
|
|
863
|
-
func pauseRecording() {
|
|
890
|
+
public func pauseRecording() {
|
|
864
891
|
guard isRecording && !isPaused else { return }
|
|
865
892
|
|
|
866
893
|
// Store the current duration when pausing
|
|
@@ -929,8 +956,8 @@ class AudioStreamManager: NSObject {
|
|
|
929
956
|
}
|
|
930
957
|
|
|
931
958
|
/// Resumes the current audio recording.
|
|
932
|
-
func resumeRecording() {
|
|
933
|
-
// Check for active call
|
|
959
|
+
public func resumeRecording() {
|
|
960
|
+
// Check for active phone call
|
|
934
961
|
if isPhoneCallActive() {
|
|
935
962
|
Logger.debug("Cannot resume recording during an active phone call")
|
|
936
963
|
delegate?.audioStreamManager(self, didFailWithError: "Cannot resume recording during an active phone call")
|
|
@@ -987,7 +1014,7 @@ class AudioStreamManager: NSObject {
|
|
|
987
1014
|
return formatDescription
|
|
988
1015
|
}
|
|
989
1016
|
|
|
990
|
-
|
|
1017
|
+
func describeCommonFormat(_ format: AVAudioCommonFormat) -> String {
|
|
991
1018
|
switch format {
|
|
992
1019
|
case .pcmFormatFloat32:
|
|
993
1020
|
return "32-bit float"
|
|
@@ -1149,9 +1176,12 @@ class AudioStreamManager: NSObject {
|
|
|
1149
1176
|
totalPausedDuration = 0
|
|
1150
1177
|
currentPauseStart = nil
|
|
1151
1178
|
lastEmissionTime = nil
|
|
1179
|
+
lastEmissionTimeAnalysis = nil
|
|
1152
1180
|
lastEmittedSize = 0
|
|
1181
|
+
lastEmittedSizeAnalysis = 0
|
|
1153
1182
|
lastEmittedCompressedSize = 0
|
|
1154
1183
|
accumulatedData.removeAll()
|
|
1184
|
+
accumulatedAnalysisData.removeAll()
|
|
1155
1185
|
recordingUUID = nil
|
|
1156
1186
|
|
|
1157
1187
|
return result
|
|
@@ -1459,6 +1489,7 @@ class AudioStreamManager: NSObject {
|
|
|
1459
1489
|
// Update total size and accumulated data
|
|
1460
1490
|
totalDataSize += Int64(data.count)
|
|
1461
1491
|
accumulatedData.append(data)
|
|
1492
|
+
accumulatedAnalysisData.append(data)
|
|
1462
1493
|
|
|
1463
1494
|
// Handle notifications if enabled
|
|
1464
1495
|
if recordingSettings?.showNotification == true {
|
|
@@ -1534,7 +1565,21 @@ class AudioStreamManager: NSObject {
|
|
|
1534
1565
|
compressionInfo: compressionInfo
|
|
1535
1566
|
)
|
|
1536
1567
|
|
|
1537
|
-
//
|
|
1568
|
+
// Update state after emission
|
|
1569
|
+
self.lastEmissionTime = currentTime
|
|
1570
|
+
self.lastEmittedSize = totalDataSize
|
|
1571
|
+
accumulatedData.removeAll()
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
if let lastEmissionTimeAnalysis = lastEmissionTimeAnalysis,
|
|
1576
|
+
let startTime = startTime,
|
|
1577
|
+
currentTime.timeIntervalSince(lastEmissionTimeAnalysis) >= emissionIntervalAnalysis {
|
|
1578
|
+
|
|
1579
|
+
let recordingTime = currentTime.timeIntervalSince(startTime)
|
|
1580
|
+
let dataToProcess = accumulatedAnalysisData
|
|
1581
|
+
|
|
1582
|
+
// Process audio if enabled
|
|
1538
1583
|
if settings.enableProcessing {
|
|
1539
1584
|
DispatchQueue.global().async { [weak self] in
|
|
1540
1585
|
guard let self = self else { return }
|
|
@@ -1543,9 +1588,8 @@ class AudioStreamManager: NSObject {
|
|
|
1543
1588
|
let processingResult = processor.processAudioBuffer(
|
|
1544
1589
|
data: dataToProcess,
|
|
1545
1590
|
sampleRate: Float(settings.sampleRate),
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
featureOptions: settings.featureOptions ?? ["rms": true, "zcr": true],
|
|
1591
|
+
segmentDurationMs: settings.segmentDurationMs,
|
|
1592
|
+
featureOptions: settings.featureOptions ?? [:],
|
|
1549
1593
|
bitDepth: settings.bitDepth,
|
|
1550
1594
|
numberOfChannels: settings.numberOfChannels
|
|
1551
1595
|
)
|
|
@@ -1555,14 +1599,14 @@ class AudioStreamManager: NSObject {
|
|
|
1555
1599
|
self.delegate?.audioStreamManager(self, didReceiveProcessingResult: result)
|
|
1556
1600
|
}
|
|
1557
1601
|
}
|
|
1602
|
+
|
|
1603
|
+
// Update state after emission
|
|
1604
|
+
self.lastEmissionTimeAnalysis = currentTime
|
|
1605
|
+
self.lastEmittedSizeAnalysis = totalDataSizeAnalysis
|
|
1606
|
+
accumulatedAnalysisData.removeAll()
|
|
1558
1607
|
}
|
|
1559
1608
|
}
|
|
1560
1609
|
}
|
|
1561
|
-
|
|
1562
|
-
// Update state after emission
|
|
1563
|
-
self.lastEmissionTime = currentTime
|
|
1564
|
-
self.lastEmittedSize = totalDataSize
|
|
1565
|
-
accumulatedData.removeAll()
|
|
1566
1610
|
}
|
|
1567
1611
|
}
|
|
1568
1612
|
|
package/ios/DataPoint.swift
CHANGED
|
@@ -7,19 +7,31 @@
|
|
|
7
7
|
|
|
8
8
|
import Foundation
|
|
9
9
|
|
|
10
|
+
public struct SpeechFeatures {
|
|
11
|
+
public var isActive: Bool
|
|
12
|
+
public var speakerId: Int?
|
|
13
|
+
|
|
14
|
+
func toDictionary() -> [String: Any] {
|
|
15
|
+
return [
|
|
16
|
+
"isActive": isActive,
|
|
17
|
+
"speakerId": speakerId as Any
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
10
21
|
|
|
11
22
|
public struct DataPoint {
|
|
12
23
|
public var id: Int
|
|
13
24
|
public var amplitude: Float
|
|
14
|
-
public var
|
|
15
|
-
public var dB: Float
|
|
16
|
-
public var silent: Bool
|
|
25
|
+
public var rms: Float
|
|
26
|
+
public var dB: Float
|
|
27
|
+
public var silent: Bool
|
|
17
28
|
public var features: Features?
|
|
18
|
-
public var
|
|
19
|
-
public
|
|
20
|
-
public
|
|
21
|
-
public
|
|
22
|
-
public
|
|
29
|
+
public var speech: SpeechFeatures?
|
|
30
|
+
public let startTime: Float // in seconds
|
|
31
|
+
public let endTime: Float // in seconds
|
|
32
|
+
public let startPosition: Int // byte position in audio file
|
|
33
|
+
public let endPosition: Int // byte position in audio file
|
|
34
|
+
public let samples: Int // number of samples in segment
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
extension DataPoint {
|
|
@@ -27,15 +39,16 @@ extension DataPoint {
|
|
|
27
39
|
return [
|
|
28
40
|
"id": id,
|
|
29
41
|
"amplitude": amplitude,
|
|
30
|
-
"
|
|
31
|
-
"dB": dB
|
|
32
|
-
"silent": silent
|
|
42
|
+
"rms": rms,
|
|
43
|
+
"dB": dB,
|
|
44
|
+
"silent": silent,
|
|
33
45
|
"features": features?.toDictionary() ?? [:],
|
|
46
|
+
"speech": speech?.toDictionary() ?? [:],
|
|
34
47
|
"startTime": startTime ?? 0,
|
|
35
48
|
"endTime": endTime ?? 0,
|
|
36
49
|
"startPosition": startPosition ?? 0,
|
|
37
50
|
"endPosition": endPosition ?? 0,
|
|
38
|
-
"
|
|
51
|
+
"samples": samples ?? 0
|
|
39
52
|
]
|
|
40
53
|
}
|
|
41
54
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DecodingConfig.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Arthur Breton on 24/2/2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import AVFoundation
|
|
9
|
+
|
|
10
|
+
public struct DecodingConfig {
|
|
11
|
+
let targetSampleRate: Double?
|
|
12
|
+
let targetChannels: Int?
|
|
13
|
+
let targetBitDepth: Int?
|
|
14
|
+
let normalizeAudio: Bool
|
|
15
|
+
|
|
16
|
+
static func fromDictionary(_ dict: [String: Any]?) -> DecodingConfig {
|
|
17
|
+
guard let dict = dict else {
|
|
18
|
+
return DecodingConfig.default
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return DecodingConfig(
|
|
22
|
+
targetSampleRate: dict["targetSampleRate"] as? Double,
|
|
23
|
+
targetChannels: dict["targetChannels"] as? Int,
|
|
24
|
+
targetBitDepth: dict["targetBitDepth"] as? Int,
|
|
25
|
+
normalizeAudio: dict["normalizeAudio"] as? Bool ?? false
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static var `default`: DecodingConfig {
|
|
30
|
+
return DecodingConfig(
|
|
31
|
+
targetSampleRate: nil,
|
|
32
|
+
targetChannels: nil,
|
|
33
|
+
targetBitDepth: nil,
|
|
34
|
+
normalizeAudio: false
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func toAudioFormat(baseFormat: AVAudioFormat) -> AVAudioFormat {
|
|
39
|
+
let sampleRate = targetSampleRate ?? baseFormat.sampleRate
|
|
40
|
+
let channels = targetChannels ?? Int(baseFormat.channelCount)
|
|
41
|
+
|
|
42
|
+
return AVAudioFormat(
|
|
43
|
+
standardFormatWithSampleRate: sampleRate,
|
|
44
|
+
channels: AVAudioChannelCount(channels)
|
|
45
|
+
)!
|
|
46
|
+
}
|
|
47
|
+
}
|