iOS 7 rewind songs on lock screen using AVPlayer - ios

I havn't found the way how to rewind song from lock screen iOS 7. Volume slider is available, all the buttons work correctly but the upper slider is not available!
AVPlayer is the class I'm using.
Any help is appreciated!

You'll need to set this in the "now-playing info".
Whenever the item (track) changes, do something like
NSMutableDictionary *nowPlayingInfo = [[NSMutableDictionary alloc] init];
[nowPlayingInfo setObject:track.artistName forKey:MPMediaItemPropertyArtist];
[nowPlayingInfo setObject:track.trackTitle forKey:MPMediaItemPropertyTitle];
...
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.player.rate] forKey:MPNowPlayingInfoPropertyPlaybackRate];
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.currentPlaybackTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlayingInfo;
Whenever the playback rate changes (play/pause), do
NSMutableDictionary *nowPlayingInfo = [[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo mutableCopy];
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.player.rate] forKey:MPNowPlayingInfoPropertyPlaybackRate];
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.currentPlaybackTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlayingInfo;
You can get the current playback time like this:
- (NSTimeInterval)currentPlaybackTime {
CMTime time = self.player.currentTime;
if (CMTIME_IS_VALID(time)) {
return time.value / time.timescale;
}
return 0;
}

You have to register for different events like below in swift
MPRemoteCommandCenter.shared().playCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().nextTrackCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().previousTrackCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().stopCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().pauseCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().changePlaybackPositionCommand.isEnabled = true
MPRemoteCommandCenter.shared().changePlaybackPositionCommand.addTarget(self, action: #selector(self.handleChangePlaybackPositionRemoteCommandActions))
let rcc = MPRemoteCommandCenter.shared()
let skipBackwardIntervalCommand: MPSkipIntervalCommand? = rcc.skipBackwardCommand
skipBackwardIntervalCommand?.isEnabled = false
let skipForwardIntervalCommand: MPSkipIntervalCommand? = rcc.skipForwardCommand
skipForwardIntervalCommand?.isEnabled = false
let seekForwardCommand: MPRemoteCommand? = rcc.seekForwardCommand
seekForwardCommand?.isEnabled = true
rcc.changePlaybackPositionCommand.isEnabled = true
rcc.changePlaybackRateCommand.isEnabled = true
rcc.ratingCommand.isEnabled = true
rcc.playCommand.isEnabled = true
rcc.togglePlayPauseCommand.isEnabled = true
here handleChangePlaybackPositionRemoteCommandActions this method will be your method which will have to manage seeking of song when scrubber (upper slider) changes its value
it will look something like below:-
#objc func handleChangePlaybackPositionRemoteCommandActions(event:MPChangePlaybackPositionCommandEvent) -> MPRemoteCommandHandlerStatus
{
print("handleChangePlaybackPositionRemoteCommandActions : \(event.positionTime)")
self.appDelegate.audioPlayer?.seek(to: CMTime(seconds: event.positionTime, preferredTimescale: (self.appDelegate.audioPlayer?.currentItem?.currentTime().timescale)!))
MPNowPlayingInfoCenter.default().nowPlayingInfo![MPNowPlayingInfoPropertyElapsedPlaybackTime] = event.positionTime
return MPRemoteCommandHandlerStatus.success
}

I can not seem to find a way to add the functionality of dragging the progress bar to scrub through the currently playing song either, however getting the progress bar to show is partly covered by Chris above, but you have to pass the song's duration in as well as the other information to get the progress bar to show up. So in addition to what Chris pointed out, you need to add the following to the NSMutableDictionary:
[nowPlayingInfo setObject:[track valueForProperty: MPMediaItemPropertyPlaybackDuration]
forKey:MPMediaItemPropertyPlaybackDuration];
Also, you can handle a long press of the forward and back buttons and scroll your music. In the:
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent
you can look for these event subtypes:
if (receivedEvent.subtype == UIEventSubtypeRemoteControlBeginSeekingBackward){};
if (receivedEvent.subtype == UIEventSubtypeRemoteControlBeginSeekingForward){};
if (receivedEvent.subtype == UIEventSubtypeRemoteControlEndSeekingBackward){};
if (receivedEvent.subtype == UIEventSubtypeRemoteControlEndSeekingForward){};
The BeginSeekingForward and BeginSeekingBackward are triggered by long presses of the forward and back buttons respectively, and of course the EndSeekingForward and EndSeekingBackward event subtypes are when the finger is lifted off the button.
When you get these event subtypes, pass a new NSDictionary to
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo
with all of the information that was already in it (otherwise properties that you do not pass in will be cleared) plus a new MPNowPlayingInfoPropertyElapsedPlaybackTime and MPNowPlayingInfoPropertyPlaybackRate. For the rate, you decide how fast you want to scroll the audio. 1 is normal speed, negative values play backwards, so -2 is 2x normal speed in reverse. You will have to set the playback rate of the MPNowPlayingInfoCenter to be the same as the playback rate of the AVPlayer or they will not line up. This is why you want to also pass in the current time. You can get this with:
[player currentTime]
so to set the current time and rate:
[nowPlayingInfo setObject:[player currentTime]
forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[nowPlayingInfo setObject:[[NSNumber alloc] initWithFloat:10]
forKey:MPNowPlayingInfoPropertyPlaybackRate];
and set the rate of the player with:
[player setRate:10]
This should line up both the player and the progress bar while scrolling with a 10x forward scroll speed. So you would do this when you get the BeginSeekingForward event subtype. When the scrolling ends, set the rate back to one, but still pass in the time and other information to the information center. For BeginSeekingBackward, just use a negative number for the rate.
I know this is an old post, but there was not a good solution and I ran across this thread while searching for how to get music information on to the lock screen. I was trying to implement this functionality in C# with Xamarin iOS and found a nice guide and sample Objective-C app with some of this functionality at http://www.sagorin.org/ios-playing-audio-in-background-audio/. But it did not have the scrolling functionality, nor did it make use of the information center, it just covered handling pause/play from the lock screen. I made a sample app in C# with Xamarin that demonstrates supplying info to the information center and scrolling with the back and forward buttons from the lock and control screens, which you can get here: https://github.com/jgold6/Xamarin-iOS-LockScreenAudio
I hope someone finds this helpful.

Here's how you do this thing with the timeline slider:
First of all the nowPlayingInfo must be set right. You can find how to do that anywhere (don't forget the key MPNowPlayingInfoPropertyPlaybackRate);
Second is to define the action for 'sliding':
MPChangePlaybackPositionCommand *changePlaybackPositionCommand = [[MPRemoteCommandCenter sharedCommandCenter] changePlaybackPositionCommand];
[changePlaybackPositionCommand addTarget:self action:#selector(changePlaybackPositionEvent:)];
[[MPRemoteCommandCenter sharedCommandCenter].changePlaybackPositionCommand setEnabled:YES];
Inside the action you can do some additional things, but the main thing here is
[yourPlayer seekTo:event.positionTime completionHandler:^(BOOL finished) {
[self updatePlaybackRateMetadata];
}];
updatePlaybackRateMetadata method has to update MPNowPlayingInfoPropertyElapsedPlaybackTime and MPNowPlayingInfoPropertyPlaybackRate.
A lot of time passed, but i hope this will help someone!

Related

NSTimer not passing parameters correctly?

I have a UITableViewCell. It has 2 AVPlayers, 2 AVPlayerItems, and 2 AVPlayerLayers. One AVPlayer and its contents are on the right side of the cell, and one on the left side of the cell. I am using the AVAssetResourceLoader to cache and play videos at the same time. This works great, except the second video does not display. It does cache properly, because after scrolling the tableview, it plays correctly as it should from the cache. But just the AVPlayerLayer does not display the video content as it should.
The AVAssetResourceLoader can not handle 2 simultaneous downloads. Upon first loading the tableview, the video on the left starts to download/play. The code for playing the video on the right side is read immediately after the code for downloading/playing the video on the left side--meaning of course, that the download process has not yet finished. To fix this problem, I have created a timer, like so:
if (self.downloadInProgress) {
// download is in progress. wait to start next download
self.downloadQueueCount++;
NSDictionary *videoSettings = #{#"id": activity2.objectId,
#"activity": activity2, #"cell": videoCell, #"left": #NO}
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self selector:#selector(DownloadNextVideo:)
userInfo:videoSettings repeats:YES];
} else {
[self playStreamingVideoOnRightInCell:videoCell withActivity:activity2];
}
And then the selector method:
- (void)DownloadNextVideo:(NSTimer *)timer {
if (self.downloadInProgress == NO) {
NSString *activityId = [[timer userInfo] objectForKey:#"id"];
self.videoChallengeID1 = activityId;
CompletedTableViewCell *videoCell = (CompletedTableViewCell *)[[timer userInfo]objectForKey:#"cell"];
Activity *activity = [[timer userInfo] objectForKey:#"activity"];
BOOL left = [[timer userInfo] objectForKey:#"left"];
[self.timer invalidate];
if (left) {
[self playStreamingVideoOnLeftInCell:videoCell withActivity:activity];
} else {
[self playStreamingVideoOnRightInCell:videoCell withActivity:activity];
}
}
}
Basically, this works correctly--the AVAssetResourceLoader gets the correct URL, correct activityID, and downloads/caches the correct video--except the video does not play as it downloads. I changed background colors, and I can confirm that the AVPlayerLayer is indeed present in the frame it should be. Just no video shows up.
I tried switching left and right videos and download order--and in that case, the right video played, and the left video didn't play. Basically, whichever video has to use this timer and continually check if a download is in progress or not, is the video that does not display correctly, and that makes me think it is something I am doing wrong with the timer concerning scope/passing variables.
One last detail, in case it may help. If I say the following (instead of the first method I had here):
if (self.downloadInProgress) {
[self playStreamingVideoOnRightInCell:videoCell withActivity:activity2];
}
Then, the playerLayer DOES show video--but it's the wrong video. It shows the same video as in the left side of the cell.
This has been a nightmare to debug, and I am fairly certain at this point that it has something to do with using a timer here to play the video instead of doing it inside the regular scope. Anyone have any ideas what I could be doing wrong?
In order to fix the proper video playing, you need to change the following. You are using the NSNumber value instead of a BOOL for the left value.
Your code:
BOOL left = [[timer userInfo] objectForKey:#"left"];
Should be:
BOOL left = [[[timer userInfo] objectForKey:#"left"] boolValue];

Control volume of one channel in objective-c

I have an really simple audio player with AVAudioPlayer with (at this point) one song.
It is simple to control the volume of both channels (left and right) at the same time.
But is it possible to control only the left channel or only the right channel?
In this way I can set the left speaker a little louder then the right or vice versa.
The AVAudioPlayer reference should be consulted to answer this question.
You can see from the interface that you can't control the left/right channel directly, but you can set the pan from -1 (full left) to 1 (full right). Perhaps this will achieve what you are looking for?
Using core audio will be much more difficult, but it should be able to handle this. Another possibility using AVAudioPlayer is to split the sound into its separate channels and to play them separately. Then, you can set the volume separately. There is an example in the documentation of how to ensure that two sounds play at exactly the same time. (And, you'll want to set the pan for each sound separately so that each sound plays correctly.)
A less efficient method is two play two copies of the .mp3 at once. (Caveat: I haven't tested this code.)
// assumes you have url for the file you want to play
AVAudioPlayer *first = [[AVAudioPlayer alloc] initWithContentsOfURL:myUrl error:nil];
AVAudioPlayer *second = [[AVAudioPlayer alloc] initWithContentsOfURL:myUrl error:nil];
first.pan = -1; // only play on left channel
second.pan = 1; // only play on right channel
first.volume = 0.6;
second.volume = 1.0;
NSTimeInterval shortStartDelay = 0.01;
NSTimeInterval now = player.deviceCurrentTime;
[first playAtTime: now + shortStartDelay];
[second playAtTime: now + shortStartDelay];

Is there a public way to force MPNowPlayingInfoCenter to show podcast controls?

I would like Control Center (via MPNowPlayingInfoCenter) to show the forward 15 seconds / back 15 seconds controls that Apple shows with podcasts, like so:
The utter lack of documentation tells me that there's no obvious way to do this, but has anyone out there found any non-obvious way to force this without resorting to a private method?
I've already got my handling for the forward/back button set up to advance appropriately, I'd just like to use the more appropriate UI. Any help would be greatly appreciated.
OK so I had a bit of time on my hands and so I followed the breadcrumb.…
This is what I found.
Include the MediaPlayer framework and get hold of the RemoteCommandCenter:
MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter];
then if you wanted to set the skip controls as per Overcast you can do the following:
MPSkipIntervalCommand *skipBackwardIntervalCommand = [rcc skipBackwardCommand];
[skipBackwardIntervalCommand setEnabled:YES];
[skipBackwardIntervalCommand addTarget:self action:#selector(skipBackwardEvent:)];
skipBackwardIntervalCommand.preferredIntervals = #[#(42)]; // Set your own interval
MPSkipIntervalCommand *skipForwardIntervalCommand = [rcc skipForwardCommand];
skipForwardIntervalCommand.preferredIntervals = #[#(42)]; // Max 99
[skipForwardIntervalCommand setEnabled:YES];
[skipForwardIntervalCommand addTarget:self action:#selector(skipForwardEvent:)];
and in the event handlers do what you need to do to skip by the interval:
-(void)skipBackwardEvent: (MPSkipIntervalCommandEvent *)skipEvent
{
NSLog(#"Skip backward by %f", skipEvent.interval);
}
-(void)skipForwardEvent: (MPSkipIntervalCommandEvent *)skipEvent
{
NSLog(#"Skip forward by %f", skipEvent.interval);
}
Note: The preferredIntervals property is an NSArray but I haven’t figured out how extra intervals can be utilised by the command center unless you do something with this yourself.
Things to note that I’ve found so far. When you do this you are taking control of all the controls so the default play and pause buttons won't show unless you do the same for them:
MPRemoteCommand *pauseCommand = [rcc pauseCommand];
[pauseCommand setEnabled:YES];
[pauseCommand addTarget:self action:#selector(playOrPauseEvent:)];
//
MPRemoteCommand *playCommand = [rcc playCommand];
[playCommand setEnabled:YES];
[playCommand addTarget:self action:#selector(playOrPauseEvent:)];
(there is also a togglePlayPauseCommand defined but I could’t get this to fire from the Command Centre - it does fire from headphones though.)
Other discoveries:
The buttons are in fixed positions left / middle / right so you cant have (for example) a previousTrack and a skipBackward as they both occupy the left position.
There are seekForward / seekBackward commands that need a prevTrack and nextTrack command to be triggered. When you set up both then a single tap triggers next / previous and a press and hold triggers a begin seek and an end seek when you lift your finger.
// Doesn’t show unless prevTrack is enabled
MPRemoteCommand *seekBackwardCommand = [rcc seekBackwardCommand];
[seekBackwardCommand setEnabled:YES];
[seekBackwardCommand addTarget:self action:#selector(seekEvent:)];
// Doesn’t show unless nextTrack is enabled
MPRemoteCommand *seekForwardCommand = [rcc seekForwardCommand];
[seekForwardCommand setEnabled:YES];
[seekForwardCommand addTarget:self action:#selector(seekEvent:)];
-(void) seekEvent: (MPSeekCommandEvent *) seekEvent
{
if (seekEvent.type == MPSeekCommandEventTypeBeginSeeking) {
NSLog(#"Begin Seeking");
}
if (seekEvent.type == MPSeekCommandEventTypeEndSeeking) {
NSLog(#"End Seeking");
}
}
There is also a feedback mechanism that I haven’t seen before (occupies left position)
MPFeedbackCommand *likeCommand = [rcc likeCommand];
[likeCommand setEnabled:YES];
[likeCommand setLocalizedTitle:#"I love it"]; // can leave this out for default
[likeCommand addTarget:self action:#selector(likeEvent:)];
MPFeedbackCommand *dislikeCommand = [rcc dislikeCommand];
[dislikeCommand setEnabled:YES];
[dislikeCommand setActive:YES];
[dislikeCommand setLocalizedTitle:#"I hate it"]; // can leave this out for default
[dislikeCommand addTarget:self action:#selector(dislikeEvent:)];
BOOL userPreviouslyIndicatedThatTheyDislikedThisItemAndIStoredThat = YES;
if (userPreviouslyIndicatedThatTheyDislikedThisItemAndIStoredThat) {
[dislikeCommand setActive:YES];
}
MPFeedbackCommand *bookmarkCommand = [rcc bookmarkCommand];
[bookmarkCommand setEnabled:YES];
[bookmarkCommand addTarget:self action:#selector(bookmarkEvent:)];
// Feedback events also have a "negative" property but Command Center always returns not negative
-(void)dislikeEvent: (MPFeedbackCommandEvent *)feedbackEvent
{
NSLog(#"Mark the item disliked");
}
-(void)likeEvent: (MPFeedbackCommandEvent *)feedbackEvent
{
NSLog(#"Mark the item liked");
}
-(void)bookmarkEvent: (MPFeedbackCommandEvent *)feedbackEvent
{
NSLog(#"Bookmark the item or playback position");
}
This displays three horizontal bars and brings up an alert sheet - you can highlight these individually by setting the active property.
There is also a rating command defined - but I couldn't get this to show in the Command Center
// MPRatingCommand *ratingCommand = [rcc ratingCommand];
// [ratingCommand setEnabled:YES];
// [ratingCommand setMinimumRating:0.0];
// [ratingCommand setMaximumRating:5.0];
// [ratingCommand addTarget:self action:#selector(ratingEvent:)];
and a playback rate change command - but again couldn’t get this to show in Command Center
// MPChangePlaybackRateCommand *playBackRateCommand = [rcc changePlaybackRateCommand];
// [playBackRateCommand setEnabled:YES];
// [playBackRateCommand setSupportedPlaybackRates:#[#(1),#(1.5),#(2)]];
// [playBackRateCommand addTarget:self action:#selector(remoteControlReceivedWithEvent:)];
There is also a block-based target action mechanism if you prefer
// #property (strong, nonatomic) id likeHandler;
self.likeHandler = [likeCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent *event) {
NSLog(#"They like it");
return MPRemoteCommandHandlerStatusSuccess; // or fail or no such content
}];
One final point to be aware of: If you have registered to receive remote events via [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; then some of these commands also trigger events in the - (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent handler. These are UIEvents though with type UIEventTypeRemoteControl and a subtype to distinguish the event. You can't mix and match these with MPRemoteCommandEvents in this method. There are hints that MPRemoteCommandEvents will replace the UIEvents at some point.
All of this based on trial and error so feel free to correct.
Gareth
For Swift developers
import MediaPlayer
let rcc = MPRemoteCommandCenter.shared()
let skipBackwardCommand = rcc.skipBackwardCommand
skipBackwardCommand.isEnabled = true
skipBackwardCommand.addTarget(handler: skipBackward)
skipBackwardCommand.preferredIntervals = [42]
let skipForwardCommand = rcc.skipForwardCommand
skipForwardCommand.isEnabled = true
skipForwardCommand.addTarget(handler: skipForward)
skipForwardCommand.preferredIntervals = [42]
func skipBackward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
let interval = command.preferredIntervals[0]
print(interval) //Output: 42
return .success
}
func skipForward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
let interval = command.preferredIntervals[0]
print(interval) //Output: 42
return .success
}
Other command would be the similar and they can be checked here
Oooooooh. Marco Arment got this to work in Overcast, and at least left a breadcrumb trail for the Castro guys with this tweet:
It’s MPRemoteCommandCenter. Good luck with the documentation, though.
Here's said documentation for anyone who's been following this question - I'm guessing it has to do with skipBackwardCommand and skipForwardCommand. I don't have time to look into it this very second, so I'll leave this here in case anyone wants to poke at it and give a more thorough answer.
Apple has no documentation because there is no way to change this. Again, Apple is keeping the best stuff to itself (Siri comes to mind also).
The jailbroken version supports changing Control Center buttons, which I found on this site. I have a feeling that you want to use this app on the actual iOS 7, not a jailbroken version, so this does not help you at all.
These private API's get in the way of developing good apps way too often. Unless Apple gives us more freedom to use currently-private API's, you are out of luck.
In addition to the other answers, I found if I did not set the nowPlayingInfo on MPNowPlayingInfoCenter then the skip buttons did not appear but the default nextTrack and PreviousTrack buttons appeared. (plain fastforward and rewind appearing buttons)
Be sure you are setting MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo at some point like below:
var songInfo: NSMutableDictionary = [
MPMediaItemPropertyTitle: "song title",
MPMediaItemPropertyArtist: "artist ",
MPNowPlayingInfoPropertyElapsedPlaybackTime: "0"
]
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = songInfo as [NSObject : AnyObject]

UISlider and playing sound in iOS

I'm trying to use a UISlider to play sound in a fashion which emulates how a violin bow would play on a string. At current, I can get sound playing from the slider however the functionality isn't correct.
-(IBAction) slider
{
NSString* path = [[NSBundle mainBundle] pathForResource:#"im_on" ofType:#"mp3" ];
if (song) [song release];
song = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[song play];
}
This is code and there is not much else to it. I have linked this method in IB to a slider which is triggered on the event Value Changed. When set to continuous = YES, when the slider begins to slide, the sound plays. However if it continues to slide the sound will stop until the slider stops where it then play again. When set to continuous = NO, the sound does not play when the value changes but when the touch is lifted. the sound continues to play while the value is hanging after that.
Is there a way to have the sound continue to play until the slider stops/changes direction/touch is released? i.e override all but the first value changed sent event until x condition is met.

Change lock screen background audio controls text?

I have an iOS app that streams background audio using AVAudioSession. It is working correctly, but I am curious, is there any way to change the text on the lock screen audio controls? Right now it simply displays the name of my app, but I would like to change it to the name of the track.
Additionally, the multi-tasking bar has no text under the controls- is there a way to add the track name there, as the iPod app does?
iOS 5 now supports setting the track title as well as an album art image in both the lock screen and the remote playback controls (the controls you get when you double-click the home button and swipe right). Take a look at the
MPNowPlayingInfoCenter class. Of course, to maximize compatibility, you'd want to test whether MPNowPlayingInfoCenter is available, by doing something like:
if ([MPNowPlayingInfoCenter class]) {
/* we're on iOS 5, so set up the now playing center */
UIImage *albumArtImage = [UIImage imageNamed:#"HitchHikersGuide"];
albumArt = [[MPMediaItemArtwork alloc] initWithImage:albumArtImage];
NSDictionary *currentlyPlayingTrackInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:#"Life, the Universe and Everything", [NSNumber numberWithInt:42], albumArt, nil] forKeys:[NSArray arrayWithObjects:MPMediaItemPropertyTitle, MPMediaItemPropertyPlaybackDuration, MPMediaItemPropertyArtwork, nil]];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = currentlyPlayingTrackInfo;
}
here it is in swift! (no need to check for iOS 5 and up anymore)
let albumArt = MPMediaItemArtwork(image: UIImage(named:"HitchHikersGuide"))
let albumDict = [MPMediaItemPropertyTitle: "Life, the Universe and Everything", MPMediaItemPropertyPlaybackDuration: 42, MPMediaItemPropertyArtwork: albumArt]
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = albumDict

Resources