From cfaedb29756be3f9271c0d2d3dd9b47a917281e1 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Wed, 11 Nov 2015 09:21:08 -0500 Subject: [PATCH] Listen for device changes. Refresh menu on device change. Terminate when closing menu and no active processors. --- SyllableDetector/AudioInterface.swift | 78 +++++++++++++++++++++++ SyllableDetector/ViewControllerMenu.swift | 24 +++++-- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/SyllableDetector/AudioInterface.swift b/SyllableDetector/AudioInterface.swift index bdd4264..87a6ce0 100644 --- a/SyllableDetector/AudioInterface.swift +++ b/SyllableDetector/AudioInterface.swift @@ -91,6 +91,9 @@ private func checkError(status: OSStatus, type: AudioInterfaceError? = nil, func class AudioInterface { + // event listeners + static var listeners = [AudioObjectPropertySelector: Array<(Int, (() -> Void))>]() + struct AudioDevice { let deviceID: AudioDeviceID let deviceUID: String @@ -247,6 +250,81 @@ class AudioInterface return AudioDevice(deviceID: $0) } } + + static func createListenerForDeviceChange(cb: (Void) -> Void, withIdentifier unique: T) throws { + let selector = kAudioHardwarePropertyDevices + + // alread has listener + if var cur = listeners[selector] { + // add callback + cur.append((unique.hashValue, cb)) + + // update listeners + listeners[selector] = cur + + return + } + + // property address + var propertyAddress = AudioObjectPropertyAddress(mSelector: selector, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster) + let onAudioObject = AudioObjectID(bitPattern: kAudioObjectSystemObject) + + // create listener + try checkError(AudioObjectAddPropertyListenerBlock(onAudioObject, &propertyAddress, nil, dispatchEvent)) + + // create callbacks array + let cbs: Array<(Int, (() -> Void))> = [(unique.hashValue, cb)] + listeners[selector] = cbs + } + + static func destroyListenerForDeviceChange(withIdentifier unique: T) { + let selector = kAudioHardwarePropertyDevices + + guard var cur = listeners[selector] else { + // nothing to remove + return + } + + // hash to remove + let hashToRemove = unique.hashValue + + // remove from listeners + cur = cur.filter() { + return $0.0 != hashToRemove + } + + // if empty + if cur.isEmpty { + // remove listener + listeners.removeValueForKey(selector) + + // property address + var propertyAddress = AudioObjectPropertyAddress(mSelector: selector, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster) + let onAudioObject = AudioObjectID(bitPattern: kAudioObjectSystemObject) + + do { + try checkError(AudioObjectRemovePropertyListenerBlock(onAudioObject, &propertyAddress, nil, dispatchEvent)) + } + catch { + DLog("unable to remove listener") + } + } + else { + // update listeners + listeners[selector] = cur + } + } + + static func dispatchEvent(numAddresses: UInt32, addresses: UnsafePointer) { + for var i: UInt32 = 0; i < numAddresses; ++i { + let selector = addresses[Int(i)].mSelector + if let listenersForSelector = listeners[selector] { + for entry in listenersForSelector { + dispatch_async(dispatch_get_main_queue(), entry.1) + } + } + } + } } class AudioOutputInterface: AudioInterface diff --git a/SyllableDetector/ViewControllerMenu.swift b/SyllableDetector/ViewControllerMenu.swift index 27d56dd..0c8c38f 100644 --- a/SyllableDetector/ViewControllerMenu.swift +++ b/SyllableDetector/ViewControllerMenu.swift @@ -22,12 +22,28 @@ class ViewControllerMenu: NSViewController, WindowControllerProcessorDelegate { // reload devices buttonLaunch.enabled = false reloadDevices() + + // listen + do { + try AudioInterface.createListenerForDeviceChange({ + DLog("refreshing device") + self.reloadDevices() + }, withIdentifier: self) + } + catch { + DLog("Unable to add device change listener: \(error)") + } } -// override func viewDidDisappear() { -// // terminate -// NSApp.terminate(nil) -// } + override func viewDidDisappear() { + // remove listener + AudioInterface.destroyListenerForDeviceChange(withIdentifier: self) + + // terminate + if 0 == openProcessors.count { + NSApp.terminate(nil) + } + } func reloadDevices() { // fetch list of devices