12

I am learning Swift as my first programming language.

I've struggled for many hours to resume background audio playback after interruption (eg call)

What should happen:

  1. Audio keeps playing when app goes to background (works)
  2. When interrupted by a call, get the notification for interruption began (works)
  3. When call ends, get the notification for interruption ends (works)
  4. Resume playing the audio (does NOT work - hear nothing)

Really appreciate any help! Thanks

Notes:

  1. The app is registered for background audio and plays fine before interruption
  2. I have tried with and without the time delay to resume playing - neither work

Code:

import UIKit
import AVFoundation

var player: AVQueuePlayer!

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
            try AVAudioSession.sharedInstance().setActive(true, withOptions: .NotifyOthersOnDeactivation)
        } catch { }
        let songNames = ["music"]
        let songs = songNames.map { AVPlayerItem(URL:
            NSBundle.mainBundle().URLForResource($0, withExtension: "mp3")!) }
        player = AVQueuePlayer(items: songs)

        let theSession = AVAudioSession.sharedInstance()
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector:"playInterrupt:",
            name:AVAudioSessionInterruptionNotification,
            object: theSession)

        player.play()
    }

    func playInterrupt(notification: NSNotification) {

        if notification.name == AVAudioSessionInterruptionNotification
            && notification.userInfo != nil {

            var info = notification.userInfo!
            var intValue: UInt = 0
            (info[AVAudioSessionInterruptionTypeKey] as! NSValue).getValue(&intValue)
            if let type = AVAudioSessionInterruptionType(rawValue: intValue) {
                switch type {
                case .Began:
                    print("aaaaarrrrgggg you stole me")
                    player.pause()

                case .Ended:
                    let timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: "resumeNow:", userInfo: nil, repeats: false)
                }
            }
        }
    }
    func resumeNow(timer : NSTimer) {
        player.play()
        print("attempted restart")
    }
}
Simon Newstead
  • 395
  • 4
  • 19

5 Answers5

6

Finally got it!

Solution: added the mixable option by changing the setCategory line to be:

AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback,
                                            withOptions: .mixWithOthers )
Paul Nyondo
  • 2,126
  • 2
  • 23
  • 30
Simon Newstead
  • 395
  • 4
  • 19
1

I wrote a code like this and successfully resumed the audio from the phone call ended. I was build on Xcode 6.4 and ran the app on my iPhone 4S.

var player: AVQueuePlayer! = nil

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

override func viewDidAppear(animated: Bool)
{
    super.viewDidAppear(true)

    var song = AVPlayerItem(URL: NSBundle.mainBundle().URLForResource("AlmostAYearAgo", withExtension: "mp3")!)
    player = AVQueuePlayer(items: [song])

    let theSession = AVAudioSession.sharedInstance()

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "playInterrupt:", name: AVAudioSessionInterruptionNotification, object: theSession)

    player.play()
}

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

func playInterrupt(notification: NSNotification)
{
    if notification.name == AVAudioSessionInterruptionNotification && notification.userInfo != nil
    {
        var info = notification.userInfo!
        var intValue: UInt = 0

        (info[AVAudioSessionInterruptionTypeKey] as! NSValue).getValue(&intValue)

        if let type = AVAudioSessionInterruptionType(rawValue: intValue)
        {
            switch type
            {
            case .Began:
                print("aaaaarrrrgggg you stole me")
                player.pause()
            case .Ended:
                let timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: "resumeNow:", userInfo: nil, repeats: false)
            }
        }
    }
}

func resumeNow(timer : NSTimer)
{
    player.play()
    print("attempted restart")
}
Jason Nam
  • 2,011
  • 2
  • 13
  • 22
  • Tried it, but doesn't seem to do anything. Audio still doesn't play on resume. – Simon Newstead Sep 13 '15 at 09:57
  • in both: case .Ended and in resumeNow just before player.play() have you managed to get it working? – Simon Newstead Sep 13 '15 at 10:00
  • Check this answers http://stackoverflow.com/questions/12461691/avplayer-doesnt-resume-playback-on-endinterruption-on-iphone-but-does-so-on-ipa – Jason Nam Sep 13 '15 at 10:16
  • Hi Jason thanks but that doesn't seem to help solve the problem (and was from a long while ago - can I see where it's been fixed or documented?) – Simon Newstead Sep 14 '15 at 03:05
  • Thanks Jason, read through prior that but was a bit confused because in the linked docu on that page: https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioPlayerDelegateProtocolReference/index.html#//apple_ref/doc/uid/TP40008068 it says the handling audio parts are deprecated. do you have any suggestions? by the way, as a beginner I really appreciate help from experienced folks like you thanks! – Simon Newstead Sep 15 '15 at 14:23
  • Sorry for the documantation things ;( Would you check my answer again? Seems like I can resume the song without any problem.. – Jason Nam Sep 16 '15 at 10:59
  • Hi Jason, thanks - still stuck :( I copied and ran the code but I had first the problem the audio stopped when going to background. So I added the "do" "try" with setting category to AVAudioSessionCategoryPlayback etc as my original code and it went to background, but still didn't resume audio after a call. Can you make it work in background? Thanks Jason! – Simon Newstead Sep 17 '15 at 14:16
0

For me reload player after end interruption resolve the problem:

    func interruptionNotification(_ notification: Notification) {
    guard let type = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
      let interruption = AVAudioSessionInterruptionType(rawValue: type) else {
        return
    }
    if interruption == .ended && playerWasPlayingBeforeInterruption {
      player.replaceCurrentItem(with: AVPlayerItem(url: radioStation.url))
      play()
    }
  }
Vitalii Gozhenko
  • 9,220
  • 2
  • 48
  • 66
0

For me it was adding a delay that fixed the problem:

@objc func handleInterruption(_ notification: Notification) {
    // audio interuption - restart audio player for any type (began or ended) to make sure it keeps going
    guard let type = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
        let interruption = AVAudioSession.InterruptionType(rawValue: type) else {
            NSLog("*** Incorrect notification format")
            return
    }

    if interruption == .began {
        NSLog("audio interruption: began")
    } else if interruption == .ended {
        NSLog("audio interruption: ended")
        if isRunning {
            // Sadly this lengthy delay hack is necessary, otherwise sounds won't play :(
            DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
                self.start() // sets up the audio session, connects nodes, starts the engine, plays the player, and sets isRunning to true
            }
        }
    }
}

And the delay really does need to be this long. 2s didn't do the trick.

John Scalo
  • 3,194
  • 1
  • 27
  • 35
-1

Simon, absolutely confirm it on my end. I spend 2 days looking for it! If you use just:

AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback) then does not matter what you do your player will not resume playing audio if you use instead:

AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.MixWithOthers)

then it works like perfect and will resume after receiving phone call while the app is in the background.

Thanks!

aurawindsurfing
  • 423
  • 4
  • 11
  • Simon, not sure if you noticed but this is not the working solution. I thought the solution was working but when you use: AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.MixWithOthers then your player will mix with others. Try staring music app as well in the same time. You will get 2 sounds playing at the same time and mixing. It works for resuming after the call while in the background but it messes up the whole playback of your sound. Anyone any idea please? – aurawindsurfing Sep 19 '15 at 10:05
  • The thing that we were missing was self.becomeFirstResponder() once that was added the whole thing started to work for me. – aurawindsurfing Sep 19 '15 at 17:16
  • Good to know, thanks. I don't need it because my app is a silent meditation app and they won't mix audio themselves, so hadn't tested that option. – Simon Newstead Sep 21 '15 at 07:37