iOS 7 rewind songs on lock screen using AVPlayer

2020-02-10 07:35发布

问题:

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!

回答1:

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;
}


回答2:

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
}


回答3:

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.



回答4:

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!