使用AVAudioPlayer检测iPhone的响铃/静音/静音开关无效?

34

我尝试使用以下方法来检测Ring/Silent开关是否处于活动状态:

如何以编程方式感知iPhone静音开关?

AVAudioSession类别未按照文档规定工作

但在我的iPhone 4上,“state”值始终为“Speaker”(CFStringGetLength(state)返回的长度值始终为7)。有人成功使用过这种方法吗?如果是,使用的是哪种设备和SDK版本?

我是这样调用它的:


- (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]);
    ...
}

我在想,也许当手机上的物理开关被切换时,可能会触发一些其他(更准确的)kAudioSessionProperty事件。有人有什么想法吗?

顺便说一下,我正在使用AVAudioSessionCategoryAmbient类别和我的[AVAudioSession sharedInstance]。

更新:我还尝试使用不同的音频类别和一些其他音频会话属性,但似乎没有一个能在静音/取消静音开关时触发。 :(

2014年1月1日更新:如果您想检测静音开关,则SoundSwitch库是正确的选择,虽然它有点像黑客,而且我在我的iPhone 5S上使用多任务处理它时遇到了崩溃,但它甚至可以在iOS 7中工作。


[self deviceIsSilenced] 是什么? - Jane Sales
我已经更新了帖子,加入了deviceIsSilenced,感谢你的提问,Jane! :) - taber
就我个人而言,它对我也不起作用... - Reuven
我正要发布一个完全重复的帖子(iPhone 4等)。 - Mazyod
请注意:我使用了来自https://dev59.com/eXVC5IYBdhLWcg3wdw25#6910574的环境开关,但没有用... - Mazyod
7个回答

32

我在开发者论坛上得到了答案,你们可能不喜欢它。

 

我收到了苹果公司的回复。

    

他们说他们从未提供过检测硬件静音开关的方法,也没有意图这样做。

:(

在我看来,检测静音开关并在用户忘记打开时通知用户确实有价值...我曾经有人抱怨声音没有了,原因就是静音开关! 哎呀。

PS:如果您想让苹果公司增加此功能(当然您希望这样做!),请在“iPhone SDK”中提交一个新的“增强”错误报告,网址为http://bugreport.apple.com/

更新:虽然仍然没有官方方法来检查静音开关的状态,但有一个名为“SoundSwitch”的解决方案/库似乎可以解决问题。 查看接受的新答案以获取链接。


这种行为似乎是合理的,但并不总是方便 - 这似乎是关于苹果的意识形态,而不仅仅是提供API的小功能。"我曾经有人抱怨他们没有声音,而静音开关就是原因!" - 我更经常遇到的情况是人们忘记关闭声音,当不适当时有人打电话;) 如果苹果提供程序化访问开关,我们将会收到有关特定应用程序的投诉,因为某些人在应用程序中不正确地处理了静音开关状态。 - sergtk
2
我同意@taber的观点;很遗憾苹果没有提供访问权限。当然,该属性不应该是可变的,只能用于检查状态并在适当时通知用户。我认为苹果处理音量的方式非常糟糕。我仍然不明白按下音量减小按钮时会修改哪个通道。我相信大多数用户甚至不知道苹果会创建一个目标。 - lindon fox

11

“不过,如果有人能向我们展示如何使用AudioSessionProperty_AudioRouteDescription,悬赏将属于他。”

好的,只需使用NSLog()打印结果,你就会得到:

routes: {
    "RouteDetailedDescription_Inputs" =     (
    );
    "RouteDetailedDescription_Outputs" =     (
                {
            "RouteDetailedDescription_PortType" = Speaker;
        }
    );
}

很不幸,我在iPad2/OS 5.0上无论是否静音都得到了相同的结果。因此,它似乎在功能上等同于kAudioSessionProperty_AudioRoute,没有任何作用。

查看开发者论坛发现这是一个经常遇到的问题,最好的总结是:

