【问题标题】:program access to iPhone volume buttons程序访问 iPhone 音量按钮
【发布时间】:2010-10-20 20:17:31
【问题描述】:

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

【问题讨论】:

    标签: iphone objective-c


    【解决方案1】:

    最近遭到 Apple 拒绝后

    不要使用这个。 Apple 现在使用了一些补丁,如果它使用任何私有 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,而只是忽略它。

    【讨论】:

    • 是的,我正在考虑收听 VolumeChanged,但我想知道当没有实际的 VolumeChange 发生时它是否仍会收到消息(例如音量处于最大并且我正在增加它)?
    • 您可以在设备上尝试代码,看看是否收到事件。
    • 您无法使用此代码发布应用程序,它使用私有 API。这就是编译时警告的内容,下划线前缀也是一个赠品。
    • 您可以使用 MPMusicPlayerController *musicPlayer = [MPMusicPlayerController iPodMusicPlayer];音乐播放器.volume = newVolume;这不是私人的,会改变音量值。
    【解决方案2】:

    如果你只是想得到通知,我认为是这样的:

    如果我错了,请纠正我,但我不相信这使用任何内部 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,这是一个现已修复的错误的解决方法。

    但我很确定如果你想要做的只是 GET 通知,而不是 SET 系统音量,你可以像使用通知中心一样使用任何其他活动!

    请注意:由于 Apple 已向相机添加了音量增大操作,因此在可见 UIImagePickerController不会发布此通知。

    【讨论】:

    • 请注意,当UIImagePickerController(例如摄像头)可见时,此通知不会发送。
    • 这只对我有用,如果我还添加了一个不可见的 MPVolume 视图。也就是说,只需添加到您的代码:#import <MediaPlayer/MediaPlayer.h>MPVolumeView *slide = [MPVolumeView new];
    • @Erpheus 不幸的是 UIImagePickerController 的限制仍然适用。即使将幻灯片添加到 UIImagePickerController 视图,也不会触发该事件。
    • 这是私有 API 吗?
    【解决方案3】:

    我在研究上述所有资源和其他线程中发现的最简单和功能最完整的方法是:JPSVolumeButtonHandler(除了作为用户之外,我没有参与其中。但非常感谢负责的人!)

    编辑:1.0.2 版带有一些重大更改/增强功能。我将把我之前对 1.0.1 的答案放在首屏。

    我放置了一个示例包装类,您可以按原样部署它,也可以用来学习 JPSVolumeButtonHandler 在separate Github repository real quick here 中的正确使用。

    以下是包装器的使用方式(我会尽快将其添加到存储库中):

    1. 单例类有两个标志:isInUseisOnisInUse 旨在在某种通用应用程序设置中进行设置,并在一般情况下打开和关闭按钮支持。 因此,无论类中的任何其他值,如果这是false,当用户按下音量按钮时不会发生任何事情,并且实现尽可能确保保持清洁并且不会不必要地影响系统音量级别。 (请阅读 README 中提到的问题,了解当首次打开按钮支持时会发生什么。) isOn 的意思是 true 在需要按钮的持续时间内。您可以打开和关闭它,而无需考虑isInUse 的当前值。

    2. 无论您在哪个视图中初始化按下音量按钮时应该发生的动作,都可以像这样设置动作:

      PhysicalButton.shared.action = { /* 做某事 */ }

    动作的类型为() -> Void。在您初始化动作之前,什么都不会中断。只是什么都不会发生。这种防御功能对我来说很重要,因为只有在设置按钮支持后才会创建使用音量按钮支持的视图。

    要查看实际情况,您可以download the app 我正在免费使用它。这些设置通常操纵“物理按钮支持”。主秒表视图是在进入视图时实际打开按钮处理并在离开时关闭的视图。如果您有时间,您还会在设置 > 用户指南 > 选项:物理按钮支持中找到重要说明:

    在特殊情况下,应用程序可能无法正常运行 在秒表视图之外关闭音量按钮处理...

    我会将完整的注释添加到 Github README.md。如果它与您的情况相关,请随意调整和重用它。

    情况实际上并没有那么特殊,我还没有完全弄清楚出了什么问题。当用户在音量按钮打开的情况下终止应用程序(或者您只是在 Xcode 中停止应用程序)时,可能无法从操作系统中正确删除物理按钮支持。因此,您最终会得到两个内部处理程序实例,您只能控制其中一个。因此,每个按钮点击都会导致对操作例程的两次甚至更多调用。我的包装器有一些保护代码来防止按钮调用过快。但这只是部分解决方案。修复需要进入底层处理程序,遗憾的是我对它的了解仍然太少,无法自己尝试修复。


    旧,适用于 1.0.1:

    我特别感兴趣的是 Swift 解决方案。代码在 Objective-C 中。为了节省一些研究,这就是我使用 Cocoapods 所做的一切(对于像我这样的傻瓜):

    1. pod 'JPSVolumeButtonHandler' 添加到 podfile
    2. 在命令行运行pod install
    3. 在桥接头文件中添加#import <JPSVolumeButtonHandler.h>
    4. 为音量增大和减小按钮设置回调,如下所示:

      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。

    期待了解 Apple 对超载宝贵的音量按钮的感受。至少我的应用程序确保是微创的,并且按钮的使用真的很有意义。它不是相机应用,但类似的应用之前使用过物理音量按钮(甚至不太好)。

    【讨论】:

    • 在最新的 iOS 10.2 上试过这个,但它不工作
    • 我很快就更新了答案(或者实际上没那么快)。希望能帮助到你。我花了很长时间使用新版本 1.0.2 来让我的代码正常工作。让我知道进一步的解释可能会有所帮助。
    【解决方案4】:

    如果您愿意使用私有 API,我有 a patch to Wolf3d,它正好添加了您正在寻找的功能。它使用私有的AVSystemController 类和UIApplication 上的一些隐藏方法

    【讨论】:

      【解决方案5】:

      好的,

      所以我看到了您的解决方案,但不知道 Apple 是否会拒绝或接受使用 AVSystemController_SystemVolumeDidChangeNotification。但我有一个解决办法。

      使用MPVolumeView 中的UISlider 来注册这样的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.
      }
      

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

      【讨论】:

      • 一开始进了两次,不明白为什么。
      猜你喜欢
      • 1970-01-01
      • 2019-09-04
      • 1970-01-01
      • 1970-01-01
      • 2012-03-28
      • 2013-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多