I am working with CoreAudio in swift and needed to find when the user changes the system volume.
I can get the volume correctly and even add a property listener to find when the user changes the volume. But I need to stop listening at some point (if the user changes the default output device) but I am not able to remove the property listener.
I have created a basic test for playground and made the same test in a command line obj-c project. The test works fine in obj-c but it does not work in swift
The code just adds the listener and then removes it, so changing the volume after running the code should print nothing, but in swift it keeps printing
the swift code:
import CoreAudio
//first get default output device
var outputDeviceAOPA:AudioObjectPropertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster)
var outputDeviceID = kAudioObjectUnknown
var propertySize = UInt32(sizeof(AudioDeviceID))
AudioObjectGetPropertyData(UInt32(kAudioObjectSystemObject), &outputDeviceAOPA,
0, nil, &propertySize, &outputDeviceID)
// get volume from device
var volumeAOPA:AudioObjectPropertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioDevicePropertyVolumeScalar,
mScope: kAudioObjectPropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster
)
var volume:Float32 = 0.5
var volSize = UInt32(sizeof(Float32))
AudioObjectGetPropertyData(outputDeviceID, &volumeAOPA, 0, nil, &volSize, &volume)
print(volume)
var queue = dispatch_queue_create("testqueue", nil)
var listener:AudioObjectPropertyListenerBlock = {
_, _ in
AudioObjectGetPropertyData(outputDeviceID, &volumeAOPA, 0, nil, &volSize, &volume)
print(volume)
}
AudioObjectAddPropertyListenerBlock(outputDeviceID, &volumeAOPA, queue, listener)
AudioObjectRemovePropertyListenerBlock(outputDeviceID, &volumeAOPA, queue, listener)
while true{
//keep playground running
}
And this is the objective-c code:
//objective-c code working
// main.m
// objccatest
#import <Foundation/Foundation.h>
#import <CoreAudio/CoreAudio.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//first get default output device
AudioObjectPropertyAddress outputDeviceAOPA;
outputDeviceAOPA.mSelector= kAudioHardwarePropertyDefaultOutputDevice;
outputDeviceAOPA.mScope= kAudioObjectPropertyScopeGlobal;
outputDeviceAOPA.mElement= kAudioObjectPropertyElementMaster;
AudioObjectID outputDeviceID = kAudioObjectUnknown;
UInt32 propertySize = sizeof(AudioDeviceID);
AudioObjectGetPropertyData(kAudioObjectSystemObject, &outputDeviceAOPA,
0, nil, &propertySize, &outputDeviceID);
// get volume from device
AudioObjectPropertyAddress volumeAOPA;
volumeAOPA.mSelector= kAudioDevicePropertyVolumeScalar;
volumeAOPA.mScope= kAudioObjectPropertyScopeOutput;
volumeAOPA.mElement= kAudioObjectPropertyElementMaster;
Float32 volume = 0.5;
UInt32 volSize = sizeof(Float32);
AudioObjectGetPropertyData(outputDeviceID, &volumeAOPA, 0, nil, &volSize, &volume);
NSLog(@"%f", volume);
dispatch_queue_t queue = dispatch_queue_create("testqueue", nil);
AudioObjectPropertyListenerBlock listener = ^(UInt32 a, const AudioObjectPropertyAddress* arst){
AudioObjectGetPropertyData(outputDeviceID, &volumeAOPA, 0, nil, &volSize, &volume);
NSLog(@"%f", volume);
};
AudioObjectAddPropertyListenerBlock(outputDeviceID, &volumeAOPA, queue, listener);
AudioObjectRemovePropertyListenerBlock(outputDeviceID, &volumeAOPA, queue, listener);
while (true){
//keep app running
}
}
return 0;
}
I think this is a bug in the Core Audio API, but maybe there is a workaround or obj-c blocks work different to the swift closures.
Yeah, it could be a bug actually because the listener block can not be removed by AudioObjectRemovePropertyListenerBlock. However, I find that to register an AudioObjectPropertyListenerProc with an AudioObject can be a workaround with Swift.
//var queue = dispatch_queue_create("testqueue", nil)
//var listener:AudioObjectPropertyListenerBlock = {
// _, _ in
// AudioObjectGetPropertyData(outputDeviceID, &volumeAOPA, 0, nil, &volSize, &volume)
// print(volume)
//}
//
//AudioObjectAddPropertyListenerBlock(outputDeviceID, &volumeAOPA, queue, listener)
//AudioObjectRemovePropertyListenerBlock(outputDeviceID, &volumeAOPA, queue, listener)
var data: UInt32 = 0
func listenerProc() -> AudioObjectPropertyListenerProc {
return { _, _, _, _ in
AudioObjectGetPropertyData(outputDeviceID, &volumeAOPA, 0, nil, &volSize, &volume)
print(volume)
return 0
}
}
AudioObjectAddPropertyListener(outputDeviceID, &volumeAOPA, listenerProc(), &data)
AudioObjectRemovePropertyListener(outputDeviceID, &volumeAOPA, listenerProc(), &data)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With