Refresh application state running on background - ios

I have music player app, and when app goes background it show music control on locked screen, in my case currently playing on radio artist and song. I use following:
- (void)applicationWillResignActive:(UIApplication *)application {
[[PlayerManager sharedInstance] setupInfoForLockerScreen];
}
-(void)setupInfoForLockerScreen{
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
NSString *songName = self.currentPlaylist.lastItem.track.song.length > 0 ? self.currentPlaylist.lastItem.track.song : #"";
NSString *artistName = self.currentPlaylist.lastItem.track.artist.length > 0 ? self.currentPlaylist.lastItem.track.artist : #"";
infoCenter.nowPlayingInfo = #{
MPMediaItemPropertyTitle: self.currentPlaylist.title,
MPMediaItemPropertyArtist: songName.length > 0 && artistName.length > 0 ? [NSString stringWithFormat:#"%# - %#", songName, artistName] : #"",
MPMediaItemPropertyPlaybackDuration: #(0)
};
}
Problem is, when data changed and next song will be on radio, how do i tell my app to refresh itself? applicationWillResignActive i guess called only once when app initially goes to background.

The MPMusicPlayerController class has some methods and events to help with this.
First you need to tell your application to listen for the MPMusicPlayerControllerNowPlayingItemDidChangeNotification event:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNowPlayingItemChangedEvent:) name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:self.myMusicPlayer];
This registers an event handler that gets called whenever the currently playing song changes.
Then call the beginGeneratingPlaybackNotifications method on your MPMusicPlayerController to tell it to start sending you playback notifications.
[self.myMusicPlayer beginGeneratingPlaybackNotifications];
You can control when you want to be notified and when you don't by calling beginGeneratingPlaybackNotifications and endGeneratingPlaybackNotifications as needed.
Then create the event handler. This is the method that will get called every time the MPMusicPlayerControllerNowPlayingItemDidChangeNotification fires:
- (void)handleNowPlayingItemChangedEvent:(NSNotitication*)notification
{
// Update the lock screen info here
}
Now whenever the currently playing song changes, your event handler will get called and you can update your now playing info.

Related

How to update MPMusicPlayerController when song changes

