检测音量按钮按下

26

音量按钮通知功能未被调用。

代码:

func listenVolumeButton(){
    // Option #1
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "volumeChanged:", name: "AVSystemController_SystemVolumeDidChangeNotification", object: nil)
    // Option #2
    var audioSession = AVAudioSession()
    audioSession.setActive(true, error: nil)
    audioSession.addObserver(self, forKeyPath: "volumeChanged", options: NSKeyValueObservingOptions.New, context: nil)
}

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
    if keyPath == "volumeChanged"{
        print("got in here")
    }
}

func volumeChanged(notification: NSNotification){
   print("got in here")
}

listenVolumeButton()viewWillAppear中被调用。

无论哪种情况下,代码都没有进入打印语句"got in here"

我尝试了两种不同的方法,但都没有成功。

我遵循了这个链接:Detect iPhone Volume Button Up Press?

6个回答

34

使用第二种方法,键路径的值应为"outputVolume"。这是我们观察的属性。 因此将代码更改为:

var outputVolumeObserve: NSKeyValueObservation?
let audioSession = AVAudioSession.sharedInstance()

func listenVolumeButton() {
    do {
        try audioSession.setActive(true)
    } catch {}

    outputVolumeObserve = audioSession.observe(\.outputVolume) { (audioSession, changes) in
        /// TODOs
    }
}

14
音量调到最大后,是否仍然能够收到通知? - AustinT
1
AustinT,你解决了绕过最大音量限制的方法吗?我也遇到了同样的问题。 - Dirk
2
有人在Github上有Swift 3的代码示例吗? - Milos Mandic
2
嗨@rakeshbs,这个观察者在iOS 11.4上不起作用!有没有更新?请让我知道。如果你在iOS 11.4上遇到了问题,请告诉我。 - Hiren kanetiya
1
我有一个解决音量最大的问题的方案... 如果 AVAudioSession.sharedInstance().outputVolume == 1.0 { print("VOLUME MAX") } - Jeff
显示剩余6条评论

21

上面的代码在Swift 3中无法工作,这种情况下,请尝试以下代码:

func listenVolumeButton() {
   do {
    try audioSession.setActive(true)
   } catch {
    print("some error")
   }
   audioSession.addObserver(self, forKeyPath: "outputVolume", options: NSKeyValueObservingOptions.new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  if keyPath == "outputVolume" {
    print("got in here")
  }
}

5
对我来说这并不起作用——至少在模拟器中不起作用。 - Ken Roy
1
不适用于模拟器,也不会在已达到最大音量时触发。 - Nike Kov
1
感谢确认在iOS 12 / xcode 10 / iPhone X上工作正常,但您需要添加以下行(我将其作为视图控制器的类变量): var audioSession = AVAudioSession() - woody121
已确认在iOS 12 / xcode 10.2.1 / iPhone SE上工作正常,无需将audiosession作为类属性。 - songgeb

9

使用以下代码可以监听用户何时按下音量硬件按钮:

class VolumeListener {
    static let kVolumeKey = "volume"

    static let shared = VolumeListener()

    private let kAudioVolumeChangeReasonNotificationParameter = "AVSystemController_AudioVolumeChangeReasonNotificationParameter"
    private let kAudioVolumeNotificationParameter = "AVSystemController_AudioVolumeNotificationParameter"
    private let kExplicitVolumeChange = "ExplicitVolumeChange"
    private let kSystemVolumeDidChangeNotificationName = NSNotification.Name(rawValue: "AVSystemController_SystemVolumeDidChangeNotification")

    private var hasSetup = false

    func start() {
        guard !self.hasSetup else {
            return
        }

        self.setup()
        self.hasSetup = true

    }

    private func setup() {
        guard let rootViewController = UIApplication.shared.windows.first?.rootViewController else {
            return
        }

        let volumeView = MPVolumeView(frame: CGRect.zero)
        volumeView.clipsToBounds = true
        rootViewController.view.addSubview(volumeView)

        NotificationCenter.default.addObserver(
            self,
            selector: #selector(self.volumeChanged),
            name: kSystemVolumeDidChangeNotificationName,
            object: nil
        )

        volumeView.removeFromSuperview()
    }

    @objc func volumeChanged(_ notification: NSNotification) {
        guard let userInfo = notification.userInfo,
            let volume = userInfo[kAudioVolumeNotificationParameter] as? Float,
            let changeReason = userInfo[kAudioVolumeChangeReasonNotificationParameter] as? String,
            changeReason == kExplicitVolumeChange
            else {
                return
        }

        NotificationCenter.default.post(name: "volumeListenerUserDidInteractWithVolume", object: nil,
                                        userInfo: [VolumeListener.kVolumeKey: volume])
    }
}

要监听,只需添加观察者:

NotificationCenter.default.addObserver(self, selector: #selector(self.userInteractedWithVolume),
                                           name: "volumeListenerUserDidInteractWithVolume", object: nil)

您可以通过检查userInfo来访问音量值:

@objc private func userInteractedWithVolume(_ notification: Notification) {
    guard let volume = notification.userInfo?[VolumeListener.kVolumeKey] as? Float else {
        return
    }

    print("volume: \(volume)")
}

在iOS 14上,无需创建“MPVolumeView”即可正常工作,但不确定早期的操作系统是否也适用。 - Ben Sullivan
在我的iOS 14.6 iPad上,仍需要创建MPVolumeView... - Saafo
1
有人能确认这在iOS 15上是否会出现问题吗?对我来说,volumeChanged选择器没有被调用。 - Nate
已在iOS 15上进行了测试,似乎无法正常工作。有人知道检测音量变化原因的不同方法吗? - Yoni Reiss

5
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
  } 
}

