33

I've tried using these methods in an attempt to detect that the Ring/Silent switch is active or not:

How to programmatically sense the iPhone mute switch?

AVAudioSession category not working as documentation dictates

But on my iPhone 4, the "state" value is always "Speaker" (and the length value returned by CFStringGetLength(state) is always 7). Has anyone used this method successfully? If so, on what kind of device and SDK version?

I'm calling it like so:


- (BOOL)deviceIsSilenced {
    CFStringRef state;
    UInt32 propertySize = sizeof(CFStringRef);
    OSStatus audioStatus = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &state);
    if (audioStatus == kAudioSessionNoError) {
        NSLog(@"audio route: %@", state) // "Speaker" regardless of silent switch setting, but "Headphone" when my headphones are plugged in
        return (CFStringGetLength(state) <= 0);
    }
    return NO;
}

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    audioSession.delegate = self;
    [audioSession setCategory:AVAudioSessionCategoryAmbient error:nil];
    [audioSession setActive:YES error:nil];
    NSLog(@"muted? %i", [self deviceIsSilenced]);
    ...
}

I was thinking maybe some other (more accurate) kAudioSessionProperty event is fired when the physical switch on the phone is ... switched. Anyone have any ideas?

By the way, I'm using the AVAudioSessionCategoryAmbient category with my [AVAudioSession sharedInstance].

Update: I've also tried using different audio categories, and a handful of other audio session properties, none seem to fire when muting/unmuting the switch. :(

Jan. 1, 2014 Update: It's a bit of a hack, and I encountered a crash while multitasking w/ it on my iPhone 5S, but the SoundSwitch library linked in the new accepted answer is the way to go if you want to detect the silent switch. It even works in iOS 7.

Community
  • 1
  • 1
taber
  • 3,166
  • 4
  • 46
  • 72

7 Answers7

31

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.

taber
  • 3,166
  • 4
  • 46
  • 72
  • Such behavior seems reasonable, but not always convenient - this seems about Apple ideology, not just about providing small feature to API. ". I've had people complain that they don't have any sound and the silent switch was the reason!" - I have had much more frequently when people forgot to switch off sound and somebody called when it was not acceptable ;) And if Apple provides access to switch programmatically we will obtain complains about specific application because somebody incorrectly handles silent switch status in app. – sergtk Aug 29 '12 at 12:07
  • 2
    I agree with @taber; it is a shame Apple does not provide access. Of course the property should not be mutable, just available to check the status and let the user know where appropriate. I think the way Apple handles volume in general is very poor. I still do not understand when what channel will be modified when I press the volume down button. I believe most users are not even aware that Apple makes a destination. – lindon fox Nov 24 '12 at 16:59
11

"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.

Alex Curylo
  • 4,744
  • 1
  • 27
  • 37
  • Awesome!! you solved one more step of this problem. Using AudioRoute returns a CFString, which is easy to deal with. But how about that CFDictionary that you are logging. How can I access the "RouteDetailedDescription_PortType" key? – Mazyod Oct 20 '11 at 20:33
  • I.E. There should be something like: CFString s = ExtractObjectWithKey(theCFDictionary, @"RouteDetailedDescription_PortType"); ... See what I mean? – Mazyod Oct 20 '11 at 20:35
  • 1
    hmmm ... can't paste code in a comment? OK, I'll add another answer. – Alex Curylo Oct 21 '11 at 07:50
  • This is a very helpful answer. More documentation can be read in `AudioToolbox/AudioServices.h` but it's a little confusing and jumps around a lot. I personally think its a somewhat overkill to have a NSDictionary of type NSArray of type NSDictionary of type NSString. – eternalmatt May 06 '12 at 14:20
  • Getting the description still just says "Speaker". It still doesnt say if the mute switch is on :( – RPM Jun 25 '12 at 17:49
  • 6
    Thanks - do you have a working `(BOOL) deviceIsSilenced` method that utilizes this method? – Jason Nov 26 '12 at 17:39
4

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).

Moshe Gottlieb
  • 3,963
  • 25
  • 41
  • 1
    The link lives again: http://sharkfood.com/content/Developers/content/Sound%20Switch/ – Moshe Gottlieb Feb 19 '14 at 17:19
  • 1
    The link is dead now, the answer explains how to achieve this by using the apis `AudioServicesAddSystemSoundCompletion`, `AudioServicesPlaySystemSound`. – Moshe Gottlieb Dec 14 '16 at 11:03
  • 3
    People are asking about this, so here it is - I've setup a sample project on github: https://github.com/moshegottlieb/SoundSwitch instead of the dead link. – Moshe Gottlieb Dec 15 '16 at 08:53
  • @Pang The link to VSSilentSwitch was added by someone that edited my answer, not by me. It was an editing mistake and I corrected it. That library is mentioned in another answer for this question (starting with "Found this library") – Moshe Gottlieb Aug 12 '17 at 08:16
  • 1
    The linked github.com/moshegottlieb/SoundSwitch actually does work! Here's a 100% free + ad-free app that I made which demonstrates that library in action: https://apps.apple.com/ge/app/gong-sound/id1579942956 – Jon Schneider Jul 04 '22 at 14:16
  • 1
    @JonSchneider Glad it helps :-) Thanks for the app, I installed it on my phone and now everyone around me wants to break my phone (or turn on the silent switch!) – Moshe Gottlieb Jul 04 '22 at 14:22
