I'm integrating MPNowPlayingInfoCenter into a music playing app. The integration of that and MPRemoteCommandCenter work great when I run it in the iOS simulator. When I run the same code on a device the music controls in Control Center do not change at all. It's as if the app isn't event registering with MPNow and MPRemote centers.
MPNowPlayingInfoCenter *np = [MPNowPlayingInfoCenter defaultCenter];
np.nowPlayingInfo = #{MPMediaItemPropertyTitle:[tracks objectAtIndex:self.currentTrack],
MPMediaItemPropertyArtist:currentArtist,
MPMediaItemPropertyAlbumTitle:currentAlbum,
MPMediaItemPropertyMediaType:#(MPMediaTypeMusic),
MPMediaItemPropertyPlaybackDuration:#(self.audioHandler.audioDuration),
MPNowPlayingInfoPropertyElapsedPlaybackTime:#(self.audioHandler.position),
MPNowPlayingInfoPropertyPlaybackRate:rate
};
I've got all this good stuff in my view controller life cycle methods:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
// Turn off remote control event delivery
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
// Resign as first responder
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
A couple notes:
I'm not using AVPlayer. I'm using CoreAudio/Audio units to play
the audio. This part of the app is working fine.
I'm testing on iOS 7 and iOS 8 only.
I'm using the Amazing Audio Engine as a CoreAudio wrapper: http://theamazingaudioengine.com
Found it. When initializing the amazing audio engine (a great framework by the way) one needs to tell the framework that it doesn't want to share the audio output with other apps if one wants to use the MPNowPlayingInfoCenter. To do this I did:
self.audioController = [[AEAudioController alloc]
initWithAudioDescription:[AEAudioController nonInterleaved16BitStereoAudioDescription]
inputEnabled:NO];
self.audioController.allowMixingWithOtherApps = NO;
Related
I'm using MPMoviePlayerController and I need to detect pressing Next/Prev buttons. I tried several things, none of which seem to works.
Here is what I tried:
remote control events
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
-(void) viewWillDisappear:(BOOL)animated
{
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
-(void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent
{
// stuff
}
The problem is remoteControlReceivedWithEvent method is never called. I've read that this will not work in iOS version higher than 6 - I'm working on iOS 7
notifications
I tried using MPMoviePlayerPlaybackStateDidChangeNotification and check against MPMoviePlaybackStateSeekingForward or MPMoviePlaybackStateSeekingBackward - unfortunatelly, these playback state are set when dragging the playback bar, not when pressing Next/Prev buttons.
Any ideas?
Sorry I don´t understand your problem very well, but if you want use the controls out your App in the Control Center, you can use:
// You need cath the singleton
MPRemoteCommandCenter *myRemote = [MPRemoteCommandCenter sharedCommandCenter];
//And add the selector you can fire depends on the button, a couple of examples:
[myRemote.playCommand addTarget:self action:#selector(myPlayMethods)];
[myRemote.nextTrackCommand addTarget:self action:#selector(myNextTrackMethods)];
try registering for event in :
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Turn on remote control event delivery
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
// Set itself as the first responder
[self becomeFirstResponder];
}
Also Don't set kAudioSessionProperty_OverrideCategoryMixWithOthers property
Have you tried MPMoviePlayerNowPlayingMovieDidChangeNotification?
If this does not work then i would suggest moving to a lower level API i.e. AVPlayer. It provides fine grained control over all the actions while video playing and otherwise.
You need to register to handle a notification for moviePlayerLoadStateChanged. When you press the next/prev buttons moviePlayerLoadStateChanged will be called and the loadState will be MPMovieLoadStateUnknown
-(void)registerMyStuff {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerLoadStateChanged:)
name:MPMoviePlayerLoadStateDidChangeNotification
object:self.mpc];
}
- (void)moviePlayerLoadStateChanged:(NSNotification *)notification
{
MPMoviePlayerController *moviePlayer = notification.object;
MPMovieLoadState loadState = moviePlayer.loadState;
if(loadState == MPMovieLoadStateUnknown)
{
// this is where the next/prev buttons notify
// there is no video in this state so load one
// just reload the default movie
NSLog(#"MPMovieLoadStateUnknown");
self.mpc.contentURL = self.fileURL;
[self.mpc prepareToPlay];
return;
}
else if(loadState & MPMovieLoadStatePlayable)
{
NSLog(#"MPMovieLoadStatePlayable");
}
else if(loadState & MPMovieLoadStatePlaythroughOK)
{
NSLog(#"MPMovieLoadStatePlaythroughOK");
} else if(loadState & MPMovieLoadStateStalled)
{
NSLog(#"MPMovieLoadStateStalled");
}
}
You change MPMoviePlayerController overlayView just like change UIImagePickerController overlayView to implement the function you need.
MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc]
initWithContentURL:someUrl];
moviePlayer.movieControlMode = MPMovieControlModeHidden;
[moviePlayer play];
NSArray *windows = [[UIApplication sharedApplication] windows];
if ([windows count] > 1) {
UIWindow *moviePlayerWindow = [[UIApplication sharedApplication] keyWindow];
[moviePlayerWindow addSubview:yourCustomOverlayView];
}
In my app, I have multiple tabs and on each tab I have an instance of AVPlayer. When I activate AirPlay, however, "the first player wins". That means that the player on the currently active tab connects to AirPlay and when I switch to a different tab and press play, nothing happens. So only the first instance of AVPlayer that connects to AirPlay can actually play through AirPlay and no players on the other tabs work. What to do?
The solution is quite easy: When your view controller that contains a player appears, you set allowsExternalPlayback on the AVPlayer instance to YES, when in disappears you set it to NO.
Example:
- (void)viewWillAppear:(BOOL)animated
{
// _player is an instance of AVPlayer
if ([_player respondsToSelector:#selector(setAllowsExternalPlayback:)]) {
// iOS 6+
_player.allowsExternalPlayback = YES;
} else {
// iOS 5
_player.allowsAirPlayVideo = YES;
}
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
// _player is an instance of AVPlayer
if ([_player respondsToSelector:#selector(setAllowsExternalPlayback:)]) {
// iOS 6+
_player.allowsExternalPlayback = NO;
} else {
// iOS 5
_player.allowsAirPlayVideo = NO;
}
[super viewWillDisappear:animated];
}
Enjoy.
I'm writing an app that streams music and I'm having a ton of trouble setting the icon on the music control dock screen (when you double-click the home button and swipe to the left). All the documentation said to do something like this, but the icon never appears and I don't ever receive the event notification. All of this code is in my player view controller. _radioPlayer is an instance of an AVQueuePlayer. What am I doing wrong here?
- (void)viewWillAppear:(BOOL)animated
{
UIApplication *application = [UIApplication sharedApplication];
if([application respondsToSelector:#selector(beginReceivingRemoteControlEvents)])
[application beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
[_radioPlayer pause];
break;
case UIEventSubtypeRemoteControlPlay:
[_radioPlayer play];
break;
case UIEventSubtypeRemoteControlPause:
[_radioPlayer pause];
break;
default:
break;
}
}
EDIT: I read through the documentation and followed the steps but it's still not working. Here are some of the possible reasons I've come up with:
A. My music isn't playing yet when viewDidAppear is called. In the documentation it states
"Your app must be the “Now Playing” app. Restated, even if your app is the first responder >and you have turned on event delivery, your app does not receive remote control events until >it begins playing audio."
I tried calling beginReceivingRemoteControlEvents: and becomeFirstResponder: after the music starts playing but this doesn't work either. Can these only be called in viewDidAppear:? Does iOS automatically detect when the music begins playing so this isn't necessary?
B. There is something weird about using an AVQueuePlayer. I'm almost positive this isn't it since the event message is handled by the view controller, not the player itself.
It may not working for you because you are calling beginReceivingRemoteControlEvents in viewWillAppear instead of viewDidAppear. Check out the documentaion -
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Turn on remote control event delivery
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
I am developing an application for iOS which works in two different modes, one of them plays music from the iPod library, and the other one synthesizes sounds (eg: pure tones).
In order to synthesize sounds I use an audio unit, so through its callback I can pass the desired signal. As far as sounds must be played at an specific volume, I set the device volume using iPodMusicPlayer.
The issue I see is that the first time the application is run on an iPod Touch 5g after turning it on, once the iPodMusicPlayer is used to set the volume of the device, applicationMusicPlayer will not respond to stop.
I came up with the following code. Runing it on an iPod Touch 5g after turning it on, music will not stop after touching stop.
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MPMusicPlayerController *mp = [MPMusicPlayerController iPodMusicPlayer];
mp.volume = 0.3;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)playAction:(id)sender {
MPMusicPlayerController *mp = [MPMusicPlayerController applicationMusicPlayer];
MPMediaQuery *query = [MPMediaQuery songsQuery];
[mp setQueueWithQuery:query];
[mp play];
}
- (IBAction)stopAction:(id)sender {
MPMusicPlayerController *mp = [MPMusicPlayerController applicationMusicPlayer];
[mp stop];
}
#end
PS: It is also possible to reproduce the issue by entering the music app, the quitting it (double press on home button and quit music from the multitasking bar), and finally run the code above.
In the other devices I have tested so far (iPad 2nd and 3rd generation, iPod Touch 4 generation) the application works well.
Thanks in advance.
In recent iOS versions apps have some kind of access to the media control buttons on the lock screen, like the Play/Pause button:
It looks like the buttons are supposed to work with the MPMusicPlayerController class, is that right? Is there a way to get the “raw” events from the buttons? Because the music player only seems to offer an API to supply a bunch of MPMediaItems. What if my app is for example a radio that needs to handle the buttons differently?
After a bit more searching I have found this related question that makes things clear. The music player controller class is not really the right track, the trick is to subscribe for remote events in your controller:
- (void) viewDidAppear: (BOOL) animated
{
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (BOOL) canBecomeFirstResponder
{
return YES;
}
- (void) remoteControlReceivedWithEvent: (UIEvent*) event
{
// see [event subtype] for details
}