I was wondering you would update the UI when the song changes in MPMusicPlayerController. For example, if you have an image view with the album cover, but a user presses the skip button, how would you update the image view's image with the new album cover?
To control music playback, we use an instance of MPMusicPlayerController. There are two types of music players. The iPodMusicPlayer is a reference to the music player instance used by the iPod app. Any settings you change, such as the shuffle or repeat modes, will be changed in the iPod app, too. If the iPod is playing when your application starts, the music will continue playing and you can access the current song and skip back and forward through the currently active playlist. When your app quits, the music will continue playing. I imagine this mode is very handy for most utility apps that try to improve your music listening experience by interacting with the iPod.
In contrast, applicationMusicPlayer gives you a music player whose settings you can change independently of the iPod app. This is probably the way to go if your app is a game and you want to give the user the ability to choose the background music from their library. In Songtext, we’ll use iPodMusicPlayer because we want to know which song is playing when our app launches:
#property (nonatomic, strong) MPMusicPlayerController *musicPlayer;
self.musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
The music player uses notifications to inform you about changes of:
- The current song (MPMusicPlayerControllerNowPlayingItemDidChangeNotification),
- The play/paused/stopped state (MPMusicPlayerControllerPlaybackStateDidChangeNotification), or
- The volume (MPMusicPlayerControllerVolumeDidChangeNotification).
So the next thing you typically do is to register yourself as an observer for the notifications you are interested in, e.g. in viewDidLoad. We want to receive all 3 notifications:
// Register for music player notifications
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:#selector(handleNowPlayingItemChanged:)
name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification
object:self.musicPlayer];
[notificationCenter addObserver:self
selector:#selector(handlePlaybackStateChanged:)
name:MPMusicPlayerControllerPlaybackStateDidChangeNotification
object:self.musicPlayer];
[notificationCenter addObserver:self
selector:#selector(handleExternalVolumeChanged:)
name:MPMusicPlayerControllerVolumeDidChangeNotification
object:self.musicPlayer];
[self.musicPlayer beginGeneratingPlaybackNotifications];
There is one other related notification that is sent by the iPod media library when the contents of the library change, e.g. when you sync your device with iTunes. You must listen to this notification if your app creates its playlists that need to be updated after library changes. To do so, register yourself as an observer for MPMediaLibraryDidChangeNotification notifications and call:
[[MPMediaLibrary defaultMediaLibrary] beginGeneratingLibraryChangeNotifications]
The notification handlers are where you update your UI in response to changes in the player’s state:
// When the now playing item changes, update song info labels and artwork display.
- (void)handleNowPlayingItemChanged:(id)notification {
// Ask the music player for the current song.
MPMediaItem *currentItem = self.musicPlayer.nowPlayingItem;
// Display the artist, album, and song name for the now-playing media item.
// These are all UILabels.
self.songLabel.text = [currentItem valueForProperty:MPMediaItemPropertyTitle];
self.artistLabel.text = [currentItem valueForProperty:MPMediaItemPropertyArtist];
self.albumLabel.text = [currentItem valueForProperty:MPMediaItemPropertyAlbumTitle];
// Display album artwork. self.artworkImageView is a UIImageView.
CGSize artworkImageViewSize = self.artworkImageView.bounds.size;
MPMediaItemArtwork *artwork = [currentItem valueForProperty:MPMediaItemPropertyArtwork];
if (artwork != nil) {
self.artworkImageView.image = [artwork imageWithSize:artworkImageViewSize];
} else {
self.artworkImageView.image = nil;
}
}
// When the playback state changes, set the play/pause button appropriately.
- (void)handlePlaybackStateChanged:(id)notification {
MPMusicPlaybackState playbackState = self.musicPlayer.playbackState;
if (playbackState == MPMusicPlaybackStatePaused || playbackState == MPMusicPlaybackStateStopped) {
[self.playPauseButton setTitle:#"Play" forState:UIControlStateNormal];
} else if (playbackState == MPMusicPlaybackStatePlaying) {
[self.playPauseButton setTitle:#"Pause" forState:UIControlStateNormal];
}
}
// When the volume changes, sync the volume slider
- (void)handleExternalVolumeChanged:(id)notification {
// self.volumeSlider is a UISlider used to display music volume.
// self.musicPlayer.volume ranges from 0.0 to 1.0.
[self.volumeSlider setValue:self.musicPlayer.volume animated:YES];
}
In the case you are looking for the lock screen playing info:
Have a look at MPNowPlayingInfoCenter
You need to pass a NSDictionary, here's an example:
-(void)updateNowPlayingInfo
{
//Set Values for MPNowPlayingInfoCenter
NSArray *keys = [NSArray arrayWithObjects:
MPMediaItemPropertyTitle,
MPMediaItemPropertyPlaybackDuration,
MPNowPlayingInfoPropertyPlaybackRate,
MPNowPlayingInfoPropertyElapsedPlaybackTime,
nil];
NSArray *values = [NSArray arrayWithObjects:
self.currentPlayingVideo.title,
[NSNumber numberWithFloat:self.currentPlayingVideo.duration],
[NSNumber numberWithInt:1],
[NSNumber numberWithDouble:self.currentVideoPlayView.videoPlayerViewController.moviePlayer.currentPlaybackTime],
nil];
NSDictionary *mediaInfo = [NSDictionary dictionaryWithObjects:values forKeys:keys];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:mediaInfo];
}

Lock Screen iPod Controls Not Working With Spotify Music Player

I added the Spotify player to my app which also plays music using the MPMusicPlayerController. When music is playing from Spotify and the screen is locked, the remote control events are not received for play/pause and FFW/RWD when the user presses these buttons on the locked screen.
If music is playing from the MPMusicPlayerController, I am able to receive the remote control events based on the following code:
-(void) ViewDidLoad {
...
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
...
}
and
- (BOOL) canBecomeFirstResponder
{
return YES;
}
- (void) remoteControlReceivedWithEvent: (UIEvent*) event
{
// see [event subtype] for details
if (event.type == UIEventTypeRemoteControl) {
// We may be receiving an event from the lockscreen
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
case UIEventSubtypeRemoteControlPlay:
case UIEventSubtypeRemoteControlPause:
// User pressed play or pause from lockscreen
[self playOrPauseMusic:nil];
break;
case UIEventSubtypeRemoteControlNextTrack:
// User pressed FFW from lockscreen
[self fastForwardMusic:nil];
break;
case UIEventSubtypeRemoteControlPreviousTrack:
// User pressed rewind from lockscreen
[self rewindMusic:nil];
break;
default:
break;
}
}
}
While the iPod controls are visible when the app enters the background, they do not respond when I press pause. Instead, the iPod controls disappear when I press pause. What addition is needed to enable detection of play/pause and FFW/RWD when streaming audio such as Spotify is playing in the background from lock screen?
I believe I ran into this in the past. If I remember correctly I added in the
-(void)remoteControlReceivedWithEvent:(UIEvent *) event { ... }
as well as
- (BOOL) canBecomeFirstResponder { return YES; }
to the app delegate (This is also where my audio controller lived). I was having having the issue where the UIViewControllers were not alive during the time I wanted to catch the UIEventTypeRemoteControl notifications.
Give that a try and see if that helps.
After further investigation, I have found that if include the following code when my app enters the background and when the remote control events are received, the iPod controls do not disappear.
// Set up info center to display album artwork within ipod controls (needed for spotify)
MPMediaItemArtwork *ipodControlArtwork = [[MPMediaItemArtwork alloc]initWithImage:artworkImage];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = [NSDictionary dictionaryWithObjectsAndKeys:nowPlayingTitle, MPMediaItemPropertyTitle,
nowPlayingArtist, MPMediaItemPropertyArtist, ipodControlArtwork, MPMediaItemPropertyArtwork, [NSNumber numberWithDouble:0.0], MPNowPlayingInfoPropertyPlaybackRate, nil];

