Receive remote control events without audio - objective-c

Here is some background information, otherwise skip ahead to the question in bold. I am building an app and I would like it to have access to the remote control/lock screen events. The tricky part is that this app does not play audio itself, it controls the audio of another device nearby. The communication between devices is not a problem when the app is in the foreground. As I just found out, an app does not assume control of the remote controls until it has played audio with a playback audio session, and was the last do so. This presents a problem because like I said, the app controls ANOTHER device's audio and has no need to play its own.
My first inclination is to have the app play a silent clip every time it is opened in order to assume control of the remote controls. The fact that I have to do this makes me wonder if I am even going to be allowed to do it by Apple or if there is another way to achieve this without fooling the system with fake audio clips.
QUESTION(S): Will Apple approve an app that plays a silent audio clip in order to assume control of the remote/lock screen controls for the purpose of controlling another device's audio? Is there any way of assuming control of the remote controls without an audio session?
P.S. I would prefer to have this functionality on iOS 4.0 and up.
P.P.S I have seen this similar question and it has gotten me brainstorming but the answer provided is not specific to what I need to know.

NOTE: As of iOS 7.1, you should be using MPRemoteCommandCenter instead of the answer below.
You create various system-provided subclasses of MPRemoteCommand and assign them to properties of the [MPRemoteCommandCenter sharedCommandCenter].
I'm keeping the rest of this around for historical reference, but the following is not guaranteed to work on recent iOS versions. In fact, it just might not.
You definitely do need an audio player but not necessarily an explicit session to take control of the remote control events. (AVAudioSession is implicit to any app that plays audio.) I spent a decent amount of time playing with this to confirm this.
I've seen a lot of confusion on the internet about where to set up the removeControlEventRecievedWithEvent: method and various approaches to the responder chain. I know this method works on iOS 6 and iOS 7. Other methods have not. Don't waste your time handling remote control events in the app delegate (where they used to work) or in a view controller which may go away during the lifecycle of your app.
I made a demo project to show how to do this.
Here's a quick rundown of what has to happen:
You need to create a subclass of UIApplication. When the documentation says UIResponder, it means UIApplication, since your application class is a subclass of UIResponder. In this subclass, you're going to implement the remoteControlReceivedWithEvent: and canBecomeFirstResponder methods. You want to return YES from canBecomeFirstResponder. In the remote control method, you'll probably want to notify your audio player that something's changed.
You need to tell iOS to use your custom class to run the app, instead of the default UIApplication. To do so, open main.m and change this:
return UIApplicationMain(argc, argv, nil, NSStringFromClass([RCAppDel`egate class]));
to look like this:
return UIApplicationMain(argc, argv, NSStringFromClass([RCApplication class]), NSStringFromClass([RCAppDelegate class]));
In my case RCApplication is the name of my custom class. Use the name of your subclass instead. Don't forget to #import the appropriate header.
OPTIONAL: You should configure an audio session. It's not required, but if you don't, audio won't play if the phone is muted. I do this in the demo app's delegate, but do so where appropriate.
Play something. Until you do, the remote controls will ignore your app. I just took an AVPlayer and gave it the URL of a streaming site that I expect to be up. If you find that it fails, put your own URL in there and play with it to your heart's content.
This example has a little bit more code in there to log out remote events, but it's not all that complicated. I just define and pass around some string constants.
I bet that a silent looping MP3 file would help work towards your goal.

Moshe's solution worked great for me! However one issue I noticed is when you paused the audio, the media controls would go away and you won't be able to play it again without going back into the app. If you set the Media Info on the lock screen when you play the audio then this won't happen:
NSDictionary *mediaInfo = #{MPMediaItemPropertyTitle: #"My Title",
MPMediaItemPropertyAlbumTitle: #"My Album Name",
MPMediaItemPropertyPlaybackDuration: [NSNumber numberWithFloat:0.30f]};
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:mediaInfo];

Related

Best Practices When Using CoreBluetooth Framework

Lately I have been playing around with the bluetooth framework and grew a strong enough knowledge to start building an application. The only problem is that all the examples I found and all the practice I have made consist in putting the core bluetooth core code inside the same file as the UIView with which the user is interacting.
I would like my future application to have multiple views in which the BLE scan occurs on the background. I initially thought about creating an object with a name similar to bleDeviceFinder and pass this object through each view. However, after thinking about it I realised that if I want something to happen in the current view I need the function didDiscoverPeripheral to have direct access to the UIView objects which it is supposed to affect.
I know it is probably a stupid question, what would be the best way to do so? I was thinking maybe to set and alert and subscribe every view to that alert; is this a good solution?
A quasi singleton BTLEManager that you pass around in the app. It sends NSNotifications for events like discovery, and your ViewControllers observe these notifications. The truth (i.e. list of discovered devices) stays in BTLEManager. Once a viewController has received such a notification it asks the BTLEManager for the list of current devices and then the viewController changes your views accordingly. The Views should never talk to the BTLEManager directly.
That's how I would do it.

NSWorkspaceDidActivateApplicationNotification called before application is ready to use (launched)

I'm using both NSWorkspaceDidActivateApplicationNotification and NSWorkspaceDidLaunchApplicationNotification notifications to know which app the user is interacting with.
The problem is that, if an application is just opened and still launching, I first receive a activate notification, and soon afterwards a launch notification.
Is there any way to know within the activate method that the app is still launching and not yet ready for use? (Still bouncing in the dock)
I see that the ichat sample project by apple does not use the above approach and instead only listens to launch notifications. It then uses kAXApplicationActivatedNotification to add an AXObserver to the app. Is this the preferred way? (And also NSRunningApplications to add an observer to all already loaded apps).
I wanted to keep using just plain simple NSNotifications because I think it may be less memory intensive. (No need to keep an observer around for each and every app loaded).
check the NSRunningApplication object passed in the userinfo of the NSWorkspaceDidActivateApplicationNotification
NSRunningApplication *app = [note.userInfo objectForKey:NSWorkspaceApplicationKey];
if(app.isFinishedLaunching)
NSLog(#"up");

Resuming iPod Playback after calling AVAudioSession setActive:NO

What's the best way to resume playback of the ipod or other audio sessions after your app has completed using its session?
NOTE I've already tried calling...
[_session setActive:NO
withFlags:AVAudioSessionSetActiveFlags_NotifyOthersOnDeactivation
error:outError];
I'm building an iOS VoIP app which uses AVAudioSessionCategoryPlayAndRecord as its category and kAudioSessionMode_VoiceChat as its mode. To simplify handling of audio session management, I've got a singleton which listens for notifications on application state and other important events. However, the above code does not seem to cause the music to resume on the iPod app.
Finally, we've also noticed that the music controls that are shown on the bottom of the screen when the home button is double tapped act as if our application is the one playing audio. Yet we've already set it to be inactive.
Would love to understand more about what's going on here if anyone has any clue.
I seems to me that you are unable to notify the iOS that you no longer require an audio session. Are you sure that the call to "setActive" doesn't fail? Or maybe somewhere else in the code you reactivate your session.
Other than that, the Music application should be able to re-gain it's audio as soon as the system realizes you are not using it. This can be accomplished by third-party applications using the AVAudioSessionInterruptionNotification notification. For debugging you might try adding yourself as observer and see if your callback gets called. So, if you successfully deactivate your session, other applications should be able to do the rest.

iOS app lifecycle regarding CLLocationManager startMonitoringForRegion

In iOS one can purportedly use the CLLocationManager's startMonitoringForRegion: method to register a delegate to respond to the device moving into a specific geographic region, even when the app isn't launched. From the CLLocationManager Class Reference:
In iOS, the regions you register with the location manager persist between launches of your application. If a region crossing occurs while your iOS app is not running, the system automatically wakes it up (or relaunches it) in the background so that it can process the event. When relaunched, all of the regions you configured previously are made available in the monitoredRegions property of any location manager objects you create.
I assume if the app is relaunched, iOS doesn't actually bring it to the foreground. I couldn't find any good samples illustrating where startMonitoringForRegion fits into an overall application, so my questions are:
Does one have to register the delegate for startMonitoringForRegion from somewhere specific? I'm guessing it can't be plonked in a view controller if we're launching the app without bringing it into the foreground. Can someone give an example with some context around it?
If we decide we do want to bring the app into the foreground as a result of entering the region, how would we do so?
When is startMonitoringForRegion registered with the OS and when is it unregistered? Does the user have to have launched the app at least once (even if it's since been killed) for the initial registration to take place? What about if the user powers off the device? Will our handler be registered the next time the device is powered on, or will the user have to launch the app at least once again?
Does one have to register the delegate for startMonitoringForRegion from somewhere specific? I'm guessing it can't be plonked in a view controller if we're launching the app without bringing it into the foreground.
False. A view controller object still does exists if it's allocated-initialized, even if its contents are not presented.
If we decide we do want to bring the app into the foreground as a result of entering the region, how would we do so?
Not possible using public APIs (I'm not sure whether an app in the background can use - [UIApplication openURL:] with its own URL scheme to bring itself into the background, but I doubt it); however you may be able to use the SpringBoardServices framework to launch your app:
SBSLaunchApplicationWithIdentifier(CFSTR("com.mycompany.theBestiPhoneAppEver"), false);
When is startMonitoringForRegion registered with the OS and when is it unregistered? Does the user have to have launched the app at least once (even if it's since been killed) for the initial registration to take place?
If the user never runs your application, code inside will never be run, so it won't get registered.
What about if the user powers off the device? Will our handler be registered the next time the device is powered on, or will the user have to launch the app at least once again?
Now that's a good question. I don't know it off the top of my head, nor did I find an answer in the documentation (probably you haven't found that either), so I'd say you just better try it yourself to be sure.

Reasons for MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification

The documentation for MFMessageComposeViewController says you should check [MFMessageComposeViewController canSendText] before trying to create a MFMessageComposeViewController.
It also says:
in iOS 5.0 or later, you should register as an observer of the MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification notification to be notified of changes in the availability of sending text messages.
What possible reasons could there be for the availability of text messaging changing?
Is it just to make Linus Torvalds cry?
Background: I’m creating an iOS app that requires the ability to send SMS messages. I’ve added sms to UIRequiredDeviceCapabilities in Info.plist. It should only run on iPhones (according to Apple docs, I read somewhere this does not include iPod touches or iPads solely with iMessage)
If text messaging is not available, the app is useless, and should present some useful error message.
Update: I’ve tested with my iPhone 4S that simply having a locked SIM still returns YES from canSendText – still not a reason for message sending ability to change.
Update #2: I have created a simple test app to see what might cause it. I have not received the notification at all.
Things that don’t cause MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification
Turning iMessage on & off
Losing WiFi connectivity
Turning on airplane mode
Locking iPhone SIM card
Things that can cause canSendText to change between YES/NO (but not send a notification)
Turning iMessage on & off on an iPad (I assume iPod touch too)
I’m guessing the correct case is for iPads and iPod touches when iMessage is turned on or off, however, there must be a bug (either in my test code or Apple’s) that’s preventing this from working.
If you need this notification, I tried just checking [MFMessageComposeViewController canSendText] again on UIApplicationDidBecomeActiveNotification. This sometimes works, but if you switch quickly between Settings.app and your app, it may not have changed yet by the time you switch to your app, causing canSendText to still reflect the old value.
My solution for now is just going to be simply checking canSendText just before relevant decisions are made, and handle the (rare?) case where it may have changed without me knowing in some appropriate way.
Since MFMessageComposeViewController can use iMessage in iOS5, I'm guessing it's probably to handle the case where iPod Touches and Wi-fi iPads lose internet connectivity while a message is being typed.
I managed to rule out another scenario where it does not trigger MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification.
Sender iPad with a working wifi connection
Reciepient Android phone
Result: MessageComposeResult == MessageComposeResultSent, MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification was not triggered
Next I tried the same scenario with iMessage in order to duplicate the final results. (== not being able to send an iMessage).
Then I realized that iMessage was able to tell me in advance this would never work by putting a red exclamation next to the recipients name with the message this person is not registered with iMessage. The MFMessageComposeViewController does not do this!!!
My current conclusion is: canSendText should be named canComposeText.
MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification does nothing.
Currently, there is no case where MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification is sent.
The only time when the value of canSendText changes is when iMessage is turned on/off on an iPad or iPod Touch (not an iPhone, which has SMS messaging).
Even when canSendText changes, no notification is sent.
(Unless it’s just a bug in my test code?) This appears to be a bug or missing feature in Apple’s code.