0

Try inserting this line above your call to AudioSessionGetProperty inside deviceIsSilenced

AudioSessionInitialize(NULL, NULL, NULL, NULL);

Should then start returning empty string when switch is down (although will show Headphone and some other states if BT headset or accessory connected for example).

FWIW I don't believe there is anything in public API that will fire when actual switch is moved.

flake
  • 1
  • Thanks, I just gave that a shot, but no luck unfortunately. Maybe it's something with using that combined with AVAudioPlayer, hmm. – taber Aug 02 '11 at 21:33
  • I'm using that combined with AVAudioPlayer and it works for me. Actually, I don't check for kAudioSessionNoError - have you stepped through and checked you're not getting one? – Jane Sales Aug 05 '11 at 08:12
  • @taber did you got the ans for the mute switch in on (or) off?. – kannan Nov 16 '12 at 07:47
0

Ok, After following kAudioSessionProperty_AudioRoute using CMD + click, I found this :(

/*!
 @enum           AudioSession audio categories states
 @abstract       Deprecated AudioSession properties
 @constant       kAudioSessionProperty_AudioRoute 
 Deprecated in iOS 5.0; Use kAudioSessionProperty_AudioRouteDescription 
 */
enum {
    kAudioSessionProperty_AudioRoute                            = 'rout',   // CFStringRef      (get only)        
};

turns out we have to use kAudioSessionProperty_AudioRouteDescription, but this guy returns a CFDictionaryRef or something, and I have absolutely no idea how to deal with it....

I made this an answer in case no one shows us how to use kAudioSessionProperty_AudioRouteDescription, where I will try to accept my answer...

However, if someone could show us how to use this kAudioSessionProperty_AudioRouteDescription, the bounty is rightfully his.

edit:

Clearly, this is an iOS 5 problem. I didn't state this earlier because it looked too obvious, but then I thought it might not be as obvious to search engines .. if you get what I mean.

So, iOS 5 is not working with the silent/mute switch on the iPhone because of the referred deprecated value.

Mazyod
  • 22,319
  • 10
  • 92
  • 157
  • You may have trouble getting answers because AFAIK iOS5 SDK is still under NDA. In fact, the answer you posted is iffy in that regard. My advice is to look up the header documentation of the constant you're referring to (right-click & Jump To Definition). You will find your answer there. – Tatiana Racheva Oct 19 '11 at 23:20
  • But the answer may be 'negative' – Tatiana Racheva Oct 19 '11 at 23:41
  • Well, I just want to know how to use CFDictionary in this context .. An CFDictionary expert will answer this question easily.. And I am regretting posting precious bounty for nothing :( – Mazyod Oct 20 '11 at 12:24
  • btw, iOS 5 SDK has been released to the public weeks ago. It's no longer under NDA :) – Mazyod Oct 20 '11 at 12:28
  • Like I said, in the header they explain the layout of the structures. It's quite arcane, and in this case you don't get any different of an answer than the one you get out of the deprecated API. Sigh. – Tatiana Racheva Nov 15 '11 at 21:13
  • Am not sure what you are hinting at .. My Answer is just a lead, it's not a satisfying answer from the start. – Mazyod Nov 17 '11 at 18:32
  • I mean the value you get by using the new API is the same you get from the deprecated API, and on iOS5 it is just as useless in detecting the mute switch state. I'm just saying it doesn't matter how the new API works, it still won't tell you what you want to know. – Tatiana Racheva Nov 26 '11 at 20:31
0

I think you've got the wrong impression. A route is where it's going. You want to know the volume level. Use kAudioSessionProperty_CurrentHardwareOutputVolume

slf
  • 22,595
  • 11
  • 77
  • 101
  • 3
    Sorry, that's totally irrelevant. I am not trying to find the volume level, I am trying to find out if the mute switch is on/off. The code posted in the question works on iOS 4.0. In iOS 5, it got deprecated as I posted. – Mazyod Oct 20 '11 at 15:02
  • 1
    @Mazyod did you find the mute switch button on(or)off?. – kannan Nov 16 '12 at 07:23
  • @user1586650 check alexcurylo's answer. I still haven't implemented it, tbh. – Mazyod Nov 16 '12 at 11:36
  • @user1586650 I said, tbh, I haven't implemented it yet. – Mazyod Nov 16 '12 at 16:04
0

Found this library http://www.verietassoftware.com/index.php?option=com_content&view=article&id=27&Itemid=115

Would Apple allow that stuff? It is a 500Kb library doing some black magic with audio & phone settings

Carl D'Halluin
  • 1,052
  • 10
  • 14