简体   繁体   中英

AVAudioSession error: Deactivating an audio session that has running I/O

2017-02-24 14:56:44.280 PropertyManager[10172:5336578] 14:56:44.280 ERROR:    [0x1a0a24000] AVAudioSession.mm:692: -[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.

2017-02-24 14:56:44.281 PropertyManager[10172:5336578] error === Error Domain=NSOSStatusErrorDomain Code=560030580 "(null)"
PropertyManager was compiled with optimization - stepping may behave oddly; variables may not be available.

Your error log is very succinctly self-expressive:

Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session

It tells you the problem and also the solution.

Right now you're doing something like this:

[[AVAudioSession sharedInstance] setActive:NO
               withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
                                             error:nil];

You should however, first stop the audio player instance and then set the activation status to Yes or No.

[yourAudioPlayer stop];
[[AVAudioSession sharedInstance] setActive:NO
                   withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
                                                 error:nil];

Refer to Apple Documentation to see values of enum AudioSessionSetActiveOption .

Also see: Apple Documentation on setActive:withOptions method

As for your second error

PropertyManager was compiled with optimization - stepping may behave oddly; variables may not be available.

see this excellent answer .

让音频播放器在录制会话假之前停止..如果你想让音频播放器为零..删除那条线..它对我有用..

@NSNoob is 100% correct. The player (or something else) is still active.

This is how I did it using the solution from dani-mp . He said:

I'd say that pausing the player is not a synchronous operation, so we shouldn't be deactivating the session before knowing that the player has been paused ( see the response in this thread ).

A solution for this problem could be that we listen to changes to timeControlStatus and deactivate the audio session once the player has really been paused.

The answer in the thread says

This error indicates that something in your app is still using audio I/O at the time when the AVAudioSession setActive:NO is being called. It's impossible to say which object without more information, but sometimes it's the result of calling an asynchronous stop method on a player of some sort and not waiting for the notification that the player has stopped before deactivating the audio session.

To play audio in the background and use the MPRemoteCommandCenter I had to switch from .mixWithOthers to .playback

Read the 10 steps in the commented out code.

var player: AVPlayer?
var playerTimeControlStatusObserver: NSKeyValueObservation?
var wasPlayerPlayingBeforeItWentToTheBackground = false // 0. this is needed in steps 2 and 6 

override func viewDidLoad() {
     super.viewDidLoad()

     NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}

func checkIfPlayerWasPlayingWhenAppEntersBackground() { // 1. this gets called in the UIApplication.didEnterBackgroundNotification selector method

    if let player = player, player.isPlaying  {
                
        wasPlayerPlayingBeforeItWentToTheBackground = true // 2.  this is needed in step 6

        player.pause() // 3. once the player is paused the `.timeControlStatus` will fire when it's 100% stopped
    }
}

playerTimeControlStatusObserver = player?.observe(\.timeControlStatus, options: [.new, .old]) { [weak self](player, change) in

    switch (player.timeControlStatus) {
        // ... other cases

        case .paused: // 4. read the very long function name to see what happens next. Once this .paused case runs the player is Definitely stopped.
            DispatchQueue.main.async { [weak self] in
                self?.checkTimeStatusAndApplicationStateToMakeSureThePlayerIsDefinitelyStoppedAndInTheBackground()
            }

        // ... other cases
    }
}

func checkTimeStatusAndApplicationStateToMakeSureThePlayerIsDefinitelyStoppedAndInTheBackground() {

    let state = UIApplication.shared.applicationState
    if state == .background { // 5. this code only runs if it's in the background

        if wasPlayerPlayingBeforeItWentToTheBackground { // 6. this has to be true

            wasPlayerPlayingBeforeItWentToTheBackground = false // 7. set it back to false

            do { // 8. deactivate the AVAudioSession
                try AVAudioSession.sharedInstance().setActive(false)
                try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
                try AVAudioSession.sharedInstance().setActive(true)

                player?.play() // 9. resume play

            } catch let err as NSError {
                print(err.localizedDescription)
            }
        }
    }
}

@objc func appDidEnterBackground() {

    checkIfPlayerWasPlayingWhenAppEntersBackground() // 10. when the app is backgrounded run the code from step 1.
}

Of course I had to do the same check when the app was on its way back to the foreground to pause the player, check the .timeControlStatus observer, and then switch back to .mixWithOthers .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM