Skip to content

Commit

Permalink
Merge pull request #5 from nathanntg/device-refresh
Browse files Browse the repository at this point in the history
Listen for device changes and refresh menu.
  • Loading branch information
nathanntg committed Nov 11, 2015
2 parents f2885d8 + cfaedb2 commit 8e06c16
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 4 deletions.
78 changes: 78 additions & 0 deletions SyllableDetector/AudioInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -247,6 +250,81 @@ class AudioInterface
return AudioDevice(deviceID: $0)
}
}

static func createListenerForDeviceChange<T: Hashable>(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<T: Hashable>(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<AudioObjectPropertyAddress>) {
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
Expand Down
24 changes: 20 additions & 4 deletions SyllableDetector/ViewControllerMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8e06c16

Please sign in to comment.