How to change track position on lock screen/control center? - ios

When playing a song with the ios 7 music app the user can use slider to change song position in the lock screen/the control center. Slider is active:
But when playing music in my app user can't do it. Slider isn't active:
How can i enable these feature in my app?

You can change track position with help of MPRemoteCommandCenter on iOS 9.1 and higher.
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_0) {
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.changePlaybackPositionCommand setEnabled:true];
[commandCenter.changePlaybackPositionCommand addTarget:self action:#selector(changedThumbSliderOnLockScreen:)];
}
and method
- (MPRemoteCommandHandlerStatus)changedThumbSliderOnLockScreen:(MPChangePlaybackPositionCommandEvent *)event
{
// change position
[self setCurrentPlaybackTime:event.positionTime];
// update MPNowPlayingInfoPropertyElapsedPlaybackTime
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
return MPRemoteCommandHandlerStatusSuccess;
}

swift4
You can change track position with help of MPRemoteCommandCenter on iOS 9.1 and higher.
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.changePlaybackPositionCommand.isEnabled = true
commandCenter.changePlaybackPositionCommand.addTarget(
self, action:#selector(changePlaybackPositionCommand(_:)))
and method
#objc func changePlaybackPositionCommand(_ event:
MPChangePlaybackPositionCommandEvent) -> MPRemoteCommandHandlerStatus {
let time = event.positionTime
//use time to update your track time
return MPRemoteCommandHandlerStatus.success
}
Note that you have to do that for every command in commandCenter if you want that commend to be enabled.

I was looking for the same thing but I don't think this is possible see this post:
How to enable audio scrubber in iOS Lock Screen control panel?
Also popular apps like Spotify and Soundcloud don't have this implemented.
If you are looking for a way to show the current music on the lock screen you need to do the following.
First when you play a new track update the NowPlayingInfo :
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
[songInfo setObject:trackTitle forKey:MPMediaItemPropertyTitle];
[songInfo setObject:artistName forKey:MPMediaItemPropertyArtist];
[songInfo setObject:duration forKey:MPMediaItemPropertyPlaybackDuration];
[songInfo setObject:releaseDate forKey:MPMediaItemPropertyReleaseDate];
[songInfo setValue:playbackRate forKey:MPNowPlayingInfoPropertyPlaybackRate];
[songInfo setObject:elapsedTime forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[songInfo setObject:albumArtImage forKey:MPMediaItemPropertyArtwork];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
To handle events from the Lockscreen, you first need to tell your app to start receiving events from the remote control. I do this in the application didFinishLaunchingWithOptions of my AppDelegate using the following code
// Turn on remote control event delivery
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
Next you need to implement remoteControlReceivedWithEvent method to handle the captured events. In APPDelegate add the following method
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlPause:
//pause code here
break;
case UIEventSubtypeRemoteControlPlay:
//play code here
break;
case UIEventSubtypeRemoteControlPreviousTrack:
// previous track code here
break;
case UIEventSubtypeRemoteControlNextTrack:
//next track code here
break;
default:
break;
}
}
}
More info on MPNowPlayingInfoCenter from the apple docs -> https://developer.apple.com/library/ios/documentation/mediaplayer/reference/MPNowPlayingInfoCenter_Class

Related

Playinfo changes when I click on Play or Pause - MPNowPlayingInfoCenter

I have tried setting the playinginfo using the below method :
-(void)handleTheMediaPropertyList
{
UIImage *artWork = [UIImage imageNamed:#"sample.png"];
NSDictionary *nowPlaying = #{MPMediaItemPropertyArtist: #"SAMPLE TEST",
MPMediaItemPropertyAlbumTitle: #"SAMPLE TEST",
MPMediaItemPropertyArtwork:[[MPMediaItemArtwork alloc] initWithImage:artWork]
};
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nowPlaying];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
-(void)remoteControlReceivedWithEvent:(UIEvent *)event
{
if (event.type == UIEventTypeRemoteControl) {
switch (event.subtype) {
case UIEventSubtypeRemoteControlPlay:
break;
case UIEventSubtypeRemoteControlPause:
break;
case UIEventSubtypeRemoteControlPreviousTrack:
break;
case UIEventSubtypeRemoteControlNextTrack:
break;
default:
break;
} }
}
After playing the audio, whenever I go to the control center, playing info shows fine there. But when I PAUSE it playing info gets changed to and instead of showing Artist name which I had set above it shows the URL of the link where it is getting streamed from. And later after pausing then playing it again then same data is showed.
So for the first time when the audio is played the Playinfo shows the proper data, but once I open the control center and press PAUSE and PLAY then the playing info gets changed.
Please help me with this.

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: Siri not available does not return AVAudioSessionInterruptionOptionShouldResume

