Reputation: 381
I am playing Apple Music using [MPMusicPlayerController applicationMusicPlayer]. (AVPlayer and AVAudioPlayer don't work for Apple Music.)
While in the background, I need to know when the song has ended so I can play the next song. Unfortunately, MPMusicPlayerControllerPlaybackStateDidChangeNotification isn't fired while the app is in the background.
So I created a background task, using beginBackgroundTaskWithExpirationHandler, which fires an NSTimer once a second to check for the end of song. I have the audio background mode set, but my task still only gets 3 minutes. It was my understanding that if I use the audio background mode, my task would get infinite time. Is that not true? If not, how do I handle this? Surely others have come across this situation.
Upvotes: 1
Views: 1198
Reputation: 381
For future reference:
I found that MPMusicPlayerController never engages audio UIBackgroundMode, using either applicationMusicPlayer or systemMusicPlayer. So you can never get more than 3 minutes background time. However, AVAudioPlayer DOES enagage audio background mode. So my (ugly) solution is to loop a silent MP3 using AVAudioPlayer while in the background. I'm going to assume Apple won't protest, this seems like a legitimate use of silent audio. Note that for both AVAudioPlayer and MPMusicPlayerController to play at the same time, you must have AVAudioSessionCategoryOptionMixWithOthers set for the AVAudioSession.
Upvotes: 3
Reputation: 193
I have released almost same app in AppStore. It plays songs using [MPMusicPlayerController applicationMusicPlayer].
Yes, MPMusicPlayerControllerPlaybackStateDidChangeNotification did not came to background app. So, I used the NSTimer to determine when the song maybe ended. You set NSTimer to 1sec. I set NSTimer to the period of the song's play-time. i.e. when the song has 3 minutes 15 secs time, I set the NSTimer to 3 minutes 15 secs.
When user pause the music, I pause the NSTimer. And when user resume the music, I set the NSTimer to the rest time of the song.
It works very well. I hope this may help you. Sorry for my cheep English.
Step you may need;
- (void)applicationWillResignActive:(UIApplication *)application { _bgid = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) { [[UIApplication sharedApplication] endBackgroundTask:_bgid]; _bgid = UIBackgroundTaskInvalid; }]; } - (void)applicationDidBecomeActive:(UIApplication *)application { if ( _bgid != UIBackgroundTaskInvalid ) { [[UIApplication sharedApplication] endBackgroundTask:_bgid]; } }
3.Use NSTimer in non-repeated mode.
#import "NMCountdownTimer.h" @interface NMCountdownTimer () { @private NSTimer *_timer; NSTimeInterval _rest; NMMethodCompletion _completion; } @end @implementation NMCountdownTimer - (void)start:(NSTimeInterval)secs completion:(NMMethodCompletion _Nullable)completion { _completion = completion; if ( secs > 0.0f ) { NSLog(@"[ ] start (secs:%f)", secs); _timer = [NSTimer scheduledTimerWithTimeInterval:secs target:self selector:@selector(fired) userInfo:nil repeats:FALSE]; _rest = 0.0f; } } - (void)pause { if ( _timer && ( _rest == 0.0f ) ) { _rest = [[_timer fireDate] timeIntervalSinceReferenceDate] - [[NSDate date] timeIntervalSinceReferenceDate]; NSLog(@"[ ] pause (rest:%f)", _rest); [_timer invalidate]; _timer = nil; } } - (void)resume { if ( ( ! _timer ) && ( _rest > 0.0f ) ) { NSLog(@"[ ] resume (rest:%f)", _rest); _timer = [NSTimer scheduledTimerWithTimeInterval:_rest target:self selector:@selector(fired) userInfo:nil repeats:FALSE]; _rest = 0.0f; } } - (void)kill { [_timer invalidate]; _timer = nil; _rest = 0.0f; _completion = nil; NSLog(@"[ ] kill"); } - (void)fired { [_timer invalidate]; _timer = nil; _rest = 0.0f; if ( _completion ) { _completion ( nil ); } NSLog(@"[ ] fired"); } - (void)dealloc { #if ( Namera_SHOW_DEALLOC && FALSE ) NSLog(@"[DEA] %@ NMCountdownTimer", self ); #endif _timer = nil; _completion = nil; } @end
somewhere in your code like this; NSTimeInterval playback_duration = [[song valueForProperty: MPMediaItemPropertyPlaybackDuration] doubleValue]; .... MPMediaItemCollection *collection = [MPMediaItemCollection collectionWithItems:[NSArray arrayWithObject:song]]; [_musicCtr setQueueWithItemCollection:collection]; [_musicCtr play]; [_countdown_timer start:playback_duration completion:^(id _Nullable object) { [weakSelf execute_next_song]; }];
Upvotes: 0