iOS 覆盖硬件音量按钮(与 Zello 相同)

iOS override hardware volume buttons (Same as Zello)

我目前正在开发 PTT(一键通)应用程序。 我正在尝试使用硬件音量按钮来 start/stop 传输。

到目前为止我看到的所有建议解决方案都可以缩小为两个:

  1. 使用KVO观察AVAudioSession 属性 outputVolume.
  2. 使用私人 API 通知,即 AVSystemController_SystemVolumeDidChangeNotification 并且自 iOS 15.0 - SystemVolumeDidChange.

无需深入探讨每种解决方案的优缺点,它们都有一个共同点 - 它们都是基于体积的,这会引发几个问题。

  1. 按音量按钮可更改系统音量。虽然这可以通过重置系统音量来解决,但这不是一个很好的解决方案。
  2. 无法区分来自硬件按钮的音量变化和来自命令中心的音量变化,因此按钮的使用仅限于应用程序处于前台并处于活动状态时。
  3. 当用户按下音量按钮时,第一个音量变化事件与随后的连续事件之间会有短暂的延迟,这使得难以跟踪快速按下和释放。

我注意到 Zello 应用程序以某种方式设法克服了这些问题,因为即使在设备关闭或命令中心打开时它们也可以使用音量按钮 - 不会对系统音量造成任何干扰。此外,从命令中心更改音量也没有效果。

有没有人知道如何实现这种行为?

我认为您尝试做的事情会导致应用商店在审查过程中被拒绝,因为 Apple 在 the App Store Review Guidelines paragraph 2.5.9

中提到了这一点

Apps that alter or disable the functions of standard switches, such as the Volume Up/Down and Ring/Silent switches, or other native user interface elements or behaviors will be rejected. For example, apps should not block links out to other apps or other features that users would expect to work a certain way. Learn more about proper handling

Warning Private API 用法如下。我们必须有一些特殊情况可以让你通过。

看看 2012 年的 Explore the iOS SDK and use undocumented APIs

总而言之,您需要调用私有方法- [UIApplication setWantsVolumeButtonEvents:YES]来启用以下通知:

  • _UIApplicationVolumeUpButtonDownNotification
  • _UIApplicationVolumeUpButtonUpNotification
  • _UIApplicationVolumeDownButtonDownNotification
  • _UIApplicationVolumeDownButtonUpNotification

在 Swift 中,可以通过以下方式启用此功能:

@objc public protocol UIApplicationPrivate {
    @objc func setWantsVolumeButtonEvents(_:Bool)
}

class VolumeButtonsManager {
    private static var observer: NSObjectProtocol?

    static func setup(with application: UIApplication) {
        observer = NotificationCenter.default.addObserver(forName: nil,
                                                          object: nil,
                                                          queue: nil,
                                                          using: handleEvent)
        
        let application = unsafeBitCast(application, to:UIApplicationPrivate.self)
        application.setWantsVolumeButtonEvents(true)
    }

    private static func handleEvent(_ notification: Notification) {
        switch notification.name.rawValue {
        case "_UIApplicationVolumeUpButtonDownNotification": print("Volume Up Button Down")
        case "_UIApplicationVolumeUpButtonUpNotification": print("Volume Up Button Up")
        case "_UIApplicationVolumeDownButtonDownNotification": print("Volume Down Button Down")
        case "_UIApplicationVolumeDownButtonUpNotification": print("Volume Down Button Up")
        default: break
        }
    }
}

UIApplicationPrivate 通过 Silencing a warning for explicitly constructed selector.