Callback for kAudioSessionProperty_AudioRouteChange not called in one case - objective-c

I want to be notified if headphones are plugged in or plugged out. I'm currently doing this using a property listener like this:
AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, myCallback, self);
This works perfectly well in all cases except in one, it goes like this:
Launch, iPhone is unmuted, no headphones plugged in
Switch the mute button on: The callback is called, good
Plug in the headphones now: Callback is not called, not good
Is there another property I should consider? Is there a special case involving the mute button I'm not aware of?
I'm using an iPhone 3GS and iOS 4.1.
Thanks a lot for any help!
Patrick

This bug/feature only happens when using an audio session category affected by the silent switch (e.g. kAudioSessionCategory_SoloAmbientSound). kAudioSessionCategory_MediaPlayback for example works as expected.

Related

How should I go about stopping/resuming all sounds within my iOS game?

I currently have background music that starts up when the app opens and runs an infinite loop until the user goes into the settings and turns the music off using this
-(IBAction)stopMusicButton{
[(AppDelegate*)[UIApplication sharedApplication].delegate stopMusic];
}
They can also resume the music in the settings page. What I'm a confused about is how to stop the other sounds in my app from playing if the user doesn't want the noise. For example, when the user navigates the menu of my app there are various sounds that play when certain things are pressed. How can I allow the user to be able to toggle these sounds on and off? There are also a few different views with their respective .h/.m files, all of which play sounds. If you could point me in the right direction I'd really appreciate it! Thanks in advance!
I use a singleton class to store settings information - you could probably use your app delegate as well. All you need is a boolean property called isSoundMuted or something like that. Set that whenever the sound is muted or unmuted, then check the variable's value before playing any sounds in an if block.
if (MySingletonClass.isSoundMuted == NO)
{
[self playSound]
}

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.

Receive remote control events without audio

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

Sound does only work on Device but not in Simulator

I am playing some short sounds on my iPad like so: Play a short sound in iOS
I am using a caf file which I can successfully play from the Finder. Now I went through quite a bit of a hassle trying to achieve the playback of the sound and I am curious what might be the problems which I don't seem to understand:
Option 1: When I create the SystemSoundID and then play it right away I don't hear anything on the device and the simulator.
Option 2: When I create an instance variable for the SystemSoundID and initialize it in viewDidLoad I manage to play sound but only on the iPad, not the Simulator.
Option 3: Instead of using SystemSoundID I can also use AVAudioPlayer to playback a .wav file which then works on both the iPad and the Simulator but here I need to create the AVAudioPlayer in viewDidLoad otherwise I won't get any sound if I do everything in one go.
The best option currently seems to be Option 3 because it works on both the Simulator and the iPad, but because I need to pre-initialize the Player I would need an AVAudioPlayer instance for every different sound that I want to play, which does not seem to be very memory-wise...
Is there something that I am missing and is it possible to play sounds on both platforms using the AudioToolbox framework (Option 1 & Option 2)
I wrote a library to simplify all this. It wraps AVAudioPlayer, and works fine on both the device and simulator.
https://github.com/nicklockwood/SoundManager
The code is fairly straightforward, although I do some semi-clever stuff to initialise the audio player. If you don't want to use the library you can just copy the code.
A word of warning though - the simulator throws some odd exceptions internally whenever you use AVAudioPlayer. They don't affect the app at all, but if you have enabled break-on-exceptions in Xcode then the app will drop into the debugger a few times during startup and you'll have to manually resume, which may freak you out if you're not expecting it.

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.