Detecting the iPhone's Ring / Silent / Mute switch using AVAudioPlayer not working?
Solution 1:
Well I found the answer thanks to someone from the Developer Forums, and you guys aren't gonna like it!
I've had a response from Apple on this.
They've said they don't and never have provided a method for detecting hardware mute switch and don't intend to do so.
:(
IMO there is definitely value in detecting the silent switch and notifying the user in case they've forgotten it was on... I've had people complain that they don't have any sound and the silent switch was the reason! Oh well.
PS: If you would like Apple to add this feature (and of course you do!) please file a new "Enhancement" bug report for "iPhone SDK" at http://bugreport.apple.com/
Update: While there is still no official way to check the status of the mute switch, there's a workaround/library called "SoundSwitch" that seems to do the trick. Check out the new accepted answer for the link.
Solution 2:
"However, if someone could show us how to use this AudioSessionProperty_AudioRouteDescription, the bounty is rightfully his."
Well, just NSLog() the result and you get
routes: {
"RouteDetailedDescription_Inputs" = (
);
"RouteDetailedDescription_Outputs" = (
{
"RouteDetailedDescription_PortType" = Speaker;
}
);
}
Unfortunately, I get that identical result on an iPad2/OS 5.0 both muted and unmuted. So it appears to be functionally equivalent to kAudioSessionProperty_AudioRoute, no joy there.
Looking on the developer boards finds that this is a frequently encountered problem, summed up best with
"I reported this issue with rdar://9781189 back in July and the issue is still present in the GM."
So yeah ... sure looks like you're SOL with this in 5.0.
ADDENDUM:
"But how about that CFDictionary that you are logging. How can I access the "RouteDetailedDescription_PortType" key?"
Toll free bridging is your friend.
CFDictionaryRef asCFType = nil;
UInt32 dataSize = sizeof(asCFType);
AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &dataSize, &asCFType);
NSDictionary *easyPeasy = (NSDictionary *)asCFType;
NSDictionary *firstOutput = (NSDictionary *)[[easyPeasy valueForKey:@"RouteDetailedDescription_Outputs"] objectAtIndex:0];
NSString *portType = (NSString *)[firstOutput valueForKey:@"RouteDetailedDescription_PortType"];
NSLog(@"first output port type is: %@!", portType);
produces
first output port type is: Speaker!
Many common CFTypes are bridged to more convenient types.
http://developer.apple.com/library/ios/#documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html
Now, it takes a little bit of practice to guess correctly what magic incantation casts will get something useful out of a dictionary as above. A class dump helper along these lines will help you get up to speed with that:
- (void)whatsInThis:(CFDictionaryRef)thingy
{
NSDictionary *dict = (NSDictionary *)thingy;
for (NSString *key in dict.allKeys)
{
id value = [dict valueForKey:key];
NSLog(@"key: %@ value type %@", key, [value class]);
if ([value isKindOfClass:[NSArray class]])
{
NSArray *array = (NSArray *)value;
for (id item in array)
{
NSLog(@" -- object type %@", [item class]);
if ([item isKindOfClass:[NSDictionary class]])
[self whatsInThis:item];
}
}
}
}
which for our dictionary at hand produces (adding indentation for clarity)
key: RouteDetailedDescription_Inputs value type __NSCFArray
key: RouteDetailedDescription_Outputs value type __NSCFArray
-- object type __NSCFDictionary
key: RouteDetailedDescription_PortType value type __NSCFString
which lets you know for sure what you could have deduced from the original log, knowing that NSLog displays arrays within ( ) and dictionaries within { } so the correct casts were eminently guessable. But some CFType structures are rather harder to parse than that.
Solution 3:
I went through this VSSilentSwitch library.
Didn't work for me (doesn't work when you start actually using audio).
I was thinking on how he did it, and then realised that the audio completion call is being called almost as soon as the sound begins playing when we're silent.
To be a bit more specific:
System sounds being played using AudioServicesPlaySystemSound
will complete playback as soon as it started.
Of course, this will only work on audio categories that respect the silent switch (the default AVAudioSessionCategoryAmbient
respects it).
So the trick is to create a system sound, preferably of a silent sound, and keep playing it over and over again, while checking the time it took from playback to completion (install a completion procedure using AudioServicesAddSystemSoundCompletion
).
If the completion proc is called very soon (allow some threshold) - it means the silent switch is on.
This trick has many caveats, the biggest one being the fact that it won't work on all audio categories.
If your app plays audio in the background - make sure you stop this test while in the background or your app will run forever in the background (and will be rejected by apple, too).