如何中断设备音频播放声音,然后让设备音频继续 iOS

How to interrupt device audio to play a sound and then let the device audio continue on iOS

我有一个应用程序,用户可以在其中播放从其他用户收到的语音消息。播放语音消息应中断设备音频(音乐、播客等从其他应用程序播放),播放语音消息然后让设备音频继续。

这是我正在尝试实现的特定用例

通过将 AVAudioSession 的类别设置为 .ambient,我可以在播放 Apple Music 的同时播放语音​​消息,但这并不是我所需要的。

如果我使用使 Apple Music 停止的 .playback 类别,会在应用程序中播放语音消息,但 Apple Music 之后不会继续播放。

您已经发现可以通过激活 .playback 类别音频会话来中断其他音频应用程序。当您完成播放音频并希望中断的音频继续播放时,停用音频会话并传递 notifyOthersOnDeactivation 选项。

例如

try! audioSession.setActive(false, options: .notifyOthersOnDeactivation)

我认为那些应该继续运行的应用程序,如 Apple Music、Spotify、Radio 应用程序等,实现了处理中断的功能,以及当另一个应用程序的音频被停用/想要交还音频责任时的功能。

那么你能不能试试看这是否有效

// I play the audio using this AVAudioPlayer
var player: AVAudioPlayer?

// Implement playing a sound
func playSound() {
    
    // Local url for me, but you could configure as you need
    guard let url = Bundle.main.url(forResource: "se_1",
                                    withExtension: "wav")
    else { return }
    
    do {
        // Set the category to playback to interrupt the current audio
        try AVAudioSession.sharedInstance().setCategory(.playback,
                                                        mode: .default)
        
        try AVAudioSession.sharedInstance().setActive(true)
        
        player = try AVAudioPlayer(contentsOf: url)
        
        // This is to know when the sound has ended
        player?.delegate = self
        
        player?.play()
        
    } catch let error {
        print(error.localizedDescription)
    }
}

extension AVAudioInterruptVC: AVAudioPlayerDelegate {
    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
                                     successfully flag: Bool) {
        
        do {
            
            // When the sound has ended, notify other apps
            // You can do this where ever you want, I just show the
            // example of doing it when the audio has ended
            try AVAudioSession.sharedInstance()
                .setActive(false, options: [.notifyOthersOnDeactivation])
        }
        catch {
            print(error)
        }
        
    }
}

理论上,Apple 提供了中断和恢复背景音频的“协议”,在一个可下载的示例中,我向您展示它是什么并证明它有效:

https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/bk2ch14p653backgroundPlayerAndInterrupter

在该示例中,有 两个 项目,代表两个不同的应用程序。你 运行 他们 同时。 BackgroundPlayer 在后台播放声音; Interrupter 中断它,暂停它,当它完成中断时,BackgroundPlayer 恢复。

正如您将看到的,这是通过让 Interrupter 在中断时将其音频会话类别从环境音更改为回放,然后将其改回 完成后,首先在发送 .notifyOthersOnDeactivation 信号时完全停用自身:

func playFile(atPath path:String) {
    self.player?.delegate = nil
    self.player?.stop()
    let fileURL = URL(fileURLWithPath: path)
    guard let p = try? AVAudioPlayer(contentsOf: fileURL) else {return} // nicer
    self.player = p
    // error-checking omitted
    
    // switch to playback category while playing, interrupt background audio
    try? AVAudioSession.sharedInstance().setCategory(.playback, mode:.default)
    try? AVAudioSession.sharedInstance().setActive(true)
    
    self.player.prepareToPlay()
    self.player.delegate = self
    let ok = self.player.play()
    print("interrupter trying to play \(path): \(ok)")
}

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { // *
    let sess = AVAudioSession.sharedInstance()
    // this is the key move
    try? sess.setActive(false, options: .notifyOthersOnDeactivation)
    // now go back to ambient
    try? sess.setCategory(.ambient, mode:.default)
    try? sess.setActive(true)
    delegate?.soundFinished(self)
}

然而,问题在于对 .notifyOthersOnDeactivation 的响应完全取决于其他应用程序是否表现良好。我的另一个应用程序 BackgroundPlayer 表现良好。这是它的作用:

self.observer = NotificationCenter.default.addObserver(forName:
    AVAudioSession.interruptionNotification, object: nil, queue: nil) {
        [weak self] n in
        guard let self = self else { return } // legal in Swift 4.2
        let why = n.userInfo![AVAudioSessionInterruptionTypeKey] as! UInt
        let type = AVAudioSession.InterruptionType(rawValue: why)!
        switch type {
        case .began:
            print("interruption began:\n\(n.userInfo!)")
        case .ended:
            print("interruption ended:\n\(n.userInfo!)")
            guard let opt = n.userInfo![AVAudioSessionInterruptionOptionKey] as? UInt else {return}
            let opts = AVAudioSession.InterruptionOptions(rawValue: opt)
            if opts.contains(.shouldResume) {
                print("should resume")
                self.player.prepareToPlay()
                let ok = self.player.play()
                print("bp tried to resume play: did I? \(ok as Any)")
            } else {
                print("not should resume")
            }
        @unknown default:
            fatalError()
        }
}

如你所见,我们注册中断通知,如果我们中断,我们寻找.shouldResume选项——这是中断的结果首先设置 notifyOthersOnDeactivation

到目前为止,还不错。但是有一个障碍。某些应用程序在这方面表现不佳。而最 non-well-behaved 是苹果自己的音乐应用程序!因此,实际上 不可能 让音乐应用程序执行您希望它执行的操作。你最好使用ducking,系统只是为你调整两个应用程序的相对电平,让后台应用程序(音乐)继续播放,但更安静。