Unable to set AudioSession inactive with AVPlayer
Asked Answered
I

5

8

I am using an AVPlayer to playback audio in my iOS app (because the audio files I'm playing are not local), and when my track finishes, I want to hide the InfoCenter controls, but I get this error:

AVAudioSession.mm:1079:-[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.

I am however pausing my AVPlayer before.

This is my code:

player.pause()
player.replaceCurrentItem(with: nil)

MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
try? audioSession.setActive(false, options: .notifyOthersOnDeactivation)

Due to this error, the native controls remain visible, but don't work anymore.

enter image description here

Ingulf answered 14/5, 2019 at 14:36 Comment(10)
Take a look at this #43373986Anthropophagite
Mh... that doesn't really help me. I'm not using a queue.. I only have a single AVPlayer that plays one track at a time...Ingulf
I guess this link will help you with this.Wattage
What if you stop instead of pause?Unstudied
@Unstudied AVPlayer do not have stop functionality like AVAudioPlayer haveHandmaiden
@Abdorahman Yes, I see, you're right. There isn't some simple way to say "release the buffer".Unstudied
@Unstudied there is a way just like my answer belowHandmaiden
@Abdorahman Yes, I think you've made that point already.Unstudied
@Ingulf do my solution worked for you?Handmaiden
@Abdorahman I'll be able to confirm Monday! Thanks in advanceIngulf
B
3

A quick way to lose the lock screen controls is to make your audio session mixable (without deactivating your audio session):

try! AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: .mixWithOthers)

Remove the .mixWithOthers when you want them back.

Another, perhaps more palatable way is to remove your MPRemoteCommandCenter targets once you pause playback. e.g. if you set it up like this

var playTarget: Any? = nil
var pauseTarget: Any? = nil

func addRemoteTargets() {
    let commandCenter = MPRemoteCommandCenter.shared()

    playTarget = commandCenter.playCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
        print("play \(event)")
        return .success
    }

    pauseTarget = commandCenter.pauseCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
        print("pause \(event)")
        return .success
    }
}

Then remove the targets like so:

func removeRemoteTargets() {
    let commandCenter = MPRemoteCommandCenter.shared()
    commandCenter.playCommand.removeTarget(playTarget)
    playTarget = nil
    commandCenter.pauseCommand.removeTarget(pauseTarget)
    pauseTarget = nil
}

p.s. these are "seems to work!" answers. I don't know how you are supposed to determine when an AVPlayer has finished its audio I/O (apart from polling via setActive:false failing). My reasoning is that becoming the lock screen/"Now Playing App" has 3 pieces (this is sort of documented somewhere, this is the closest thing I can find to that right now, although the most explicit documentation of how this works is in the system logs):

  1. a non mixable audio session category
  2. MPRemoteCommandCenter integration
  3. current or "recently" finished audio IO

so if you can remove any one of these pieces, the lock screen controls should go away.

Billfish answered 27/5, 2019 at 8:29 Comment(3)
Removing the togglePlayPauseCommand target was the only thing that worked for me. All the other tips (setting the player to nil, etc...) didn't change anything to the error message I was getting.Ingulf
Any commands you add you must also remove, I should make that explicitBillfish
Yes, that was clear to me. I have removed all the targets I was using and it worked. My comment was in reference to the other solutions, which did not work for me.Ingulf
C
2

AVPlayer doesn't stop instantly, so you need to subscribe for its status change to catch when it got paused. Only then you can deactivate the AVAudioSession safely.

Swift 5 example

var timeControlObservation: NSKeyValueObservation?
timeControlObservation = player.observe(\.timeControlStatus, changeHandler: { [weak player] (_, _) in
    guard let player = player else {
        return
    }
    if player.timeControlStatus != .playing {
        // Finish your work here
        AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
        timeControlObservation = nil
    }
}
player.pause()
Capwell answered 29/7, 2019 at 11:51 Comment(0)
H
1

You getting that error because you are deactivating the audio session before deallocating the player: you can deallocate it with:

play.pause()
player = nil

but you should declare your player as optional first. You question is related to: Impossible to stop AVPlayer

Handmaiden answered 20/5, 2019 at 23:5 Comment(0)
C
0

You've to remove

player.replaceCurrentItem(with: nil)
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil

An only keep this

player.pause()
try? audioSession.setActive(true, options: .notifyOthersOnDeactivation)
Contumacious answered 25/5, 2019 at 7:34 Comment(1)
This didn't work. Still got: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.Ingulf
B
0

Try This

player.pause()
player.replaceCurrentItem(with: nil)
player = nil
Brooklyn answered 27/5, 2019 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.