如何在Swift中恢复音频播放?

9

我正在按照这里的说明进行操作,我已经创建了这个测试项目来处理音频播放中的中断。具体而言,我正在使用默认的iPhone闹钟应用程序的警报作为中断。似乎中断处理程序已被调用,但无法通过let = interruptionType行,因为"wrong type"出现了两次。

import UIKit
import AVFoundation

class ViewController: UIViewController {

    var player = AVAudioPlayer()

    let audioPath = NSBundle.mainBundle().pathForResource("rachmaninov-romance-sixhands-alianello", ofType: "mp3")!

    func handleInterruption(notification: NSNotification) {

        guard let interruptionType = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? AVAudioSessionInterruptionType else { print("wrong type"); return }

        switch interruptionType {

        case .Began:
            print("began")
            // player is paused and session is inactive. need to update UI)
            player.pause()
            print("audio paused")

        default:
            print("ended")
            /**/
            if let option = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? AVAudioSessionInterruptionOptions where option == .ShouldResume {
                // ok to resume playing, re activate session and resume playing
                // need to update UI
                player.play()
                print("audio resumed")
            }
            /**/
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        do {
            try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
            player.numberOfLoops = -1 // play indefinitely
            player.prepareToPlay()
            //player.delegate = player

        } catch {
            // process error here
        }

        // enable play in background https://dev59.com/-V0a5IYBdhLWcg3wfIl-#30280699 but this audio still gets interrupted by alerts
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
            print("AVAudioSession Category Playback OK")
            do {
                try AVAudioSession.sharedInstance().setActive(true)
                print("AVAudioSession is Active")
            } catch let error as NSError {
                print(error.localizedDescription)
            }
        } catch let error as NSError {
            print(error.localizedDescription)
        }

        // add observer to handle audio interruptions
        // using 'object: nil' does not have a noticeable effect
        let theSession = AVAudioSession.sharedInstance()
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.handleInterruption(_:)), name: AVAudioSessionInterruptionNotification, object: theSession)

        // start playing audio
        player.play()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

此外,根据这里的一个想法,我已经修改了处理程序以

func handleInterruption(notification: NSNotification) {

        //guard let interruptionType = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? AVAudioSessionInterruptionType else { print("wrong type"); return }

        if notification.name != AVAudioSessionInterruptionNotification
            || notification.userInfo == nil{
            return
        }

        var info = notification.userInfo!
        var intValue: UInt = 0
        (info[AVAudioSessionInterruptionTypeKey] as! NSValue).getValue(&intValue)
        if let interruptionType = AVAudioSessionInterruptionType(rawValue: intValue) {

            switch interruptionType {

            case .Began:
                print("began")
                // player is paused and session is inactive. need to update UI)
                player.pause()
                print("audio paused")

            default:
                print("ended")
                /** /
                if let option = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? AVAudioSessionInterruptionOptions where option == .ShouldResume {
                    // ok to resume playing, re activate session and resume playing
                    // need to update UI
                    player.play()
                    print("audio resumed")
                }
                / **/
                player.play()
                print("audio resumed")
            }
        }
    }

结果是,“开始”,“音频暂停”,“结束”和“音频恢复”都显示在控制台中,但实际上音频播放并没有恢复。

注意:我将player.play()移动到了被注释的where option == .ShouldResume if语句之外,因为当.Ended中断发生时,该条件不成立。


1
抱歉,我说错了,已经更正。中断处理程序正在被调用。 - rockhammer
好的,这将是我第一次发布任何内容,可能需要我超过几分钟的时间... 请继续关注。 - rockhammer
感谢对CW答案的修改!如果你想重新发布整个答案,我很乐意删除CW版本并点赞你的答案 - 你做得很好。 - halfer
1
@halfer,谢谢你的建议。我喜欢这里的社区精神。 - rockhammer
对于那些维护他们的答案的人来说,这绝对是存在的 - 作为发帖者的比例很少有我们。 - halfer
3个回答

19
< p >< em >(此问题的作者代表发布,此前已发布)。

找到解决方案!在这里的讨论后,在viewDidLoad()中插入了以下内容

do {
    try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.MixWithOthers)
} catch {        
}

点击闹钟中断后,音频播放会继续。与先前注意到的不同的是,该解决方案不需要中断处理程序(@Leo Dabus已将其删除)。

然而,如果您正在使用中断处理程序,则.play()不能在handleInterruption()内调用,因为这样做不能保证播放恢复并似乎会防止调用audioPlayerEndInterruption()(请参见文档)。相反,在audioPlayerEndInterruption()(其3个版本之一)内必须调用.play()以确保恢复。

此外,AVAudioSession必须给出选项.MixWithOthers,如@Simon Newstead所指出,如果您希望您的应用在后台中断后恢复播放。如果用户希望应用程序在进入后台时继续播放,那么假设用户也希望应用程序在后台中断后恢复播放是合理的。事实上,这是Apple Music应用程序表现的行为。


8
@rockhammers的建议对我有用。 在这里 上课前
let theSession = AVAudioSession.sharedInstance()

在viewDidLoad中

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleInterruption(notification:)), name: NSNotification.Name.AVAudioSessionInterruption, object: theSession)

然后是函数

func handleInterruption(notification: NSNotification) {
    print("handleInterruption")
    guard let value = (notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? NSNumber)?.uintValue,
        let interruptionType =  AVAudioSessionInterruptionType(rawValue: value)
        else {
            print("notification.userInfo?[AVAudioSessionInterruptionTypeKey]", notification.userInfo?[AVAudioSessionInterruptionTypeKey])
            return }
    switch interruptionType {
    case .began:
        print("began")
        vox.pause()
        music.pause()
        print("audioPlayer.playing", vox.isPlaying)
        /**/
        do {
            try theSession.setActive(false)
            print("AVAudioSession is inactive")
        } catch let error as NSError {
            print(error.localizedDescription)
        }
        pause()
    default :
        print("ended")
        if let optionValue = (notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? NSNumber)?.uintValue, AVAudioSessionInterruptionOptions(rawValue: optionValue) == .shouldResume {
            print("should resume")
            // ok to resume playing, re activate session and resume playing
            /**/
            do {
                try theSession.setActive(true)
                print("AVAudioSession is Active again")
                vox.play()
                music.play()
            } catch let error as NSError {
                print(error.localizedDescription)
            }
            play()
        }
    }
}

你好,我遇到了一个问题,预设的代码对我不起作用,它没有进入if语句。你能帮我吗?它显示了这个错误。 WKProcessAssertionBackgroundTaskManager:由于RunningBoard已经启动了过期计时器,忽略了启动新后台任务的请求。 - Yogesh Patel

0

一些原因导致 interruptionNotification 在 iOS 12.x 上无法正常工作,因此我添加了 silenceSecondaryAudioHintNotification。 当闹钟通知来临时,您可以尝试使用 silenceSecondaryAudioHintNotification。

@objc func handleSecondaryAudioSilence(notification: NSNotification) {
    guard let userInfo = notification.userInfo,
          let typeValue = userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] as? UInt,
          let type = AVAudioSession.SilenceSecondaryAudioHintType(rawValue: typeValue) else {
        return
    }
    
    if type == .end {
        // Other app audio stopped playing - restart secondary audio.
        reconnectAVPlayer()
    }
}

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