Refactor(AudioDeviceManager): Use UID for custom device persistence, cleanup UserDefaults logic for prioritized mode, and remove all comments and legacy keys
This commit is contained in:
parent
4767cd74fd
commit
e51e57f3b1
@ -4,7 +4,7 @@ import AVFoundation
|
||||
import os
|
||||
|
||||
struct PrioritizedDevice: Codable, Identifiable {
|
||||
let id: String // Device UID
|
||||
let id: String
|
||||
let name: String
|
||||
let priority: Int
|
||||
}
|
||||
@ -21,11 +21,15 @@ class AudioDeviceManager: ObservableObject {
|
||||
@Published var selectedDeviceID: AudioDeviceID?
|
||||
@Published var inputMode: AudioInputMode = .systemDefault
|
||||
@Published var prioritizedDevices: [PrioritizedDevice] = []
|
||||
var fallbackDeviceID: AudioDeviceID?
|
||||
var fallbackDeviceID: AudioDeviceID?
|
||||
|
||||
var isRecordingActive: Bool = false
|
||||
|
||||
static let shared = AudioDeviceManager()
|
||||
|
||||
private static let audioInputModeKey = "audioInputMode"
|
||||
private static let selectedAudioDeviceUIDKey = "selectedAudioDeviceUID"
|
||||
private static let prioritizedDevicesKey = "prioritizedDevices"
|
||||
|
||||
init() {
|
||||
setupFallbackDevice()
|
||||
@ -34,17 +38,15 @@ class AudioDeviceManager: ObservableObject {
|
||||
self?.initializeSelectedDevice()
|
||||
}
|
||||
|
||||
// Load saved input mode
|
||||
if let savedMode = UserDefaults.standard.string(forKey: "audioInputMode"),
|
||||
if let savedMode = UserDefaults.standard.string(forKey: AudioDeviceManager.audioInputModeKey),
|
||||
let mode = AudioInputMode(rawValue: savedMode) {
|
||||
inputMode = mode
|
||||
}
|
||||
|
||||
// Setup device change notifications
|
||||
setupDeviceChangeNotifications()
|
||||
}
|
||||
|
||||
func setupFallbackDevice() {
|
||||
func setupFallbackDevice() {
|
||||
let deviceID: AudioDeviceID? = getDeviceProperty(
|
||||
deviceID: AudioObjectID(kAudioObjectSystemObject),
|
||||
selector: kAudioHardwarePropertyDefaultInputDevice
|
||||
@ -66,17 +68,16 @@ class AudioDeviceManager: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to load saved device
|
||||
if let savedID = UserDefaults.standard.object(forKey: "selectedAudioDeviceID") as? AudioDeviceID {
|
||||
// Verify the saved device still exists and is valid
|
||||
if isDeviceAvailable(savedID) {
|
||||
selectedDeviceID = savedID
|
||||
logger.info("Loaded saved device ID: \(savedID)")
|
||||
if let name = getDeviceName(deviceID: savedID) {
|
||||
if let savedUID = UserDefaults.standard.string(forKey: AudioDeviceManager.selectedAudioDeviceUIDKey) {
|
||||
if let device = availableDevices.first(where: { $0.uid == savedUID }) {
|
||||
selectedDeviceID = device.id
|
||||
logger.info("Loaded saved device UID: \(savedUID), mapped to ID: \(device.id)")
|
||||
if let name = getDeviceName(deviceID: device.id) {
|
||||
logger.info("Using saved device: \(name)")
|
||||
}
|
||||
} else {
|
||||
logger.warning("Saved device ID \(savedID) is no longer available")
|
||||
logger.warning("Saved device UID \(savedUID) is no longer available")
|
||||
UserDefaults.standard.removeObject(forKey: AudioDeviceManager.selectedAudioDeviceUIDKey)
|
||||
fallbackToDefaultDevice()
|
||||
}
|
||||
} else {
|
||||
@ -89,9 +90,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
}
|
||||
|
||||
private func fallbackToDefaultDevice() {
|
||||
// Instead of just setting a fallback device ID, explicitly switch to system default mode.
|
||||
// selectInputMode(.systemDefault) will handle setting inputMode,
|
||||
// clearing selectedDeviceID, and updating UserDefaults.
|
||||
selectInputMode(.systemDefault)
|
||||
logger.info("Switched to system default audio input mode due to fallback.")
|
||||
}
|
||||
@ -149,7 +147,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.availableDevices = devices.map { ($0.id, $0.uid, $0.name) }
|
||||
// Verify current selection is still valid
|
||||
if let currentID = self.selectedDeviceID, !devices.contains(where: { $0.id == currentID }) {
|
||||
self.logger.warning("Currently selected device is no longer available")
|
||||
self.fallbackToDefaultDevice()
|
||||
@ -212,29 +209,33 @@ class AudioDeviceManager: ObservableObject {
|
||||
logger.info("Selected device name: \(name)")
|
||||
}
|
||||
|
||||
if isDeviceAvailable(id) {
|
||||
if let deviceToSelect = availableDevices.first(where: { $0.id == id }), let uid = deviceToSelect.uid {
|
||||
DispatchQueue.main.async {
|
||||
self.selectedDeviceID = id
|
||||
UserDefaults.standard.set(id, forKey: "selectedAudioDeviceID")
|
||||
self.logger.info("Device selection saved")
|
||||
UserDefaults.standard.set(uid, forKey: AudioDeviceManager.selectedAudioDeviceUIDKey)
|
||||
self.logger.info("Device selection saved with UID: \(uid)")
|
||||
self.notifyDeviceChange()
|
||||
}
|
||||
} else {
|
||||
logger.error("Attempted to select unavailable device: \(id)")
|
||||
logger.error("Attempted to select unavailable device or device with no UID: \(id)")
|
||||
fallbackToDefaultDevice()
|
||||
}
|
||||
}
|
||||
|
||||
func selectInputMode(_ mode: AudioInputMode) {
|
||||
inputMode = mode
|
||||
UserDefaults.standard.set(mode.rawValue, forKey: "audioInputMode")
|
||||
UserDefaults.standard.set(mode.rawValue, forKey: AudioDeviceManager.audioInputModeKey)
|
||||
|
||||
if mode == .systemDefault {
|
||||
selectedDeviceID = nil
|
||||
UserDefaults.standard.removeObject(forKey: "selectedAudioDeviceID")
|
||||
UserDefaults.standard.removeObject(forKey: AudioDeviceManager.selectedAudioDeviceUIDKey)
|
||||
} else if selectedDeviceID == nil {
|
||||
if let firstDevice = availableDevices.first {
|
||||
selectDevice(id: firstDevice.id)
|
||||
if inputMode == .custom {
|
||||
if let firstDevice = availableDevices.first {
|
||||
selectDevice(id: firstDevice.id)
|
||||
}
|
||||
} else if inputMode == .prioritized {
|
||||
selectHighestPriorityAvailableDevice()
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,7 +260,7 @@ class AudioDeviceManager: ObservableObject {
|
||||
}
|
||||
|
||||
private func loadPrioritizedDevices() {
|
||||
if let data = UserDefaults.standard.data(forKey: "prioritizedDevices"),
|
||||
if let data = UserDefaults.standard.data(forKey: AudioDeviceManager.prioritizedDevicesKey),
|
||||
let devices = try? JSONDecoder().decode([PrioritizedDevice].self, from: data) {
|
||||
prioritizedDevices = devices
|
||||
logger.info("Loaded \(devices.count) prioritized devices")
|
||||
@ -268,7 +269,7 @@ class AudioDeviceManager: ObservableObject {
|
||||
|
||||
func savePrioritizedDevices() {
|
||||
if let data = try? JSONEncoder().encode(prioritizedDevices) {
|
||||
UserDefaults.standard.set(data, forKey: "prioritizedDevices")
|
||||
UserDefaults.standard.set(data, forKey: AudioDeviceManager.prioritizedDevicesKey)
|
||||
logger.info("Saved \(self.prioritizedDevices.count) prioritized devices")
|
||||
}
|
||||
}
|
||||
@ -285,7 +286,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
let wasSelected = selectedDeviceID == availableDevices.first(where: { $0.uid == id })?.id
|
||||
prioritizedDevices.removeAll { $0.id == id }
|
||||
|
||||
// Reindex remaining devices to ensure continuous priority numbers
|
||||
let updatedDevices = prioritizedDevices.enumerated().map { index, device in
|
||||
PrioritizedDevice(id: device.id, name: device.name, priority: index)
|
||||
}
|
||||
@ -293,7 +293,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
prioritizedDevices = updatedDevices
|
||||
savePrioritizedDevices()
|
||||
|
||||
// If we removed the currently selected device, select the next best option
|
||||
if wasSelected && inputMode == .prioritized {
|
||||
selectHighestPriorityAvailableDevice()
|
||||
}
|
||||
@ -311,28 +310,24 @@ class AudioDeviceManager: ObservableObject {
|
||||
}
|
||||
|
||||
private func selectHighestPriorityAvailableDevice() {
|
||||
// Sort by priority (lowest number = highest priority)
|
||||
let sortedDevices = prioritizedDevices.sorted { $0.priority < $1.priority }
|
||||
|
||||
// Try each device in priority order
|
||||
for device in sortedDevices {
|
||||
if let availableDevice = availableDevices.first(where: { $0.uid == device.id }) {
|
||||
selectedDeviceID = availableDevice.id
|
||||
logger.info("Selected prioritized device: \(device.name) (Priority: \(device.priority))")
|
||||
|
||||
// Actually set the device as the current input device
|
||||
do {
|
||||
try AudioDeviceConfiguration.setDefaultInputDevice(availableDevice.id)
|
||||
UserDefaults.standard.set(availableDevice.id, forKey: "selectedAudioDeviceID")
|
||||
self.notifyDeviceChange()
|
||||
} catch {
|
||||
logger.error("Failed to set prioritized device: \(error.localizedDescription)")
|
||||
continue // Try next device if this one fails
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If no prioritized device is available, fall back to default
|
||||
fallbackToDefaultDevice()
|
||||
}
|
||||
|
||||
@ -345,7 +340,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
|
||||
let systemObjectID = AudioObjectID(kAudioObjectSystemObject)
|
||||
|
||||
// Add listener for device changes
|
||||
let status = AudioObjectAddPropertyListener(
|
||||
systemObjectID,
|
||||
&address,
|
||||
@ -371,21 +365,13 @@ class AudioDeviceManager: ObservableObject {
|
||||
loadAvailableDevices { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// If in prioritized mode, recheck the device selection
|
||||
if self.inputMode == .prioritized {
|
||||
self.selectHighestPriorityAvailableDevice()
|
||||
}
|
||||
// If in custom mode and selected device is no longer available, fallback
|
||||
else if self.inputMode == .custom,
|
||||
let currentID = self.selectedDeviceID,
|
||||
!self.isDeviceAvailable(currentID) {
|
||||
} else if self.inputMode == .custom,
|
||||
let currentID = self.selectedDeviceID,
|
||||
!self.isDeviceAvailable(currentID) {
|
||||
self.fallbackToDefaultDevice()
|
||||
}
|
||||
|
||||
// Notify UI of changes
|
||||
if !self.isRecordingActive {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("AudioDeviceChanged"), object: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +382,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Remove the listener when the manager is deallocated
|
||||
var address = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioHardwarePropertyDevices,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
@ -413,7 +398,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
private func createPropertyAddress(selector: AudioObjectPropertySelector,
|
||||
scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal,
|
||||
element: AudioObjectPropertyElement = kAudioObjectPropertyElementMain) -> AudioObjectPropertyAddress {
|
||||
@ -427,7 +411,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
private func getDeviceProperty<T>(deviceID: AudioDeviceID,
|
||||
selector: AudioObjectPropertySelector,
|
||||
scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal) -> T? {
|
||||
// Skip invalid device IDs
|
||||
guard deviceID != 0 else { return nil }
|
||||
|
||||
var address = createPropertyAddress(selector: selector, scope: scope)
|
||||
@ -452,7 +435,6 @@ class AudioDeviceManager: ObservableObject {
|
||||
}
|
||||
|
||||
private func notifyDeviceChange() {
|
||||
// Only notify if recording is not active
|
||||
if !isRecordingActive {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("AudioDeviceChanged"), object: nil)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user