How to update MPMusicPlayerController when song changes - ios

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

Related

Refresh application state running on background

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.

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

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

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

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

How to mute/unmute audio when playing video using MPMoviePlayerController?

I've created my own custom controls for use with the MPMoviePlayerController. So far everything works except the mute button control.
I've configured the AVAudioSession using the following code before I create my instance of the MPMoviePlayerController.
NSError *modeError = nil;
[self.audioSession setMode:AVAudioSessionModeMoviePlayback error:&modeError];
if (modeError != nil) {
NSLog(#"Error setting mode for AVAudioSession: %#", modeError);
}
NSError *categoryError = nil;
[self.audioSession setCategory:AVAudioSessionCategoryPlayback error:&categoryError];
if (categoryError != nil) {
NSLog(#"Error setting category for AVAudioSession: %#", categoryError);
}
Then in my mute button callback method I have the following code:
NSError *activeError = nil;
[self.audioSession setActive:NO error:&activeError];
if (activeError != nil) {
NSLog(#"Error setting inactive state for AVAudioSession: %#", activeError);
}
When clicking the Mute button I get the following unuseful error:
Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
I am linking to the AVFoundation framework.
This is really starting to bug me as I can't for the life of me work out a way to reduce or mute the playback audio of my application.
I don't want to change the system global volume just the application level volume as defined by the AVAudioSession AVAudioSessionCategoryPlayback category.
It seems that you can set the volume of the AVAudioPlayer but not the MPMoviePlayerController. I've seen other posts here on SO that say just create an instance of AVAudioPlayer and set the volume but this just causes my app to crash and I expect it has something to do with the fact I'm not using the initWithContentsOfURL:error: or initWithData:error: and instead using `init'.
Any help would be appreciated.
After speaking to an Apple technician it turns out that it's not possible to control or mute the audio using MPMoviePlayerController.
Instead you have to create your own controller using AVFoundations AVPlayer class.
Once you're using that it's a matter of creating a custom audio mix and setting the volume level. It actually works very well.
Sample code:
AVURLAsset * asset = [AVURLAsset URLAssetWithURL:[self localMovieURL] options:nil];
NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
// Mute all the audio tracks
NSMutableArray * allAudioParams = [NSMutableArray array];
for (AVAssetTrack *track in audioTracks) {
AVMutableAudioMixInputParameters *audioInputParams =[AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:0.0 atTime:kCMTimeZero ];
[audioInputParams setTrackID:[track trackID]];
[allAudioParams addObject:audioInputParams];
}
AVMutableAudioMix * audioZeroMix = [AVMutableAudioMix audioMix];
[audioZeroMix setInputParameters:allAudioParams];
// Create a player item
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem setAudioMix:audioZeroMix]; // Mute the player item
// Create a new Player, and set the player to use the player item
// with the muted audio mix
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
self.mPlayer = player;
[mPlayer play];
I've written an MPMoviePlayerController replacement class that adds support for volume level. I will upload the to github shortly and add the link in this post.
I know this is an old post, but I managed to find a way to successfully control the volume of the MPMoviePlayerController control in iOS6 & iOS7, using an MPVolumeView. One gotcha is that it does NOT work in the simulator, only on the physical device. For just controlling the volume, adding a hidden MPVolumeView will work fine. However if you use a hidden one, the native OS volume display that appears when you change the volume using the physical device volume buttons will still appear centre screen. If you want to prevent this, make sure your MPVolumeView is not hidden. Instead, you can give it a very low alpha transparency and place it behind other views, so the user can't see it.
Here's the code i've used:
MPVolumeView *volumeView = [[MPVolumeView alloc]initWithFrame:CGRectZero];
[volumeView setShowsVolumeSlider:YES];
[volumeView setShowsRouteButton:NO];
// control must be VISIBLE if you want to prevent default OS volume display
// from appearing when you change the volume level
[volumeView setHidden:NO];
volumeView.alpha = 0.1f;
volumeView.userInteractionEnabled = NO;
// to hide from view just insert behind all other views
[self.view insertSubview:volumeView atIndex:0];
This allows you to control the volume by calling:
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0];
But I was still getting the native OS volume display appearing the first time I would try to change the volume - on subsequent loads it did not show this display, so figuring it was something to do with the stage in the viewcontroller life cycle, I moved it from my viewDidLoad method to the viewDidAppear method - it worked - the volume muted and the native volume display did not appear, but I now was able to hear a split second of audio before the video started playing. So I hooked into the playback state did change delegate of the MPMoviePlayerController. In viewDidload I added:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(videoPlaybackStateDidChange:)
name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
And the delegate callback method:
-(void)videoPlaybackStateDidChange:(NSNotification *)aNotification
{
// Note, this doesn't work in simulator (even in iOS7), only on actual device!
if ([moviePlayerController playbackState] == MPMoviePlaybackStatePlaying)
{
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0];
}
}
This muted the audio of the video before it started playing, but after the viewDidLoad in the life cycle, so no native OS volume muted display.
In my app, I retrieved and stored the current volume level before muting (using [MPMusicPlayerController applicationMusicPlayer].volume property), and then restored the volume to this level when the view controller was closed, meaning the user would be unaware that their device volume level was modified and reverted.
Also, if your MPMoviePlayerController is using a non-standard audio route in iOS7, calling [[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0] may not work for you - in this case you can loop through the subviews of your MPVolumeView control until you find a view which subclasses UISlider. You can then call [sliderView setValue:0 animated:NO] which should work for you. This method isn't using any private Apple APIs so shouldn't get your app rejected - after all there are so many legitimate reasons why you would offer this functionality, and it was possible in iOS6 without having to go to these lengths! In fact, I was bamboozled to discover that Apple had removed the functionality to set the volume on MPMoviePlayerController in iOS7 in the first place.. enforced migration to AVPlayer?
Update: My iPad app has now been approved using this method and is live on the app store.

Resources