"我在7月份报告了rdar://9781189中的这个问题,但该问题在GM版本中仍然存在。"

所以......看起来在5.0中你似乎没有解决的希望。

附加说明:

"但是,你记录的那个CFDictionary怎么样?我怎样访问“RouteDetailedDescription_PortType”关键字?"

桥接非托管类型是你的朋友。

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

输出:

第一个输出端口类型是:音箱!

许多常见的CFTypes被桥接到更方便的类型。

http://developer.apple.com/library/ios/#documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html

现在,需要一些练习来猜测从上述字典中提取有用信息的咒语。以下类转储工具将帮助您快速掌握:

  - (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];
           }
        }
     }
  }

针对我们手头的字典,这将产生以下结果(为了清晰起见添加缩进)

key: RouteDetailedDescription_Inputs value type __NSCFArray
key: RouteDetailedDescription_Outputs value type __NSCFArray
  -- object type __NSCFDictionary
    key: RouteDetailedDescription_PortType value type __NSCFString

这让你可以确切地知道你可以从原始日志中推断出的内容,因为 NSLog 在括号 ( ) 中显示数组,并在花括号 { } 中显示字典,因此正确的转换是显而易见的。但有些 CFType 结构比那更难解析。


太棒了!!你解决了这个问题的另一个步骤。使用AudioRoute返回一个CFString,很容易处理。但是那个你正在记录的CFDictionary怎么样呢?我怎么才能访问“RouteDetailedDescription_PortType”键? - Mazyod
CFString s = ExtractObjectWithKey(theCFDictionary, @"RouteDetailedDescription_PortType"); 这里的意思是提取theCFDictionary中键为“RouteDetailedDescription_PortType”的对象。你明白我的意思吗? - Mazyod
1
嗯...无法在评论中粘贴代码?好的,我会添加另一个答案。 - Alex Curylo
这是一个非常有帮助的答案。更多的文档可以在AudioToolbox/AudioServices.h中阅读,但它有点混乱并且跳来跳去。我个人认为,将NSDictionary类型的NSArray类型的NSDictionary类型的NSString类型有些过度了。 - eternalmatt
获取描述仍然只显示“扬声器”。它仍然没有说麦克风静音开关是否打开 :( - RPM
6
谢谢 - 你有一个可用的 (BOOL) deviceIsSilenced 方法来使用这个方法吗? - Jason

4
我查看了这个VSSilentSwitch库,但它对我没有用(当实际使用音频时不起作用)。我在考虑他是如何做到的,然后意识到在我们静音时,音频完成调用几乎同时被调用,当声音开始播放时。更具体地说,使用AudioServicesPlaySystemSound播放的系统声音会在开始播放时立即完成播放。当然,这仅适用于尊重静音开关的音频类别(默认的AVAudioSessionCategoryAmbient尊重它)。因此,诀窍是创建一个系统声音,最好是无声声音,并一遍又一遍地播放它,同时检查从播放到完成所需的时间(使用AudioServicesAddSystemSoundCompletion安装完成过程)。如果完成程序很快就被调用(允许一些阈值),那么就表示静音开关打开了。此技巧有许多注意事项,其中最大的注意事项是它不能在所有音频类别上工作。如果您的应用程序在后台播放音频,请确保在后台停止此测试,否则您的应用程序将永远在后台运行(并且也将被苹果拒绝)。

1
链接已经恢复:http://sharkfood.com/content/Developers/content/Sound%20Switch/ - Moshe Gottlieb
1
链接已失效,答案解释了如何使用API AudioServicesAddSystemSoundCompletionAudioServicesPlaySystemSound来实现此功能。 - Moshe Gottlieb
3
有人询问此事,因此在这里提供答案——我已在Github上建立了一个样例项目:https://github.com/moshegottlieb/SoundSwitch,以替代失效的链接。 - Moshe Gottlieb
@Pang 链接到VSSilentSwitch是由编辑我的答案的某人添加的,而不是我。这是一个编辑错误,我已经更正了。该库在另一个回答中提到了这个问题(以“Found this library”开头)。 - Moshe Gottlieb
1
这个链接的github.com/moshegottlieb/SoundSwitch确实有效!这是一个我制作的100%免费+无广告的应用程序,演示了该库的功能:https://apps.apple.com/ge/app/gong-sound/id1579942956 - Jon Schneider
1
@JonSchneider 很高兴能帮到你 :-) 感谢这个应用程序,我已经在我的手机上安装了它,现在周围的每个人都想打破我的手机(或者打开静音开关!) - Moshe Gottlieb

