31

I'm currently attempting to set up background audio for an app I'm developing for iOS 4. The app doesn't have a dedicated music player viewController, however, unlike other background audio apps such as Pandora, which makes the task a bit more confusing.

I've set the appropriate Info.plist settings correctly and have an AVAudioPlayer object in my app delegate which is accessible from everywhere. When the user plays a song, I replace the AVAudioPlayer with a new one initialized with the song and play it. This all works great, except now I have no idea how to go about supporting remote control events.

Based on Apple's documentation, I have this:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self becomeFirstResponder];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    switch(event.subtype) {
        case UIEventSubtypeRemoteControlTogglePlayPause:
            if([iPhoneAppDelegate backgroundAudioPlayer].playing)
                [iPhoneAppDelegate pauseBackgroundAudioPlayer];
            else
                [iPhoneAppDelegate playBackgroundAudioPlayer];
            break;
    }
}

The thing is, where do I put this? Apple's documentation seems to suggest this should go in some view controller somewhere, but my app has lots of view controllers and navigation controllers. Wherever I try to put this, for some reason tapping the Toggle Play/Pause button in the multitasking tray remote controls either causes the song to just pause for a moment and then unpause, or somehow causes the song to play twice.

pablasso
  • 2,479
  • 2
  • 26
  • 32
Anshu Chimala
  • 2,800
  • 2
  • 23
  • 22

7 Answers7

37

The documentation examples are a bit misleading, but there is no need to subclass anything anywhere. The correct place to put remoteControlReceivedWithEvent: is in the application delegate, as it remains in the responder chain regardless of whether the app is in the foreground or not. Also the begin/end receiving remote control events should be based on whether you actually need the events, not on the visibility of some random view.

  • 11
    Completely correct. To get complete control of remote controls for your app (whether it's in foreground or background) you need to call beginReceivingRemoteControlEvents in application:didFinishLaunchingWithOptions:, but that's not all. Your UIApplicationDelegate starts to receive remote control events after your app tells iOS that it's playing a track - by setting MPNowPlayingInfoCenter nowPlayingInfo property. – HiveHicks Mar 30 '12 at 16:32
  • It is really great answer, I was stuck off from a week. – Khalid Usman Jul 10 '12 at 06:57
  • Putting remoteControlReceivedWithEvent: into the app delegate just doesn't work for me; I had to go with subclassing UIWindow. – JosephH Jul 19 '12 at 15:46
  • Tip: remoteControlReceivedWithEvent wasn't working until I removed it's duplicate from UIViewController. – Shmidt Aug 17 '12 at 14:23
  • 1
    Putting remoteControlReceivedWithEvent: into the app delegate just didn't work for me either, until I realized that my app delegate (created from an xcode template back in the iOS2 days) is a subclass of NSObject. Once I switched my app delegate to a subclass of UIResponder -remoteControlReceivedWithEvent: started getting called. – hyperspasm Sep 06 '12 at 21:58
  • Update: Putting remoteControlReceivedWithEvent: into the app delegate only works for me on iOS5, in order to get things to work in iOS4 I had to subclass UIWindow. – hyperspasm Sep 08 '12 at 11:26
  • Just to enrich the answer. In the case of my tabbed bar application, on iOS6 putting the event control in the tabbar controller was working properly. On iOS7, instead, I had to move it in the applicationDelegate because I guess something changed in the way the OS manage that stuff. Now it works fine. – super Sep 29 '13 at 07:40
  • Thanks. Put remoteControlReceivedWithEvent: into the app delegate only works for me in iOS7/iOS8. – ziggear Nov 24 '14 at 13:27
19

I found a couple of solutions to receiving global remote control events on the Apple Developer Forums after a bit of searching.

One way is to subclass UIWindow and override its remoteControlReceivedWithEvent:.

The second, perhaps nicer way is to subclass UIApplication and override sendEvent:. That way, you can intercept all the remote control events and handle them there globally, and not have any other responders handle them later in the responder chain.

- (void)sendEvent:(UIEvent *)event {
     if (event.type == UIEventTypeRemoteControl) {
          // Handle event
     }
     else
          [super sendEvent:event];
}
Anshu Chimala
  • 2,800
  • 2
  • 23
  • 22
4

The second method didn't work for me, sendEvent was never called. However the first method worked just nicely (subclassing UIWindow).

Stelian Iancu
  • 2,462
  • 3
  • 18
  • 28
  • Works like a charm. I want to add that beginReceivingRemoteControlEvents can be called inside of app delegate to subscribe for remote control event. – pronebird May 19 '11 at 10:57
3

I struggled with this one for a while and none of the answers above worked. The bug in my code, and I hope that it will help someone reading this, was that I had the AudioSession set to mix with others. You want to be the foreground audio player to get Remote Control events. Check to see if you have INCORRECT code like this:

    [[AVAudioSession sharedInstance] setDelegate: self];
    [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil];
    UInt32 doSetProperty = 0;
    AudioSessionSetProperty (
                             kAudioSessionProperty_OverrideCategoryMixWithOthers,
                             sizeof (doSetProperty),
                             &doSetProperty
                             );
    NSError *activationError = nil;
    [[AVAudioSession sharedInstance] setActive: YES error: &activationError];       

And remove the AudioSessionSetProperty, or change doSetProperty to 1.

Sherwin Zadeh
  • 1,339
  • 15
  • 17
0

One thing that seems to influence this behavior is any category options you set for your AVAudioSession using setCategory:withOptions:error: instead of just setCategory:error:. In particular, from trial and error, it appears that if you set AVAudioSessionCategoryOptionMixWithOthers you will not get remote control events; the now playing controls will still control the iPod app. If you set AVAudioSessionCategoryOptionDuckOthers you will get remote control events, but it seems like there may be some ambiguity regarding which app is controlled. Setting the categoryOptions to 0 or just calling setCategory:error: works best.

Jimmy Dee
  • 1,070
  • 1
  • 11
  • 17
0

No need to subclass Window or forward events. Simply handle it from your main view controller. See the Audio Mixer (MixerHost) example for details.

http://developer.apple.com/LIBRARY/IOS/#samplecode/MixerHost/Listings/Classes_MixerHostViewController_m.html

slf
  • 22,595
  • 11
  • 77
  • 101
  • 2
    The thing is, your main view controller is not always going to be on the screen while the app goes to background, i.e. it won't become a first responder if a modal is on the screen. – pablasso Nov 30 '11 at 18:49
  • @pablasso it might be, depending on the app. But not in all cases, I agree with that. – slf Dec 01 '11 at 14:12