Detect iPhone Volume Button Up Press?

2019-01-11 05:13发布

问题:

Is there a notification that I can listen to that will tell me when an iPhone's volume is turned up?

I know about the AVSystemController_SystemVolumeDidChangeNotification, but it is essential that the notification only be triggered when the volume has been turned up, not up or down.

Secondly, how can I hide the translucent view that appears when the volume up button is pressed, showing the system's volume? Camera+ has implemented this.

回答1:

There is no documented way to to this, but you can use this workaround. Register for AVSystemController_SystemVolumeDidChangeNotification notification and add an MPVolumeView which will prevent the system volume view from showing up.

MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, 0, 10, 0)];
[volumeView sizeToFit];
[self.view addSubview:volumeView];

And don't forget to start an Audio Session

AudioSessionInitialize(NULL, NULL, NULL, NULL);
AudioSessionSetActive(true);

In This case, the MPVolumeView is hidden from the user.

As for checking if volume up or down was pressed, just grab the current application's volume

float volumeLevel = [[MPMusicPlayerController applicationMusicPlayer] volume];  

and compare it with new volume after the button was pressed in notification callback

If you don't want to do it by yourself, there's a drop-in class available in github

https://github.com/blladnar/RBVolumeButtons



回答2:

If you want an event you can register a listener on the "outputVolume" property:

- (void)viewWillAppear:(BOOL)animated {

    AVAudioSession* audioSession = [AVAudioSession sharedInstance];

    [audioSession setActive:YES error:nil];
    [audioSession addObserver:self
                    forKeyPath:@"outputVolume"
                       options:0
                       context:nil];
}

-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if ([keyPath isEqual:@"outputVolume"]) {
        NSLog(@"volume changed!");
    }
}


回答3:

I solved this problem by adding own target/action for UISlider placed inside MPVolumeView. So it's possible to catch volume change events and determine what button was pressed. Here's github repo with implementation of this approach. It works fine with iOS 7 and above, no deprecation warnings and no rejection from Apple.



回答4:

In order to distinguish volume action: INSTEAD OF (in observeValue guard)

temp != 0.5

USE for only volume up

temp > 0.5

and only detect volume down:

temp < 0.5 

This solution below will print if either volume up or down are pressed.

import AVFoundation
import MediaPlayer

override func viewDidLoad() {
  super.viewDidLoad()
  let volumeView = MPVolumeView(frame: CGRect.zero)
  for subview in volumeView.subviews {
    if let button = subview as? UIButton {
      button.setImage(nil, for: .normal)
      button.isEnabled = false
      button.sizeToFit()
    }
  }
  UIApplication.shared.windows.first?.addSubview(volumeView)
  UIApplication.shared.windows.first?.sendSubview(toBack: volumeView)
}

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)
  AVAudioSession.sharedInstance().addObserver(self, forKeyPath: "outputVolume", options: NSKeyValueObservingOptions.new, context: nil)
  do { try AVAudioSession.sharedInstance().setActive(true) }
  catch { debugPrint("\(error)") }   
}

override func viewDidDisappear(_ animated: Bool) {
  super.viewDidDisappear(animated)
  AVAudioSession.sharedInstance().removeObserver(self, forKeyPath: "outputVolume")
  do { try AVAudioSession.sharedInstance().setActive(false) } 
  catch { debugPrint("\(error)") }
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  guard let key = keyPath else { return }
  switch key {
    case "outputVolume":
      guard let dict = change, let temp = dict[NSKeyValueChangeKey.newKey] as? Float, temp != 0.5 else { return }
      let systemSlider = MPVolumeView().subviews.first { (aView) -> Bool in
        return NSStringFromClass(aView.classForCoder) == "MPVolumeSlider" ? true : false
      } as? UISlider
      systemSlider?.setValue(0.5, animated: false)
      guard systemSlider != nil else { return }
      debugPrint("Either volume button tapped.")
    default:
      break
  } 
}