程序如何访问iPhone的音量按钮?

24

有没有办法订阅音量按钮的按下事件?

5个回答

22

在最近被苹果驳回之后

不要使用这个。现在苹果使用了一些补丁,如果应用程序使用任何私有的API,它将立即拒绝你的应用程序 - 尽管应该注意到App Store上已经有很多应用程序在使用这个功能并且仍然存在!

现在唯一的方法是准备一个AVAudioPlayer以播放但不播放([player prepareToPlay])。 这似乎可以负责根据摇杆按钮调整应用程序的音量。

目前没有其他公开的处理此问题的方法。

请阅读上面的说明

是的,使用MPVolumeView。

MPVolumeView *volume = [[[MPVolumeView alloc] initWithFrame:CGRectMake(18.0, 340.0, 284.0, 23.0)] autorelease];
  [[self view] addSubview:volume];

  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(volumeChanged:) 
                                        name:@"AVSystemController_SystemVolumeDidChangeNotification" 
                                        object:nil];    
  for (UIView *view in [volume subviews]){
    if ([[[view class] description] isEqualToString:@"MPVolumeSlider"]) {
      volumeViewSlider = view;  //volumeViewSlider is a UIView * object
    }
  }
  [volumeViewSlider _updateVolumeFromAVSystemController];

-(IBAction)volumeChanged:(id)sender{
  [volumeViewSlider _updateVolumeFromAVSystemController];
}

这将给你一个滑块(与iPod中使用的相同),其值将根据手机音量而变化。

你会得到一个编译时警告,说视图可能无法响应 _updateVolumeFromAVSystemControl,但只需忽略它。


1
是的,我在考虑监听VolumeChanged事件,但我想知道当没有实际的音量变化发生时(例如音量已经最大并且我正在增加它),它是否仍会接收到消息? - COTOHA
你可以在设备上尝试运行代码,看看是否收到了事件。 - lostInTransit
2
你不能使用这段代码发布应用程序,它使用了私有API。这就是编译时警告的原因,而下划线前缀也是一个提示。 - duncanwilcox
6
您可以使用MPMusicPlayerController *musicPlayer = [MPMusicPlayerController iPodMusicPlayer]; musicPlayer.volume = newVolume;来更改音量值。这并不是私有的,将会更改音量值。 - JackPearse

17

如果你只想要接收通知,我认为应该是这样的:

如果我错了,请纠正我,但我不认为这使用任何内部API。

