How to get notification if System Preferences Default Sound changed

后端 未结 2 395
鱼传尺愫
鱼传尺愫 2021-02-06 16:15

This is fairly simple (I think). I\'m simply wanting to get a notification in my application whenever a user changes the default sound input or sound output device in System Pr

相关标签:
2条回答
  • 2021-02-06 16:47

    Set up an AudioObjectPropertyAddress for the default output device:

    AudioObjectPropertyAddress outputDeviceAddress = {
        kAudioHardwarePropertyDefaultOutputDevice,
        kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMaster
    };
    

    Then, use AudioObjectAddPropertyListener to register a listener for changes in the default device:

    AudioObjectAddPropertyListener(kAudioObjectSystemObject, 
                                     &outputDeviceAddress,
                                     &callbackFunction, nil);
    

    The callback function looks like this:

    OSStatus callbackFunction(AudioObjectID inObjectID, 
                                UInt32 inNumberAddresses,
                                const AudioObjectPropertyAddress inAddresses[],             
                                void *inClientData)
    

    As a side note, you should also use AudioObjectPropertyAddress to tell the HAL to manage its own thread for notifications. You do this by setting the run loop selector to NULL. I actually perform this step before setting up the output device listener.

    AudioObjectPropertyAddress runLoopAddress = {
        kAudioHardwarePropertyRunLoop, 
        kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMaster
    };
    
    CFRunLoopRef runLoop = NULL;
    UInt32 size = sizeof(CFRunLoopRef);
    AudioObjectSetPropertyData(kAudioObjectSystemObject, 
                                &runLoopAddress, 0, NULL, size, &runLoop);
    
    0 讨论(0)
  • 2021-02-06 17:12

    Here's how to do it in Swift:

    1. Register for notifications by adding a listener block, for example when a View Controller loads its view. The addListenerBlock function simplifies adding property listener blocks. Swift allows parameters to be variables, which is very convenient for the forPropertyAddress parameter. When calling addListenerBlock, the property address parameters are just plugged in.

      import Cocoa
      import CoreAudio
      
      class ViewController: NSViewController {
      
         // Utility function to simplify adding listener blocks:
         func addListenerBlock( listenerBlock: AudioObjectPropertyListenerBlock, onAudioObjectID: AudioObjectID, var forPropertyAddress: AudioObjectPropertyAddress) {
            if (kAudioHardwareNoError != AudioObjectAddPropertyListenerBlock(onAudioObjectID, &forPropertyAddress, nil, listenerBlock)) {
                print("Error calling: AudioObjectAddPropertyListenerBlock") }
         }
      
      
         override func viewDidLoad() { super.viewDidLoad()
      
            addListenerBlock(audioObjectPropertyListenerBlock, 
               onAudioObjectID: AudioObjectID(bitPattern: kAudioObjectSystemObject),
               forPropertyAddress: AudioObjectPropertyAddress(
                  mSelector: kAudioHardwarePropertyDefaultOutputDevice,
                  mScope: kAudioObjectPropertyScopeGlobal,
                  mElement: kAudioObjectPropertyElementMaster))
         }
      
      ...
      
    2. Provide a listener block function to receive the notifications. You're given an array that could potentially have more than one property address, so loop through and look for the one you want:

         func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) {
      
            var index: UInt32 = 0
            while index < numberAddresses {
                let address: AudioObjectPropertyAddress = addresses[0]
                switch address.mSelector {
                case kAudioHardwarePropertyDefaultOutputDevice:
      
                    let deviceID = getDefaultAudioOutputDevice()
                    print("kAudioHardwarePropertyDefaultOutputDevice: \(deviceID)")
      
                default:
      
                    print("We didn't expect this!")
      
                }
                index += 1
           }
      
           // Utility function to get default audio output device:
           func getDefaultAudioOutputDevice () -> AudioObjectID {
      
               var devicePropertyAddress = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDefaultOutputDevice, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster)
               var deviceID: AudioObjectID = 0
               var dataSize = UInt32(truncatingBitPattern: sizeof(AudioDeviceID))
               let systemObjectID = AudioObjectID(bitPattern: kAudioObjectSystemObject)
               if (kAudioHardwareNoError != AudioObjectGetPropertyData(systemObjectID, &devicePropertyAddress, 0, nil, &dataSize, &deviceID)) { return 0 }
               return deviceID
           }
      
      }
      

    Of course, you can simply add more cases to the switch statement if you want to monitor other audio device properties. Call addListenerBlock to add whatever devices and property addresses you're interested in.

    0 讨论(0)
提交回复
热议问题