当观察到新的值时,我会将系统音量设回0.5。然而这可能会使同时使用音乐的用户感到不满,因此我不建议在生产环境中采用我的解决方案。

2
如果你有兴趣,这里有一个 RxSwift 版本。
func volumeRx() -> Observable<Void> {
    Observable<Void>.create {
        subscriber in
        
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setActive(true)
        } catch let e {
            subscriber.onError(e)
        }

        let outputVolumeObserve = audioSession.observe(\.outputVolume) {
            (audioSession, changes) in
            subscriber.onNext(Void())
        }
        
        return Disposables.create {
            outputVolumeObserve.invalidate()
        }
    }
}

使用
volumeRx()
   .subscribe(onNext: {
      print("Volume changed")
   }).disposed(by: disposeBag)

1

SwiftUI方法

我花了一整天的时间来完成这个任务,因为我是新手,但我想出了一个解决方案:
A:可以读取音量
B:即使音量最大或最小也能正常工作
C:可能可以根据您的喜好进行自定义

struct VolumeEventReader<Content: View>: UIViewControllerRepresentable {
    let builder: (Float) -> Content

    class Coordinator: NSObject {
        var parent: VolumeEventReader
        var lastVolumeNotificationSequenceNumber: Int?
        var currentVolume = AVAudioSession.sharedInstance().outputVolume

        init(_ parent: VolumeEventReader) {
            self.parent = parent
        }

        @objc func volumeChanged(_ notification: NSNotification) {
            DispatchQueue.main.async { [self] in
                volumeControlIOS15(notification)
            }
        }

        func manageVolume(volume: Float, minVolume: Float) {
            switch volume {
            case minVolume: do {
                currentVolume = minVolume + 0.0625
            }
            case 1: do {
                currentVolume = 0.9375
            }
            default: break
            }

            
            if volume > currentVolume {
                // Volume up
            }
            if volume < currentVolume {
                // Volume down
            }

            parent.updateUIView(volume: volume)
            currentVolume = volume
        }

        func volumeControlIOS15(_ notification: NSNotification) {
            let minVolume: Float = 0.0625

            if let volume = notification.userInfo!["Volume"] as? Float {
                //avoiding duplicate events if same ID notification was generated
                if let seqN = self.lastVolumeNotificationSequenceNumber {
                    if seqN == notification.userInfo!["SequenceNumber"] as! Int {
                        // Duplicate nofification received
                    }
                    else {
                        self.lastVolumeNotificationSequenceNumber = (notification.userInfo!["SequenceNumber"] as! Int)
                        manageVolume(volume: volume, minVolume: minVolume)
                    }
                }
                else {
                    self.lastVolumeNotificationSequenceNumber = (notification.userInfo!["SequenceNumber"] as! Int)
                    manageVolume(volume: volume, minVolume: minVolume)
                }
            }
        }
    }
    
    let viewController = UIViewController()

    func makeUIViewController(context: Context) -> UIViewController {
        let volumeView = MPVolumeView(frame: CGRect.zero)
        volumeView.isHidden = true
        viewController.view.addSubview(volumeView)
        let childView = UIHostingController(rootView: builder(AVAudioSession.sharedInstance().outputVolume))
        addChildViewController(childView, to: viewController)
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}

    private func addChildViewController(_ child: UIViewController, to parent: UIViewController) {

        if parent.children.count > 0{
            let viewControllers:[UIViewController] = parent.children
            for viewContoller in viewControllers{
                viewContoller.willMove(toParent: nil)
                viewContoller.view.removeFromSuperview()
                viewContoller.removeFromParent()
            }
        }

        parent.addChild(child)
        child.view.translatesAutoresizingMaskIntoConstraints = false
        parent.view.addSubview(child.view)
        child.didMove(toParent: parent)
        child.view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0)
        NSLayoutConstraint.activate([
            child.view.leadingAnchor.constraint(equalTo: parent.view.leadingAnchor),
            child.view.trailingAnchor.constraint(equalTo: parent.view.trailingAnchor),
            child.view.topAnchor.constraint(equalTo: parent.view.topAnchor),
            child.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor)
        ])
    }

    func makeCoordinator() -> Coordinator {
        let coordinator = Coordinator(self)

        NotificationCenter.default.addObserver(
            coordinator,
            selector: #selector(Coordinator.volumeChanged(_:)),
            name: NSNotification.Name(rawValue: "SystemVolumeDidChange"),
            object: nil
        )

        return coordinator
    }

    func updateUIView(volume: Float) {
        let childView = UIHostingController(rootView: builder(volume))
        addChildViewController(childView, to: self.viewController)
    }
}

这将为您提供一个VolumeEventReader,可以在Swift中使用,如下所示:

struct ContentView: View { 
    var body: some View {
        VStack {
          VolumeEventReader { volume in
              VStack {
                  Text("Volume: \(volume)")
              }
              .onAppear {
                  print("\(volume)")
              }
          }
          Text("Hello World")
        }
    }
}

注意:您可以将任何视图放置在VolumeEventReader中,我只是用VStack举了一个例子。这受到GeometryReader的启发。
感谢那些帮助我找到这个解决方案的答案:
iOS 15上的系统音量更改观察器无法工作
在SwiftUI中观察系统音量

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