54

I would like Control Center (via MPNowPlayingInfoCenter) to show the forward 15 seconds / back 15 seconds controls that Apple shows with podcasts, like so:

podcast controls

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.

DesignatedNerd
  • 2,514
  • 1
  • 23
  • 46
  • Control center seems immutable as far as most things go. Not only can app developers not edit this, not even the user can edit the buttons in control center. Apple not sharing once again. – erdekhayser Dec 15 '13 at 04:33
  • If there was a way to do it I'm sure the 10 other indie podcast clients would have implemented it. – seanwolter Dec 16 '13 at 15:25
  • Yeah, I was sort of hoping against hope here. I'll file a radar on it. – DesignatedNerd Dec 16 '13 at 17:04
  • hi help me http://stackoverflow.com/questions/32564494/want-next-and-previous-button-in-my-ytplayerview-while-locked-iphone-ios – Jagveer Singh Sep 14 '15 at 12:16

5 Answers5

125

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

Screenshot of feedback command and skipforward

Gareth
  • 2,051
  • 2
  • 19
  • 14
  • Is a "rewind 42 seconds" icon set automatically in Control Center, then, when you implement `skipBackwardCommand.preferredIntervals = @[@(42)];`? That's what this question is about, the icons/controls. Could you post an image? – pkamb Jul 18 '14 at 08:30
  • Yes it is - screenshot now added – Gareth Jul 18 '14 at 09:37
  • 5
    Note that `MPRemoteCommandCenter` was added in 7.1, which might explain why no one was using it! – pkamb Jul 18 '14 at 15:27
  • @Gareth thanks for the detailed answer!!! Ive been looking for this since Overcast came out and I realized there had to be an API. Someone in the dev forums pointed me to the docs today, but your code sample makes it so much easier to grasp!!!! – vichudson1 Jul 31 '14 at 11:52
  • This is one of those answers that I'd love to be able to tick more than once - thanks @Gareth – amergin Apr 18 '15 at 12:51
  • I couldn't get the interval to work until I set it to a float, like so: `[rcc.skipBackwardCommand setPreferredIntervals:@[@30.0]];` – Jeff May 06 '15 at 20:57
  • hi bro can you help me in my question – Jagveer Singh Sep 14 '15 at 12:16
  • http://stackoverflow.com/questions/32564494/want-next-and-previous-button-in-my-ytplayerview-while-locked-iphone-ios – Jagveer Singh Sep 14 '15 at 12:16
  • Thanks for your complete and detailed answer, it helped me a lot. Still I need one more thank to complete my app. Is it possible to change the Feedback alertView Cancel button title and their shown icons? – Niloufar Oct 06 '15 at 10:04
  • @Gareth I would like to know if there is a way to set a stop button instead of play/pause buttons? Thank you very much! – Maria Camila Alvarez Aug 03 '16 at 20:42
  • Hi I tried this but some white overlay appearing on the player screen and in control center i cant able to new buttons. DO you have any idea what will be the issue? – IamDev Apr 21 '17 at 13:46
12

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

Community
  • 1
  • 1
zombie
  • 5,069
  • 3
  • 25
  • 54
3

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.

MrLore
  • 3,759
  • 2
  • 28
  • 36
DesignatedNerd
  • 2,514
  • 1
  • 23
  • 46
  • This might help figuring out a code example: https://developer.apple.com/library/ios/documentation/MediaPlayer/Reference/MPSkipIntervalCommand_Ref/Reference/Reference.html#//apple_ref/occ/cl/MPSkipIntervalCommand – erdekhayser Jul 17 '14 at 21:28
2

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.

erdekhayser
  • 6,537
  • 2
  • 37
  • 69
  • 3
    Sadly, for 7.x, this is the correct answer. If you're cranky about it like me, I'd encourage you to file a radar asking to have access to this functionality. Might just be shouting down a well, but they do seem to read them occasionally. https://bugreport.apple.com/ – DesignatedNerd Mar 10 '14 at 23:19
  • 1
    @DesignatedNerd Radar filed. – erdekhayser Mar 11 '14 at 22:02
1

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]
grizzb
  • 329
  • 5
  • 8