0

我认为你有误解。路由是它的路径,你想知道的是音量级别。请使用kAudioSessionProperty_CurrentHardwareOutputVolume


3
抱歉,那与问题完全无关。我不是在寻找音量级别,而是想知道静音开关是开还是关。问题中发布的代码适用于iOS 4.0。在iOS 5中,它已被弃用,正如我所述。 - Mazyod
1
@Mazyod,你找到了静音开关按钮是打开还是关闭了吗? - kannan
@user1586650,可以看一下alexcurylo的回答。说实话,我还没有实现它。 - Mazyod
@user1586650,说实话,我还没有实现它。 - Mazyod

0

尝试在deviceIsSilenced内部调用AudioSessionGetProperty之前插入以下行:

AudioSessionInitialize(NULL, NULL, NULL, NULL);

这样当开关关闭时,应该会返回空字符串(如果连接了BT耳机或配件,则仍会显示Headphone和其他一些状态)。

顺便说一句,我不认为公共API中有任何东西会在实际开关移动时触发。


谢谢,我刚试了一下,但不幸的是没有成功。也许是使用它与AVAudioPlayer结合的问题,嗯。 - taber
我正在使用这个与AVAudioPlayer结合使用,它对我很有效。实际上,我没有检查kAudioSessionNoError - 你有没有逐步调试并检查你是否没有得到一个? - Jane Sales
@taber,你得到了静音开关打开或关闭的答案吗? - kannan

0

请看我的回答,以开源格式提供与VSSilentSwitch相同的功能。 - Moshe Gottlieb

0

好的,在CMD + 单击后跟随 kAudioSessionProperty_AudioRoute,我找到了这个:

/*!
 @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)        
};

原来我们必须使用kAudioSessionProperty_AudioRouteDescription,但是这个函数返回一个CFDictionaryRef或其他一些东西,我绝对不知道该如何处理它......

如果没有人告诉我们如何使用kAudioSessionProperty_AudioRouteDescription,我会将我的回答作为答案...

但是,如果有人能够向我们展示如何使用这个kAudioSessionProperty_AudioRouteDescription,那么奖励就应该属于他。

编辑:

显然,这是一个iOS 5的问题。我之前没有说明这一点,因为看起来太明显了,但后来我想这可能对搜索引擎不够清晰,如果你明白我的意思。

所以,由于相关的弃用值,iOS 5不能与iPhone上的静音/静音开关配合使用。


你可能会遇到回答困难的问题,因为据我所知,iOS5 SDK仍然受到保密协议的限制。事实上,你提供的答案在这方面有些含糊不清。我的建议是查阅你所引用的常量的头文件文档(右键点击并跳转到定义)。你会在那里找到你的答案。 - Tatiana Racheva
但答案可能是“否定的”。 - Tatiana Racheva
顺便说一句,iOS 5 SDK已经几周前发布给公众了。它不再受NDA的限制:) - Mazyod
我不确定你在暗示什么..我的回答只是一个线索,从一开始就不是一个令人满意的答案。 - Mazyod
我的意思是,使用新的API得到的值与使用已弃用的API得到的值相同,在iOS5中,它在检测静音开关状态方面同样无用。我只是想说,无论新API如何工作,它仍然不能告诉你想要知道的内容。 - Tatiana Racheva
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接