I'm writing a very simple application that plays a sound when pressing a button. Since that button does not make a lot of sense when the device is set to silence I want to disable it when the device's audio volume is zero. (And subsequently reenable it when the volume is cranked up again.)
I am seeking a working (and AppStore safe) way to detect the current volume setting and get a notification/callback when the volume level changes. I do not want to alter the volume setting.
All this is implemented in my ViewController
where said button is used. I've tested this with an iPhone 4 running iOS 4.0.1 and 4.0.2 as well as an iPhone 3G running 4.0.1. Built with iOS SDK 4.0.2 with llvm 1.5. (Using gcc or llvm-gcc doesn't improve anything.) There are no issues during build implementing either way, neither errors nor warnings. Static analyzer is happy as well.
Here is what I've tried so far, all without any success.
Following Apple's audio services documentation I should register an AudioSessionAddPropertyListener
for kAudioSessionProperty_CurrentHardwareOutputVolume
which should work like this:
// Registering for Volume Change notifications
AudioSessionInitialize(NULL, NULL, NULL, NULL);
returnvalue = AudioSessionAddPropertyListener (
kAudioSessionProperty_CurrentHardwareOutputVolume ,
audioVolumeChangeListenerCallback,
self
);
returnvalue
is 0
, which means that registering the callback worked.
Sadly, I never get a callback to my function audioVolumeChangeListenerCallback
when I press the volume buttons on my device, the headset clicker or flip the ringer-silent switch.
When using the exact same code for registering for kAudioSessionProperty_AudioRouteChange
(which is used as an analogous sample project in WWDC videos, Developer documentation and on numerous sites on the interwebs) I actually do get a callback when changing the audio route (by plugging in/out a headset or docking the device).
A user named Doug opened a thread titled iPhone volume changed event for volume already max where he claimed that he is sucessfully using this way (unless the volume would not actually change because it is already set to maximum). Still, it doesn't work for me.
Another way I have tried is to register at NSNotificationCenter
like this.
// sharedAVSystemController
AudioSessionInitialize(NULL, NULL, NULL, NULL);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(volumeChanged:)
name:@"AVSystemController_SystemVolumeDidChangeNotification"
object:nil];
This should notify my method volumeChanged
of any SystemVolume
changes but it doesn't actually do so.
Since common belief tells me that if one is working too hard to achieve something with Cocoa one is doing something fundamentally wrong I'm expecting to miss something here. It's hard to believe that there is no simple way to get the current volume level, yet I haven't been able to find one using Apple's documentation, sample code, Google, Apple Developer Forums or by watching WWDC 2010 videos.
You can separate your system sound from your notification/ringer sound under Settings -> Sounds & Haptics. Under Ringers and Alerts, toggle off "Change with Buttons." Now you will be able to turn up/down volume on your music, movies and other media without affecting the ringer/notification volume.
If you go to Settings > Sounds & Haptics, you can adjust the volume of notifications and alerts. You can enable the option to Change with Buttons on the Sounds & Haptics screen to make it so your volume buttons also change your ringer and alert volumes.
It sounds like this could be related to the Attention Aware features of your iPhone. With this on 'If you're looking at your device, it will lower the volume sound of your alerts.
Any chance you did your signature wrong for the volumeChanged: method? This worked for me, dumped in my appdelegate:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(volumeChanged:)
name:@"AVSystemController_SystemVolumeDidChangeNotification"
object:nil];
}
- (void)volumeChanged:(NSNotification *)notification
{
float volume =
[[[notification userInfo]
objectForKey:@"AVSystemController_AudioVolumeNotificationParameter"]
floatValue];
// Do stuff with volume
}
My volumeChanged: method gets hit every time the button is pressed, even if the volume does not change as a result (because it's already at max/min).
The AudioSession
API used by some answers here has been deprecated as of iOS 7. It was replaced by AVAudioSession
, which exposes an outputVolume
property for the system wide output volume. This can be observed using KVO to receive notifications when the volume changes, as pointed out in the documentation:
A value in the range 0.0 to 1.0, with 0.0 representing the minimum volume and 1.0 representing the maximum volume.
The system wide output volume can be set directly only by the user; to provide volume control in your app, use the MPVolumeView class.
You can observe changes to the value of this property by using key-value observing.
You need to ensure your app's audio session is active for this to work:
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setActive(true)
startObservingVolumeChanges()
} catch {
print(“Failed to activate audio session")
}
So if all you need is to query the current system volume:
let volume = audioSession.outputVolume
Or we can be notified of changes like so:
private struct Observation {
static let VolumeKey = "outputVolume"
static var Context = 0
}
func startObservingVolumeChanges() {
audioSession.addObserver(self, forKeyPath: Observation.VolumeKey, options: [.Initial, .New], context: &Observation.Context)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &Observation.Context {
if keyPath == Observation.VolumeKey, let volume = (change?[NSKeyValueChangeNewKey] as? NSNumber)?.floatValue {
// `volume` contains the new system output volume...
print("Volume: \(volume)")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
Don't forget to stop observing before being deallocated:
func stopObservingVolumeChanges() {
audioSession.removeObserver(self, forKeyPath: Observation.VolumeKey, context: &Observation.Context)
}
-(float) getVolumeLevel
{
MPVolumeView *slide = [MPVolumeView new];
UISlider *volumeViewSlider;
for (UIView *view in [slide subviews]){
if ([[[view class] description] isEqualToString:@"MPVolumeSlider"]) {
volumeViewSlider = (UISlider *) view;
}
}
float val = [volumeViewSlider value];
[slide release];
return val;
}
That should get you the current volume level. 1 is max volume, 0 is no volume. Note: no UI elements need to be displayed for this to work. Also note current volume level is relative to headphones or speakers (meaning, the two volume levels are different, and this gets you whichever the device is currently using. This doesn't answer your question regarding receiving notifications of when volume changes.
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