iOS: How to get a Timer app to play custom sounds when the APP is in background?

General problem / application background / purpose:
I have a Timer app and I would like to play a custom sound when the App is in BACKGROUND state and receives a UILocalNotification.
I utilize UILocalNotification to trigger the timer (its the only way as an App cannot run a process in the background for longer than 10 minutes).
Issues:
Is there a way to play a different UILocalNotification sound? If so how can I do it? (In other words: when the app receives the UILocalNotification the custom sound is played - which is not loud enough.. I would like to get a proper alarm sound)
This is how I set my sound when I send my UILocalNotification:
_localNotif.soundName = UILocalNotificationDefaultSoundName;
I then create the notification and post it. Once the app receives it I handle it in the following method (please read the code comments for added information):
- (void)application:(UIApplication *)application didReceiveLocalNotification: (UILocalNotification *)notification {
NSLog(#"received notification");
// Here I try to play a custom sound created in a custom object
// however the sound does NOT get played when the APP is in BACKGROUND mode (but I still see the NSLog message in console "received notification"
SoundManager* audio = [SoundManager sharedInstance];
[audio playSound:Standard];
}
Try changing your line
_localNotif.soundName = UILocalNotificationDefaultSoundName;
To something like this:
_localNotif.soundName = "customSoundFileName.customSoundFileExtension";
As it states in official docs you have to specify the filename of a sound resource in your app’s main bundle.
I have a working example that I can share with you if you need it.
Good luck!
In the view controller where you fire the local notification:
.h
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController <AVAudioPlayerDelegate>
#property (nonatomic, strong) AVAudioPlayer *notificationSound;
You don't have to fire a local notification if it enters the background in the app delegate, a local notification will fire regardless if they are in the background or not. didReceiveLocalNotification is how you handle it when the app is active : From the docs here :
Sent to the delegate when a running app receives a local notification.
.m
-(void)startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimerTasks:) userInfo:nil repeats:YES];
[self fireLocalNotification];
}
-(void)fireLocalNotification {
UILocalNotification *timerDoneNotification = [[UILocalNotification alloc] init];
timerDoneNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:secondsLeft];
timerDoneNotification.alertBody = #"Time Up!";
timerDoneNotification.alertAction = #"OK";
timerDoneNotification.timeZone = [NSTimeZone defaultTimeZone];
timerDoneNotification.soundName = #"alarm.aif";
[[UIApplication sharedApplication] scheduleLocalNotification:timerDoneNotification];
}
And then if you just want to play/stop the sound elsewhere like if the secondsLeft is 0 when the app is active you call it as follows :
[self playAlarm];
-(void)playAlarm {
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *filePath = [mainBundle pathForResource:#"alarm" ofType:#"aif"];
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
NSError *error = nil;
self.notificationSound = [[AVAudioPlayer alloc] initWithData:fileData error:&error];
self.notificationSound.numberOfLoops = -1;
[self.notificationSound play];
}
And to stop it:
[self.notificationSound stop];
Downside to this is UILocalNotification sounds can not be longer than 30 seconds but it answers the question at hand.
Reference : UILocalNotification Docs
Points of interest :
When the system delivers a local notification, several things can happen, depending on the app state and the type of notification. If the app is not frontmost and visible, the system displays the alert message, badges the app, and plays a sound—whatever is specified in the notification. If the notification is an alert and the user taps the action button (or, if the device is locked, drags open the action slider), the app is woken up or launched. (If the user taps one of the custom actions you specify using the additionalActions property, the app is woken up or launched into the background.) In its application:didFinishLaunchingWithOptions: method, the app delegate can obtain the UILocalNotification object from the launch options dictionary using the UIApplicationLaunchOptionsLocalNotificationKey key. The delegate can inspect the properties of the notification and, if the notification includes custom data in its userInfo dictionary, it can access that data and process it accordingly. On the other hand, if the local notification only badges the app icon, and the user in response launches the app, the application:didFinishLaunchingWithOptions: method is called, but no UILocalNotification object is included in the options dictionary. When the user selects a custom action, the app delegate’s application:handleActionWithIdentifier:forLocalNotification:completionHandler: method is called to handle the action.
If the app is foremost and visible when the system delivers the notification, the app delegate’s application:didReceiveLocalNotification: is called to process the notification. Use the information in the provided UILocalNotification object to decide what action to take. The system does not display any alerts, badge the app’s icon, or play any sounds when the app is already frontmost.
UILocalNotification *notification = [UILocalNotification new];
notification.fireDate = [NSDate date]; //
notification.alertBody = #"This is custom alarm";
notification.soundName = #"YourCustomAudioFile.aiff";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
BUT
Your custom audio files must conforms two conditions:
Audio length less than 30 seconds
Audio file format should be: raw PCM, or MA4, or uLaw, or aLaw (wav, aiff or caf file containers)
So if you have MP3 custom alarm sound, you should convert it...
Here is Apple documentation (lookup for Preparing Custom Alert Sounds section) link

iOS pause between songs to exec code then resume, when app is in background

I am building an app which needs to play a track list, but between each song the music should pause to execute some code, then once complete the music should resume. This needs to work when the app is in the background as well as in the foreground.
I have tried a couple of methods but none seem to be able to do everything I want.
AVQueuePlayer - I can't seem to identify when any one song has stopped, only when the whole queue has stopped.
AVPlayer - I can identify when the track has ended with a notification, then I can run my extra code then load the next track. This works fine as long as the app is not in the background, when the app is in the background the code executes fine except the [avPlayer play] command does not work. It does not throw an error, it simply does not play. I know it has moved to the next song and loaded it into AVPlayer as I output the meta data and it has moved on.
Just to be clear the initial track does run in the background, it is only starting the next track which does not run in the background.
Code below...
Any idea what I am doing wrong?
Thanks!
+(void) playItem {
//get the play item from the song array based on intSongIndex
MPMediaItem *currentSong = [songsNowPlaying objectAtIndex:intSongIndex];
AVPlayerItem * currentItem = [AVPlayerItem playerItemWithURL:[currentSong valueForProperty:MPMediaItemPropertyAssetURL]];
[avPlayer replaceCurrentItemWithPlayerItem:currentItem];
//add notification to the currentItem
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:currentItem];
//play
[avPlayer play];
NSArray *metadataList = [[avPlayer currentItem].asset commonMetadata];
for (AVMetadataItem *metaItem in metadataList) {
NSLog(#"%#: %#",[metaItem commonKey], [metaItem value]);
}
//increment song index so next time the next song is selected
intSongIndex ++;
if (intSongIndex >= songsNowPlaying.count) {
intSongIndex = 0;
}
}
+ (void)playerItemDidReachEnd:(NSNotification *)notification {
//add code to be executed before the next song plays
//call playItem to play the next song
[self playItem];
}
Solved, this needed adding to the initial viewDidLoad
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

MPMoviePlayerController seek forward button stops the video in IOS7?

I am facing an issue with MPMoviePlayerController in iOS 7. When i single tap on the forward seek button the video stops and not allow to do anything like to play again full screen and slider change.
Here is my code.
remove the Observer for the MPMoviePlayerPlaybackDidFinishNotification
[[NSNotificationCenter defaultCenter] removeObserver:moviePlayerViewController name:MPMoviePlayerPlaybackDidFinishNotification object:moviePlayerViewController.moviePlayer];
and add New Notification MPMoviePlayerPlaybackDidFinishNotification
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(videoFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
Here is my custom method to handle the MPMoviePlayerPlaybackDidFinishNotification
-(void)videoFinished:(NSNotification*)aNotification{
MPMoviePlayerController *moviePlayer = [aNotification object];
NSLog(#"%f",moviePlayer.currentPlaybackTime);
int reason = [[[aNotification userInfo] valueForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
if (reason == MPMovieFinishReasonPlaybackEnded) {
}else if (reason == MPMovieFinishReasonUserExited) {
[self performSelector:#selector(dismiss:) withObject:aNotification afterDelay:0.5];
}else if (reason == MPMovieFinishReasonPlaybackError) {
}
}
I need to stop this strange behaviour on single click and continue to play.
Anyone know how to do this?
Thanks.
I think there are no any notifications or event are available on user
interaction with the standard player buttons, and i have to implement
own UI for the player controls. by this way we can then determine the
actions for a single touch, long touch, etc. Then, we can add whatever
functionality like increasing the play rate, or simply seeking to a
time.

Resources