@siteed/expo-audio-studio 2.18.5 → 2.18.6
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
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.18.6] - 2026-03-06
|
|
11
|
+
### Fixed
|
|
12
|
+
- fix(ios): `resetToDefaultDevice` now correctly resets the engine tap when switching back to the system default input (was a no-op due to `deviceId=nil` guard)
|
|
13
|
+
- fix(ios): recovery path after a failed device switch no longer produces silent audio (missing tap reinstall before engine restart)
|
|
14
|
+
- fix(ios): `setupNowPlayingInfo` no longer overrides user-configured audio session options (e.g. whisper-mode / no-Bluetooth configs)
|
|
15
|
+
- fix(ios): `selectInputDevice` now syncs `deviceId` into `recordingSettings` before updating the engine, ensuring the port lookup succeeds
|
|
16
|
+
- fix(ios): phone-call auto-resume handler respects user-configured `categoryOptions` instead of hardcoded `[.allowBluetooth, .mixWithOthers]`
|
|
17
|
+
- fix(ios): `AudioDeviceManager.prepareAudioSession` preserves existing session options when already `.playAndRecord`
|
|
10
18
|
|
|
11
19
|
## [2.18.5] - 2026-02-23
|
|
12
20
|
### Changed
|
|
@@ -87,11 +87,15 @@ class AudioDeviceManager {
|
|
|
87
87
|
do {
|
|
88
88
|
let session = AVAudioSession.sharedInstance()
|
|
89
89
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
// Preserve existing session configuration if already set up for recording
|
|
91
|
+
if session.category == .playAndRecord {
|
|
92
|
+
Logger.debug("AudioDeviceManager", "Session already .playAndRecord, preserving categoryOptions")
|
|
93
|
+
try session.setActive(true, options: .notifyOthersOnDeactivation)
|
|
94
|
+
} else {
|
|
95
|
+
// Configure with options needed for Bluetooth detection
|
|
96
|
+
try session.setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .allowBluetoothA2DP, .mixWithOthers])
|
|
97
|
+
try session.setActive(true, options: .notifyOthersOnDeactivation)
|
|
98
|
+
}
|
|
95
99
|
|
|
96
100
|
// Give the system a moment to detect Bluetooth devices if needed
|
|
97
101
|
// Minimal delay that still allows devices to be detected
|
|
@@ -217,7 +217,8 @@ class AudioStreamManager: NSObject, AudioDeviceManagerDelegate {
|
|
|
217
217
|
// Configure audio session
|
|
218
218
|
do {
|
|
219
219
|
let session = AVAudioSession.sharedInstance()
|
|
220
|
-
|
|
220
|
+
let resumeOptions: AVAudioSession.CategoryOptions = self.recordingSettings?.ios?.audioSession?.categoryOptions ?? [.allowBluetooth, .mixWithOthers]
|
|
221
|
+
try session.setCategory(.playAndRecord, mode: .default, options: resumeOptions)
|
|
221
222
|
try session.setActive(true, options: .notifyOthersOnDeactivation)
|
|
222
223
|
|
|
223
224
|
// Resume if we're still recording and paused
|
|
@@ -251,15 +252,9 @@ class AudioStreamManager: NSObject, AudioDeviceManagerDelegate {
|
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
private func setupNowPlayingInfo() {
|
|
254
|
-
//
|
|
255
|
+
// Session is already configured by configureAudioSession(); do not override it here.
|
|
255
256
|
audioSession = AVAudioSession.sharedInstance()
|
|
256
|
-
|
|
257
|
-
try audioSession?.setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .mixWithOthers])
|
|
258
|
-
try audioSession?.setActive(true)
|
|
259
|
-
} catch {
|
|
260
|
-
Logger.debug("AudioStreamManager", "Failed to configure audio session: \(error)")
|
|
261
|
-
}
|
|
262
|
-
|
|
257
|
+
|
|
263
258
|
// Setup Now Playing info
|
|
264
259
|
notificationView = MPNowPlayingInfoCenter.default()
|
|
265
260
|
updateNowPlayingInfo(isPaused: false)
|
|
@@ -1683,37 +1678,59 @@ class AudioStreamManager: NSObject, AudioDeviceManagerDelegate {
|
|
|
1683
1678
|
return outputBuffer
|
|
1684
1679
|
}
|
|
1685
1680
|
|
|
1681
|
+
/// Performs the full engine stop → reset → reinstall tap → restart cycle for a device switch.
|
|
1682
|
+
/// Pass `nil` to revert to the system-default input.
|
|
1683
|
+
/// This is the single source of truth for all device-switch paths (Bug 1 + Bug 2 fixes).
|
|
1684
|
+
public func performDeviceSwitch(port: AVAudioSessionPortDescription?) {
|
|
1685
|
+
let wasRunning = audioEngine.isRunning
|
|
1686
|
+
do {
|
|
1687
|
+
if wasRunning { audioEngine.stop() }
|
|
1688
|
+
audioEngine.inputNode.removeTap(onBus: 0)
|
|
1689
|
+
try AVAudioSession.sharedInstance().setPreferredInput(port)
|
|
1690
|
+
Thread.sleep(forTimeInterval: 0.15)
|
|
1691
|
+
audioEngine.reset()
|
|
1692
|
+
_ = installTapWithHardwareFormat()
|
|
1693
|
+
if wasRunning {
|
|
1694
|
+
audioEngine.prepare()
|
|
1695
|
+
try audioEngine.start()
|
|
1696
|
+
lastEmissionTime = Date()
|
|
1697
|
+
lastEmissionTimeAnalysis = Date()
|
|
1698
|
+
Logger.debug("AudioStreamManager", "Device switch complete; engine restarted (port: \(port?.portName ?? "default"))")
|
|
1699
|
+
}
|
|
1700
|
+
} catch {
|
|
1701
|
+
Logger.debug("AudioStreamManager", "Device switch failed: \(error.localizedDescription)")
|
|
1702
|
+
if wasRunning {
|
|
1703
|
+
do {
|
|
1704
|
+
_ = installTapWithHardwareFormat() // Bug 2 fix: reinstall tap in recovery path
|
|
1705
|
+
audioEngine.prepare()
|
|
1706
|
+
try audioEngine.start()
|
|
1707
|
+
Logger.debug("AudioStreamManager", "Engine recovery after device switch succeeded")
|
|
1708
|
+
} catch {
|
|
1709
|
+
Logger.debug("AudioStreamManager", "Engine recovery failed: \(error.localizedDescription)")
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1686
1715
|
/// Attempts to update the audio session with the preferred input device from current settings.
|
|
1687
1716
|
/// Called externally when the device selection changes.
|
|
1688
|
-
/// Note: Avoids changing sample rate or buffer duration while engine might be running.
|
|
1689
1717
|
public func updateAudioSessionWithCurrentSettings() {
|
|
1690
1718
|
guard let settings = self.recordingSettings, let deviceId = settings.deviceId else {
|
|
1691
1719
|
Logger.debug("Cannot update audio session preference, settings or deviceId missing")
|
|
1692
1720
|
return
|
|
1693
1721
|
}
|
|
1694
|
-
|
|
1722
|
+
|
|
1695
1723
|
let session = AVAudioSession.sharedInstance()
|
|
1696
|
-
|
|
1697
|
-
// Find the requested device port
|
|
1698
1724
|
let selectedPort = session.availableInputs?.first { port in
|
|
1699
|
-
|
|
1700
|
-
let portNormalizedId = deviceManager.normalizeBluetoothDeviceId(port.uid)
|
|
1701
|
-
let requestedNormalizedId = deviceManager.normalizeBluetoothDeviceId(deviceId)
|
|
1702
|
-
return portNormalizedId == requestedNormalizedId
|
|
1725
|
+
deviceManager.normalizeBluetoothDeviceId(port.uid) == deviceManager.normalizeBluetoothDeviceId(deviceId)
|
|
1703
1726
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
Logger.debug("Attempted to set preferred input to: \(portToSet.portName) (ID: \(portToSet.uid))")
|
|
1709
|
-
// We add a small delay hoping the system applies the change before potential next operations
|
|
1710
|
-
Thread.sleep(forTimeInterval: 0.1)
|
|
1711
|
-
} catch {
|
|
1712
|
-
Logger.debug("Failed to set preferred input device \(portToSet.portName): \(error.localizedDescription)")
|
|
1713
|
-
}
|
|
1714
|
-
} else {
|
|
1715
|
-
Logger.debug("Could not find device with ID \(deviceId) to set as preferred input.")
|
|
1727
|
+
|
|
1728
|
+
guard let portToSet = selectedPort else {
|
|
1729
|
+
Logger.debug("Could not find device with ID \(deviceId)")
|
|
1730
|
+
return
|
|
1716
1731
|
}
|
|
1732
|
+
|
|
1733
|
+
performDeviceSwitch(port: portToSet)
|
|
1717
1734
|
}
|
|
1718
1735
|
|
|
1719
1736
|
/// Stops the current audio recording.
|
|
@@ -759,6 +759,8 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate, AudioDev
|
|
|
759
759
|
AsyncFunction("selectInputDevice") { (deviceId: String, promise: Promise) in
|
|
760
760
|
Logger.debug("ExpoAudioStreamModule", "selectInputDevice called with ID: \(deviceId)")
|
|
761
761
|
self.deviceManager.selectInputDevice(deviceId, promise: promise)
|
|
762
|
+
// Sync deviceId into recordingSettings so updateAudioSessionWithCurrentSettings can find the port
|
|
763
|
+
self.streamManager.recordingSettings?.deviceId = deviceId
|
|
762
764
|
// Update the audio recorder if recording is in progress or prepared
|
|
763
765
|
if self.streamManager.isRecording || self.streamManager.isPrepared {
|
|
764
766
|
Logger.debug("ExpoAudioStreamModule", "selectInputDevice: Calling updateAudioSessionWithCurrentSettings because recording/prepared.")
|
|
@@ -776,11 +778,15 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate, AudioDev
|
|
|
776
778
|
Logger.debug("ExpoAudioStreamModule", "resetToDefaultDevice called.")
|
|
777
779
|
self.deviceManager.resetToDefaultDevice { success, error in
|
|
778
780
|
if success {
|
|
781
|
+
// Clear stored deviceId so updateAudioSessionWithCurrentSettings won't bail early
|
|
782
|
+
self.streamManager.recordingSettings?.deviceId = nil
|
|
779
783
|
if self.streamManager.isRecording || self.streamManager.isPrepared {
|
|
780
|
-
Logger.debug("ExpoAudioStreamModule", "resetToDefaultDevice:
|
|
781
|
-
|
|
784
|
+
Logger.debug("ExpoAudioStreamModule", "resetToDefaultDevice: Performing device switch to system default.")
|
|
785
|
+
// Bug 1 fix: call performDeviceSwitch(nil) directly — updateAudioSessionWithCurrentSettings
|
|
786
|
+
// would bail immediately because deviceId is now nil.
|
|
787
|
+
self.streamManager.performDeviceSwitch(port: nil)
|
|
782
788
|
} else {
|
|
783
|
-
Logger.debug("ExpoAudioStreamModule", "resetToDefaultDevice: Not
|
|
789
|
+
Logger.debug("ExpoAudioStreamModule", "resetToDefaultDevice: Not recording/prepared, no engine action needed.")
|
|
784
790
|
}
|
|
785
791
|
promise.resolve(true)
|
|
786
792
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteed/expo-audio-studio",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.6",
|
|
4
4
|
"description": "Comprehensive audio processing library for React Native and Expo with recording, analysis, visualization, and streaming capabilities across iOS, Android, and web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|