I have iOS app that handles audiosession interrupts with:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(AudioInterrupt:)
name:AVAudioSessionInterruptionNotification
object: NULL];
and in AudioInterrupt:
- (void)AudioInterrupt:(NSNotification*)notification
{
NSDictionary *interuptionDict = notification.userInfo;
// get the AVAudioSessionInterruptionTypeKey enum from the dictionary
NSInteger interuptionType = [[interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
NSNumber* seccondReason = [[notification userInfo] objectForKey:#"AVAudioSessionInterruptionOptionKey"] ;
// decide what to do based on interruption type here...
switch (interuptionType) {
case AVAudioSessionInterruptionTypeBegan:
[[[self pureAudioHandler] audioController] setActive: NO];
NSLog(#"Interruption started");
break;
case AVAudioSessionInterruptionTypeEnded:
NSLog(#"Interruption ended");
break;
}
switch ([seccondReason integerValue]) {
case AVAudioSessionInterruptionOptionShouldResume:
NSLog(#"Resume Audio");
[[[self pureAudioHandler] audioController] configurePlaybackWithSampleRate:44100 numberChannels:2 inputEnabled:NO mixingEnabled:YES];
[[[self pureAudioHandler] audioController] setActive: YES];
break;
default:
break;
}
}
This works fine with alarms and Siri. However if i have no internet connection and i press home button i get "Siri not available...". AVAudioSessionInterruptionTypeBegan is triggered. Press homebutton twice to get back into app and nor AVAudioSessionInterruptionTypeEnded or AVAudioSessionInterruptionOptionShouldResume is fired. Any workarounds?
iPad mini retina with 7.0.3
Lot of experimenting showed that interrupts aren't fired all times as they should. Sometimes missing InterruptionTypeBegan when entering Siri, and all this happens quite randomly on current testcase (Unity3D & Kalimba(libpd)).
Instead used applicationWillResignActive for killing audio and applicationDidBecomeActive to start it again, as these are working 100% of the time.
Interesting fact is that when entering back from Siri (no wifi, so "Siri not available..." appears), it changes samplerate back to half of machines native samplerate (24000 on ipad mini) after some time.

Detect currently playing song information in native iOS Music App

I'm having a some issues to get the current playing song information like (title, artist...) in the native iOS Music App.
This is the exact problem, my app is using a MPMusicPlayerController with iPodMusicPlayer this property is call myPlayer. When the user is controlling the music within my app I'm able to display the current song playing information in this way...
- (void)getTrackDescription {
// getting whats currently playing
self.nowPlayingItem = [myPlayer nowPlayingItem];
// song title currently playing
self.title = [self.nowPlayingItem valueForProperty:MPMediaItemPropertyTitle];
// if title is not fund Unknown will be displayed
if (title == (id)[NSNull null] || title.length == 0) {
title = #"Unknown";
}
// artist currently playing
self.artist = [self.nowPlayingItem valueForProperty:MPMediaItemPropertyArtist];
// if artist is not fund Unknown will be displayed
if (artist == (id)[NSNull null] || artist.length == 0) {
artist = #"Unknown";
}
//[self.currentlyPlaying setLineBreakMode:NSLineBreakByWordWrapping];
// displaying current artist and title song playing
[self.currentlyPlaying setText:[NSString stringWithFormat:#"%# - %#", artist, title]];
}
But, the problem comes when the user leave the app in the background or just use the Control Center to change the song.. My app still displays the previous song information and nothing gets update if the user continues using Control Center or the Music app itself.
I tried to solve the problem in this way...
- (void)viewDidLoad {
[super viewDidLoad];
if (self.myPlayer.playbackState == MPMusicPlaybackStatePlaying || self.myPlayer.playbackState == MPMusicPlaybackStateSeekingForward || self.myPlayer.playbackState == MPMusicPlaybackStateSeekingBackward){
// updating track name regardless the user uses app controllers or not
[self getTrackDescription];
}
}
I even tried commenting the if condition to see if I get the song information in that way but it was the same problem.
Well, I hope someone can help me out and explain what I'm doing wrong..
Thanks in advance...!
UPDATE
This what I have at the moment... My getTrackDescription logic is the same but the signature changed to this - (void)getTrackDescription:(id)notification
- (void)viewWillAppear:(BOOL)animated{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:#selector(getTrackDescription:)
name:MPMusicPlayerControllerPlaybackStateDidChangeNotification
object:self.myPlayer];
[self.myPlayer beginGeneratingPlaybackNotifications];
}
-(void)viewWillDisappear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMusicPlayerControllerPlaybackStateDidChangeNotification
object:self.myPlayer];
[self.myPlayer endGeneratingPlaybackNotifications];
}
What you're going to want to do is add your view controller as an observer to the MPMusicPlayerControllerNowPlayingItemDidChangeNotification notification, which as it sounds, gets called every time the track changes in the music player. Don't forget to specify when the player should begin/end generating these notifications with the following methods.
[[MPMusicPlayerController iPodMusicPlayer] beginGeneratingPlaybackNotifications];
[[MPMusicPlayerController iPodMusicPlayer] endGeneratingPlaybackNotifications];
This is my complete answer to the problem..
1) I set these two methods.. and set an Observer MPMusicPlayerControllerNowPlayingItemDidChangeNotification which will take care of updating my song information regardless.. with getTrackDescription which still the same as my previous post..
- (void)viewWillAppear:(BOOL)animated{
// creating simple audio player
self.myPlayer = [MPMusicPlayerController iPodMusicPlayer];
// assing a playback queue containing all media items on the device
[self.myPlayer setQueueWithQuery:[MPMediaQuery songsQuery]];
self.notificationCenter = [NSNotificationCenter defaultCenter];
[self.notificationCenter addObserver:self
selector:#selector(getTrackDescription:)
name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification
object:self.myPlayer];
[self.notificationCenter addObserver:self
selector:#selector(handle_PlayBackNotification:)
name:MPMusicPlayerControllerPlaybackStateDidChangeNotification
object:self.myPlayer];
[self.myPlayer beginGeneratingPlaybackNotifications];
}
-(void)viewWillDisappear:(BOOL)animated {
[self.notificationCenter removeObserver:self
name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification
object:self.myPlayer];
[self.notificationCenter removeObserver:self
name:MPMusicPlayerControllerPlaybackStateDidChangeNotification
object:self.myPlayer];
[self.myPlayer endGeneratingPlaybackNotifications];
}
Then the magic will happen by itself.. it just basically what #0x7fffffff recommended me to do but I had to do some digging because I wasn't familiar with notifications at all..
So, just in case if you want to see how I toggle my play/pause button for the Observer MPMusicPlayerControllerPlaybackStateDidChangeNotification this is how I did it..
- (void)handle_PlayBackNotification:(id)notification{
if(myPlayer.playbackState == 1){
// sets the pause image in play button
[self.pauseBtn setBackgroundImage:[UIImage imageNamed:#"pauseBtn2.png"] forState:(UIControlStateNormal)];
} else {
// resets the image to normal play image
[self.pauseBtn setBackgroundImage: nil forState:(UIControlStateNormal)];
}
}
just in case this my getTrackDescription ...
- (void)getTrackDescription:(id)notification {
// getting whats currently playing
self.nowPlayingItem = self.myPlayer.nowPlayingItem;
// song title currently playing
self.title = [self.nowPlayingItem valueForProperty:MPMediaItemPropertyTitle];
// if title is not fund Unknown will be displayed
if (title == (id)[NSNull null] || title.length == 0) {
title = #"Unknown";
}
// artist currently playing
self.artist = [self.nowPlayingItem valueForProperty:MPMediaItemPropertyArtist];
// if artist is not fund Unknown will be displayed
if (artist == (id)[NSNull null] || artist.length == 0) {
artist = #"Unknown";
}
// displaying current artist and title song playing
[self.currentlyPlaying setText:[NSString stringWithFormat:#"%# - %#", artist, title]];
}

Resources