0

I am building a streaming audio app using SoundCloud API. There are 4 view controllers and the 4th view controller is the now playing track VC - similar to the Now Playing scene on the Music app.

When the app is in the foreground, everything is fine and the next audio track starts playing correctly. When the the app moves to the background, the current track keeps playing but as soon as it ends, the next track does not play until I bring the app back into the foreground. I have checked a bunch of existing issues such as iOS background audio not playing but none of the suggested solutions work.


I've pasted below the code for the playTrack method which is used to play tracks. When I use the debugger I can see that if I add a breakpoint inside the closure below, the breakpoint is not triggered until the app enters the foreground.

func playTrack(track: SongSearchResult) {
println("Playing track: \(track.title)")
Logging.sharedInstance.writeData(String(format: "%@:Playing track(%@)", __FUNCTION__, track.title))
audioPlayer?.stop()
if playPauseButton.enabled == false {
  if let request: AnyObject = currentGetRequest {
    SCRequest.cancelRequest(request)
  }
}

currentGetRequest = SCRequest.performMethod(SCRequestMethodGET,
                        onResource: NSURL(string: track.streamURL),
                        usingParameters: nil,
                        withAccount: SCSoundCloud.account(),
                        sendingProgressHandler: nil,
                        responseHandler: {(response, data, error) in

  dispatch_async(dispatch_get_main_queue()) {
    var playerError: NSError? -----------------> BREAKPOINT SET HERE
    AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
    AVAudioSession.sharedInstance().setActive(true, error: nil)
    UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
    self.audioPlayer = AVAudioPlayer(data: data, error: &playerError)
    self.audioPlayer?.delegate = self
    self.delegate?.currentTrackChanged(self)
    self.audioPlayer?.prepareToPlay()
    self.audioPlayer?.play()
    self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateTime:"), userInfo: nil, repeats: true)
    self.playPauseButton.enabled = true
    self.currentTimeSlider.enabled = true
  }
})

}


I have already set the UIBackgroundModes 'audio' value in the Info.plist file. Does someone have any suggestions of what I'm doing wrong?

Thanks, Abhishek.

Community
  • 1
  • 1
inc_london
  • 461
  • 5
  • 11

3 Answers3

0

Ok - so I think I figured out the problem. Look at this link: objective c - Proper use of beginBackgroundTaskWithExpirationHandler

Because the application is registered for background audio, as soon as a track ends, background audio stops and as a result, the application (which is in the background) does not get time to complete any other tasks i.e. skip to the next track.

Community
  • 1
  • 1
inc_london
  • 461
  • 5
  • 11
0

I would also add to this that you are setting the AVAudioSession category/active and .beginReceivingRemoteControlEvents() inside the dispatch_async() call, which should really be done on a global level when the playback mechanism is set up initially (so probably in your viewDidLoad in your case).

I'm not 100% sure but I don't think that the dispatch_asyn(dispatch_get_main_queue()) will even be executed in the background? Haven't verified this either way, but it would make sense that the main queue is suspended when in the background. Try removing that dispatch call entirely. Not familiar with SCRequest, but it may already be calling that completion block on the main queue anyway if it is similar to AFNetworking.

Bhavin Bhadani
  • 22,224
  • 10
  • 78
  • 108
manderson
  • 666
  • 6
  • 15
0

It works to my app.

let session = AVAudioSession.sharedInstance()
do {
  try audioSession.setActive(true)
    
  if #available(iOS 12.0, *) {
      try audioSession.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.voicePrompt)
  } else {
      try audioSession.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default)
  }
UIApplication.shared.beginReceivingRemoteControlEvents()
} catch {
  print("AVAudioSessionCategoryPlayback failed.")}
 }
Serena
  • 1