[[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(volumeChanged:) 
        name:@"AVSystemController_SystemVolumeDidChangeNotification" 
        object:nil];

这个事件的详细信息在这里:http://www.cocoadev.com/index.pl?AVSystemController

其他回答似乎是基于这个 hack 的:http://blog.stormyprods.com/2008/09/proper-usage-of-mpvolumeview-class.html,这是一个现在已经修复的问题的解决方法。

但是我很确定,如果你只想获取通知而不是设置系统音量,你就可以像处理其他事件一样使用通知中心!!

请注意:自从苹果将音量上升操作添加到相机后,当一个UIImagePickerController可见时,此通知会不会被发布。


3
请注意,当 UIImagePickerController(例如相机)可见时,不会发送此通知。 - William Denniss
5
只有在我添加了一个不可见的MPVolume视图后,这才对我起作用。也就是说,只需将以下内容添加到您的代码中:#import <MediaPlayer/MediaPlayer.h>MPVolumeView *slide = [MPVolumeView new]; - Erpheus
1
@Erpheus 很遗憾,UIImagePickerController 的限制仍然存在。即使向 UIImagePickerController 视图添加幻灯片,事件也不会触发。 - William Denniss
这是私有 API 吗? - Ivan Lesko

2
在研究上述所有来源和其他线程时,我发现最简单且功能完整的方法是:JPSVolumeButtonHandler(我只是一个用户,没有其他参与者。但非常感谢负责人!)
编辑:发布1.0.2版本带来了一些重大变化/增强。我将保留我的早期答案1.0.1。
我放置了一个示例包装类,您可以直接部署,或使用它来学习JPSVolumeButtonHandler的正确用法在这里快速查看GitHub存储库
以下是包装器的使用方法(我会尽快将其添加到存储库中):
单例类有两个标志:isInUseisOnisInUse应该在某种通用应用程序设置中进行设置,并且总体上开关按钮支持。因此,无论类中的任何其他值如何,如果这个值为false,当用户按下音量按钮时不会发生任何事情,并且实现尽可能保持清洁,不会不必要地影响系统音量级别。(阅读README中提到的问题,了解第一次打开按钮支持时可能发生的情况。)isOn的意思是在需要按钮的时间内确切地为true。您可以随时打开和关闭它,而不考虑isInUse的当前值。
无论在哪个视图中初始化按下音量按钮时应该发生的操作,都可以像这样设置操作:

PhysicalButton.shared.action = { /* do something */ } 该操作的类型为() -> Void。在您初始化操作之前,不会发生任何故障。只是什么也不会发生。这种防御功能对我来说非常重要,因为使用音量按钮支持的视图只有在设置了按钮支持后才会创建。

要查看实际效果,您可以下载我正在使用的应用程序,这样可以快速免费使用。设置通常会操纵“物理按钮支持”。主要的秒表视图是实际上在进入视图时打开按钮处理,并在离开视图时关闭它。如果您有时间,您还会在设置 > 用户指南 > 选项:物理按钮支持中找到一个重要说明:

在特殊情况下,该应用程序可能没有机会在秒表视图外正确关闭音量按钮处理...

我将在Github README.md中添加完整说明。如果相关,请随意适应和重用它。

这种情况并不是特别异常,而且我还没有完全弄清楚问题出在哪里。当用户在音量按钮处于开启状态时关闭应用程序(或者你只是在Xcode中停止你的应用程序),物理按钮支持可能无法正确地从操作系统中移除。因此,你可能会得到两个内部处理程序实例,其中只有一个受你控制。因此,每次按下按钮都会导致两个甚至更多的调用到动作例程。我的包装器有一些保护代码来防止按钮过快调用。但这只是一个部分解决方案。修复需要进入底层处理程序,可惜我对此仍然了解太少,无法自行修复问题。

OLD, FOR 1.0.1:

特别是,我对Swift解决方案很感兴趣。代码是用Objective-C编写的。为了节省某些人的研究时间,这就是我使用Cocoapods所做的全部(对于像我这样的蒟蒻):

  1. Add pod 'JPSVolumeButtonHandler' to the podfile
  2. Run pod install on the command line
  3. Add #import <JPSVolumeButtonHandler.h> to the bridging header file
  4. Set up callbacks for the volume up and down buttons like so:

    let volumeButtonHandler = JPSVolumeButtonHandler(
        upBlock: {
            log.debug("Volume up button pressed...")
            // Do something when the volume up button is pressed...
        }, downBlock: {
            log.debug("Volume down button pressed...")
            // Do something else for volume down...
        })
    
这就是它。其余的是可选的。
在我的情况下,我想要启用将物理按钮按压与虚拟屏幕按钮叠加,仅适用于某些视图,同时确保尽可能少地阻止正常按钮功能(以便用户可以在应用程序的其余部分中运行音乐并调整其音量)。最终我得到了一个大多数是单例类,如下所示:
class OptionalButtonHandler {

  static var sharedInstance: OptionalButtonHandler?

  private var volumeButtonHandler: JPSVolumeButtonHandler? = nil
  private let action: () -> ()

  var enabled: Bool {
    set {
        if !enabled && newValue {
            // Switching from disabled to enabled...
            assert(volumeButtonHandler == nil, "No leftover volume button handlers")
            volumeButtonHandler = JPSVolumeButtonHandler(upBlock: {
                log.debug("Volume up button pressed...")
                self.action()
                }, downBlock: {
                    log.debug("Volume down button pressed...")
                    self.action()
            })
        } else if enabled && !newValue {
            log.debug("Disabling physical button...")
            // The other way around: Switching from enabled to disabled...
            volumeButtonHandler = nil
        }
    }
    get { return (volumeButtonHandler != nil) }
  }

  /// For one-time initialization of this otherwise singleton class.
  static func initSharedInstance(action: () -> ()) {
      sharedInstance = OptionalButtonHandler(action: action)
  }

  private init(action: () -> ()) {
      self.action = action
  }
}

这里上下音量按钮只有一个共同的操作。initSharedInstance()是必要的,因为我的操作包括对UI元素(视图)的引用,而该元素只会在某些依赖于用户的应用启动后设置。

这样进行一次性设置:

OptionalButtonHandler.initSharedInstance({
    // ...some UI action
})

有选择性地启用/禁用,就像这样简单:
OptionalButtonHandler.sharedInstance!.enabled = true  // (false)

请注意,我的代码逻辑确保在调用initSharedInstance()之前不会访问.enabled属性。
我正在使用Xcode 7.3和iOS 9.3.2在(必需的!)测试设备上运行。
期待着了解苹果对于重载他们珍贵的音量按钮的看法。至少我的应用程序确保最小侵入性,并且按键的使用确实很有意义。它不是相机应用程序,但是类似的应用程序以前也使用过物理音量按钮(甚至更不好)。

尝试在最新的iOS 10.2上运行,但无法工作。 - ShayanK
我很快地更新了答案(实际上并不是那么快)。希望能有所帮助。我花了相当多的时间来让我的代码在新版本1.0.2中正常工作。如果需要进一步的解释,请告诉我。 - marco

1
如果您愿意深入挖掘私有 API,我有一份针对 Wolf3d 的补丁程序,它可以为您提供您所寻找的功能。它使用了私有的AVSystemController类和一些隐藏在UIApplication中的方法。

1

好的,

我看到了您的解决方案,不确定苹果是否会拒绝或接受使用 AVSystemController_SystemVolumeDidChangeNotification。但是我有一个解决方法。

使用 MPVolumeViewUISlider 来注册 iPhone 硬件音量的任何更改,如下所示:

MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectZero];

for (UIView *view in [volumeView subviews]) {
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        self.volume_slider = (UISlider*)view;
        break;
    }
}
[volumeView sizeToFit];
#THIS IS THE MAIN LINE. ADD YOUR CALLBACK TARGET HERE
[self.volume_slider addTarget:self action:@selector(volumeListener:) forControlEvents:UIControlEventValueChanged];
[self addSubview:volumeView];
[volumeView setAlpha:0.0f];

-(void)volumeListener:(NSNotification*)notification {
     #UPDATE YOUR UI ACCORDING OR DO WHATEVER YOU WANNA DO.
     #YOU CAN ALSO GET THE SOUND STEP VALUE HERE FROM NOTIFICATION.
}

如果这对任何人有帮助,请告诉我。


一开始它进入了两次,我不明白为什么。 - ondermerol

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