Send system sounds the right way (not over AirPlay)
August 31, 2012
This is another little bit of code that I needed for my Mac app Break Reminder. When it's time for a break, Break Reminder can optionally play a sound to get your attention.
I use the NSSound API to play a simple chime-style sound, which can look something like:
[[NSSound soundNamed:@"Chime"] play];
Starting with OS X 10.8 (Mountain Lion), the user can choose to set an AirPlay device as sound output, which can be something like an AirPort Express connected to a stereo. This is great for music and the like, but for chimes, not so great!
Fortunately, there is a pretty simple solution. NSSound has a method to set the playback device to use:
NSSound *sound = [NSSound soundNamed:@"Chime"];
NSString *deviceID = ...
[sound setPlaybackDeviceIdentifier:deviceID];
[sound play];
We just need to get the device for system-kind-of-sounds, like the built-in chimes and boings and whatnots. We can use CoreAudio to get the identifier for that:
- (NSString *)systemAudioDeviceID
{
AudioDeviceID systemOutputDevice = 0;
UInt32 size = sizeof(AudioDeviceID);
AudioObjectPropertyAddress address = {
kAudioHardwarePropertyDefaultSystemOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &systemOutputDevice);
if (err != noErr) {
... handle error
return nil;
}
CFStringRef deviceUID = NULL;
size = sizeof(deviceUID);
AudioObjectPropertyAddress uidAddress = {
kAudioDevicePropertyDeviceUID,
kAudioDevicePropertyScopeOutput,
/*channel*/ 0
};
err = AudioObjectGetPropertyData(systemOutputDevice, &uidAddress, 0, NULL, &size, &deviceUID);
if (err != noErr) {
... handle error
return nil;
}
return (__bridge_transfer NSString *)deviceUID;
}
Just plug the identifier into the NSSound and you should be good to go.
Hope this helps!