8

I've properly enabled background audio for my app (in the plist). Playing the next track after the current is complete using SPPlaybackManager in the background (when the phone is locked/off) doesn't work.

When the current track ends, and the audio stops, the app won't begin playing the next track until the phone is unlocked and my app becomes active again.

How do I fix this? Here is a snippet of code I'm using to begin the playing of the next track. I observe that the current track becomes nil, and then begin playing the next track. The log shows me that the next current track is being set in the playback manager object, but it alas is silent.

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {



    if([keyPath isEqualToString:@"spotifyPlaybackManager.currentTrack"]){

        NSLog(@"%@ %@",keyPath,self.spotifyPlaybackManager.currentTrack);

        if(self.spotifyPlaybackManager.currentTrack==nil && self.mode == PlayerModeSpotify){

            NSLog(@"PLAY NEXT");
            [self.spotifyPlaybackManager playTrack:self.nextSPTrack callback:^(NSError *error){
                if(error) TKLog(@"Spotify Playback Error %@",error);
            }];
        }
        [[NSNotificationCenter defaultCenter] postNotificationName:PlayerNowPlayingItemDidChange object:self];
        return;
    }



    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}

Console:

spotifyPlaybackManager.currentTrack (null)
PLAY NEXT
spotifyPlaybackManager.currentTrack <SPTrack: 0x60f8390>: Karaoke
devinross
  • 2,816
  • 6
  • 34
  • 34

2 Answers2

8

The solution is very simple but it took me a year to realize it. My old solution was to start a background task just before the previous track ended, and keep it running until the next track was playing. That was very error prone. Instead:

Keep track of your playing state (playing or paused). Whenever you transition into Playing, start a background task. Never stop it, unless you transition into Paused. Keep the state as Playing even between tracks. As long as you have the audio background mode in your info.plist and audio is playing, your background task will have an infinite timeout.

Some pseudo code:

@interface PlayController
@property BOOL playing;

- (void)playPlaylist:(SPPlaylist*)playlist startingAtRow:(int)row;
@end

@implementation PlayController
- (void)setPlaying:(BOOL)playing
{
    if(playing == _playing) return;
    _playing = playing;

    UIApplication *app = [UIApplication sharedApplication];
    if(playing)
        self.playbackBackgroundTask = [app beginBackgroundTaskWithExpirationHandler:^ {
            NSLog(@"Still playing music but background task expired! :(");
                    [app endBackgroundTask:self.playbackBackgroundTask];
                self.playbackBackgroundTask = UIBackgroundTaskInvalid;
            }];
    else if(!playing && self.playbackBackgroundTask != UIBackgroundTaskInvalid)
        [app endBackgroundTask:self.playbackBackgroundTask];
}
...
@end

Edit: Oh, and I finally blogged about it.

nevyn
  • 7,052
  • 3
  • 32
  • 43
  • Worked like a charm. You are a true savior! – Nailer Jun 12 '13 at 15:16
  • Ah, snap. Didn't work after all. This wasn't my problem in the first place it seems. – Nailer Jun 12 '13 at 17:05
  • Thanks for the post nevyn. But doesn't `endBackgroundTask:` stop the background task that we have just started, therefore the application cannot run any other code again? i.e. after calling `endBackgroundTask:` the app won't be able to call `setPlaying:` again – Alex Aug 28 '13 at 20:57
  • Andrei: The endBackgroundTask is inside the else clause, so it will only be called when you STOP playing. After which it is natural that you can't start playing until the user activates the app again (because only the user can start playback). Remember that this method is for whether the user is *semantically* playing, not whether you are currently receiving streaming audio, or if you're between tracks. – nevyn Sep 04 '13 at 08:31
-2

CocoaLibSpotify does a lot of work to start playing a track and will likely spawn new internal threads in the process. I doubt this is allowed in the audio style of backgrounding so you'll likely need to start a temporary background task to change tracks.

iKenndac
  • 18,730
  • 3
  • 35
  • 51
  • I just tried putting 'beginBackgroundTaskWithExpirationHandler' in directly before calling 'self.spotifyPlaybackManager playTrack:...' and the code suspends running as soon as a new task is attempted to be created. Any other suggestions? – devinross Jul 18 '12 at 08:32
  • My audio app (GroovePond) does a lot of background tasks when playing background audio, such as accepting new clients and updating existing clients' state, and Apple don't seem to have any problem with it. I think that because the tasks are directly related to the playback of audio, they are allowed. – Dermot Feb 22 '13 at 02:38