5

I am playing several sounds, which each dim the background audio. When they are done, I restore background audio. What happens is every time one of the audio files play, the background dims (as desired). When the last audio finishes playing the background audio is restored (also desired). However after about 5 seconds, it throws this error and dims the audio again (not what I want since all sounds are now finished).

ERROR: [0x19c9af310] AVAudioSession.mm:646: -[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.

To my knowledge I am stopping and removing all audio.

There is 1 post I found here:

iOS8 AVAudioSession setActive error

But the solution does not work for me. Here is my audio player class. If you can advise what might be up I'd appreciate it.

import Foundation
import AVFoundation

private var _singleton:O_Audio? = O_Audio()
private var _avAudioPlayers:Array<AVAudioPlayer> = []

//Manages dimming and resuming background audio
class O_Audio:NSObject, AVAudioPlayerDelegate
{
    class var SINGLETON:O_Audio
    {
        if(_singleton == nil)
        {
            _singleton = O_Audio()
        }
        return _singleton!
    }

    class func dimBackgroundAudio()
    {
        AVAudioSession.sharedInstance().setActive(true, error: nil)
    }

    class func restoreBackgroundAudio()
    {
        AVAudioSession.sharedInstance().setActive(false, error: nil)
    }

    class func playSound(path:String)
    {
        var sound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(path, ofType: "m4a")!)
        var audioPlayer = AVAudioPlayer(contentsOfURL: sound, error: nil)
        _avAudioPlayers.append(audioPlayer)
        audioPlayer.delegate = O_Audio.SINGLETON
        audioPlayer.prepareToPlay()
        audioPlayer.play()
    }

    func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool)
    {
        //this was from the 1 stack post I found but these
        //two lines do not solve my problem
        player.stop()
        player.prepareToPlay()

        var index:Int!
        for i in 0..._avAudioPlayers.count - 1
        {
            if(_avAudioPlayers[i] == player)
            {
                index = i
                break
            }
        }

        _avAudioPlayers.removeAtIndex(index)

        if(_avAudioPlayers.count == 0)
        {
            O_Audio.restoreBackgroundAudio()
        }
    }

    func audioPlayerDecodeErrorDidOccur(player: AVAudioPlayer!, error: NSError!)
    {
        println("error")
    }
}

Important Update

So I've found what I think is a rough cause of the issue. Our App is built on Cordova. So we have a lot of Safari (browser) calls. And this bug is occurring whenever we play a video (which is played via Safari). It seems like Safari is somehow dimming the audio and keeping a running I/O thread.

Community
  • 1
  • 1
Aggressor
  • 13,323
  • 24
  • 103
  • 182
  • Which code is calling the `dim` function? Most importantly, when. You should put NSLog in all functions and track the chronology of the calls. Something is happening there after 5 seconds and we need to know the order of events. – Leonid Usov Aug 06 '15 at 22:24
  • Nothing is actually calling that at the moment. Dimming is happening automatically when the audioPlayer.play() occurs. (The OS is automatically dimming the background audio) – Aggressor Aug 06 '15 at 22:32
  • @Aggressor: Have you figered it out, why the display is dimming? – Kaptain Nov 01 '15 at 07:01
  • @Eugen unfortunately I've not heard back from Apple. I filed a bug. At first they replied a lot, and I gave them a sample project. Once they got it then went dark and I can't get a word out of them (after a half dozen requests). Gotta love apple support! – Aggressor Nov 01 '15 at 18:44
  • @Aggressor: Thanks for quick response. I noticed that when I play a audio stream in safari the screen gets dimming but not locked. I want the same for my app. – Kaptain Nov 02 '15 at 09:14

4 Answers4

1

The issue is the fact that an MPMoviePlayerController object is playing. In fact Any AVPlayerItem causes this. If you play a movie, and try to dim audio you will get this error.

At present, any movie played on iOS has an un-mutable audio track (even if there is no audio on the movie file). That permanently causes the duck issue (its a bug in the source code). I tried many workarounds and nothing worked. I am certain this is an Xcode source bug.

Aggressor
  • 13,323
  • 24
  • 103
  • 182
  • I had the same issue and almost wanted to give up. But I did some more testing in a separate test project and finally found a solution. If you still have this issue check out my answer: https://stackoverflow.com/a/64013477/4181169 – iVentis Sep 22 '20 at 16:04
1

@Aggressor: you can change the audio category to multi route so that the audio plays through speaker and headphones(if plugged in) at the same time.

By this way you won't get the dimmed audio.

saurabh_s
  • 150
  • 1
  • 2
  • 15
0

I have the same issue. You must call AVAudioSession.sharedInstance().setActive(true, error: nil); before AVAudioSession.sharedInstance().setActive(false, error: nil);

And they are appear in pairs.

user501836
  • 1,106
  • 1
  • 12
  • 20
0

I had the same issue. My app has a video playback (without sound) using AVPlayer, and occasionally short spoken audio using AVQueuePlayer while the video is still playing. Now if I had music playing in the background (i.e. via Spotify) the music ducked while the AVQueuePlayer was playing, un-ducked for a brief moment after it finished and then ducked again.

I created a sample app to debug my issue and found that my AVAudioSession setup was just not handled correctly. What worked for me in the end was the following:

At app start set AVAudioSession.CategoryOptions to .ambient like so:

try AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers)

this will prevent the video player from interrupting the music.

If I play/pause my AVQueuePlayer I change my category again using this functions:

func setAudioSession(to category: Category, activate: Bool = true) {
        switch category {
        case .default:
            // .ambient and .mixWithOthers so silent video won't interrupt background music
            setCategory(.ambient, options: .mixWithOthers, activate: activate)
        case .voiceOver:
            // .playback with .duckOthers will duck the music momentarily while short lasting audio is output, spoken audio will be interrupted by setting . interruptSpokenAudioAndMixWithOthers
            setCategory(.playback, options: [.duckOthers, .interruptSpokenAudioAndMixWithOthers], activate: activate)
        case .standalone:
            // For standalone audio use .playback without options so it will interrupt other background music, if you don't want the sound to play while the phone is on mute use .ambient instead
            setCategory(.playback, activate: activate)
        }
    }
    
    private func setCategory(_ category: AVAudioSession.Category, options: AVAudioSession.CategoryOptions = [], activate: Bool) {
        let session = AVAudioSession.sharedInstance()
        // Only set if needed
        guard session.category != category || session.categoryOptions != options else { return }
        // To change category, first setActive(false) -> change -> setActive(true)
        try? AVAudioSession.sharedInstance().setActive(false)
        try? AVAudioSession.sharedInstance().setCategory(category, options: options)
        if activate {
            try? AVAudioSession.sharedInstance().setActive(true)
        }
    }

enum Category {
        case `default`, voiceOver, standalone
    }

So basically for starting audio voice over

   @IBAction func playAudio(_ sender: UIButton) {
        self.setAudioSession(to: .voiceOver)
        audioPlayer.load(url: Bundle.main.url(forResource: "audio", withExtension: "mp3")!)
        audioPlayer.play()
    }

and then to pause (or after queue finishes):

@IBAction func pauseAudio(_ sender: UIButton) {
    audioPlayer.pause()
    self.setAudioSession(to: .default)
}

⚠️ Disclaimer: Notice how i do try? instead of do try catch block, this is because I'm still getting the Deactivating an audio session that has running I/O. error. So if i would catch the error, it wouldn't active the correct session anymore. It's still not ideal because it will always output the error but at least ducking works like a charm now.

iVentis
  • 993
  • 6
  • 19