I'm working on a game made in Unity and I'm trying to handle the case where the player has headphones that have a remote control built into them. The player presses the play button on the remote control while playing the game and music from a backgrounded music app begins playing. Ideally, we would capture this event and turn our music off but I can't seem to find a notification that is triggered in this situation. I've tried the following in AppController.mm:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
...
// music notifications
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter
addObserver: self
selector: #selector (handlePlaybackStateChanged:)
name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification
object: [MPMusicPlayerController iPodMusicPlayer]];
[[MPMusicPlayerController iPodMusicPlayer] beginGeneratingPlaybackNotifications];
return NO;
}
- (void)handlePlaybackStateChanged:(NSNotification*)notification
{
NSLog(#"Playback State: %d", [MPMusicPlayerController iPodMusicPlayer].playbackState);
//if([[MPMusicPlayerController iPodMusicPlayer].playbackState
}
Does anyone know of a notification that is actually triggered in this case or a way that I can identify this situation? Thanks in advance!
Modifications in AppControler.mm will be lost on Unity updates. The safer way is to place all native code in folder Assets/Plugins/iOS, s. Building Plugins for iOS for more information.
I made a small plugin called iPodHandlerPlugin on gitHub. Just put the lib file in Assets/Plugins/iOS and IPodHandler somwhere under Scripts.
To get notified on state changes follow the instructions given in the readme.
Maybe a static library is somewhat overdosed. Alternatively you can use the files iPodHandlerPlugin.mm and UnityIPodCallbackListener.m directly in Assets/Plugins/iOS.
Related
I want to know when my AVAudioRecorder is inaccessible (e.g when music starts playing).
As audioRecorderEndInterruption will be deprecated with iOS 9 I am focusing on AVAudioSession's interruption notification (but neither is working as expected).
The issue is that the interruption notification is never called if the app was and remains in the foreground when the interruption occurs.
E.g: The user starts and stops playing music without moving the application into the background.
To detect any interruptions I am using:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(audioSessionWasInterrupted:) name:AVAudioSessionInterruptionNotification object:nil];
...
- (void)audioSessionWasInterrupted:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
NSLog(#"Interruption notification");
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(#"InterruptionTypeBegan");
} else {
NSLog(#"InterruptionTypeEnded");
}
}
}
I get InterruptionTypeBegan as expected, but InterruptionTypeEnded isn't called if the app is still in the foreground (meaning it won't be called until the app is placed in the background and back into the foreground).
How may I receive InterruptionTypeEnded notification when the interruption occurs while the app is in the foreground?
This is a widespread problem affecting any app using AV framework components (the same goes for native iOS apps).
As explained in 's documentation on the subject of audio interruptions, the InterruptionTypeEnded should actually be applied in the scenario mentioned:
If the user dismisses the interruption ... the system invokes your callback method, indicating that the interruption has ended.
However, it also states that the InterruptionTypeEnded might not be called at all:
There is no guarantee that a begin interruption will have an end interruption.
Therefore, a different approach is needed in the scenario mentioned.
When it comes to handling music interruptions, the issue won't be around for long. iOS 9 effectively prevents outside audio sources to be used while the app's audio handler is invoked.
A way to handle the exact issue of media interruption could be to listen to MPMusicPlayerController's playbackState, as shown in this stackoverflow question: Detecting if music is playing?.
A more direct way to handle the issue of interruptions would be to either:
Block outside audio interruptions completely by re-invoking your audio component at the time of InterruptionTypeBegan.
Or by giving a UI indication that an outside media source has interrupted the audio session (for example showing an inactive microphone).
Hopefully will come up with a better solution to the problem, but in the meantime this should give you some options to solve the interruption issue.
If you haven't already, try setting your AVCaptureSession's property usesApplicationAudioSession to NO.
This question & answer may act as a good reference if you're looking for any more detail.
I try this and find InterruptionTypeEnded may called after music pause in some app, but other not called when pause.
My solution is update the UI to let user know record has stopped and do some related work such as file operation. When interruption ends, active AVAudioSession, if doesn't have error, start a new record.
If you want to join the file before and after interrupt, the answer to this question: AVAudioRecorder records only the audio after interruption may be helpful to you.
I have an app that speaks to the user and listens to the user's speech response. I've noticed that when I plug my phone into my car audio system and use the app, when my app is done speaking, it receives an interruption notification and the Music app starts playing music instead of allowing my app to continue.
This doesn't happen if the phone is not attached to an external device, and this doesn't happen the moment I plug the phone in, only when the speech stops and the phone is playing through the car. I have done some testing and determined that this behavior appears when I call the beginReceivingRemoteControlEvents method on my application. If I don't sign up for remote control events when my application loads, the problem does not occur, but I cannot display 'now playing' information for my audio or use the car's controls for controlling playback.
Has anyone found a way to listen for remote control events without forfeiting control of the device's audio playback?
This is often caused by the car stereo rather than your iOS device. Check the stereo's manual and switch it from Audio Mode to iPod Mode (or whatever your manual names these options). Basically, your car stereo is listening for the track ended notification and using that to trigger a 'play next track' notification. This calls the MPMusicPlayer which usually picks the first track alphabetically in your device's library. It could be that there is a workaround in software but I've found that the easiest thing is to change the setting on the car stereo.
Use the following to disable remote control events (you may have to replace togglePlayPauseCommand with playCommand, or do both):
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(#"toggle button pressed");
return MPRemoteCommandHandlerStatusSuccess;
}];
or, if you prefer to use a method instead of a block:
[commandCenter.togglePlayPauseCommand addTarget:self action:#selector(toggleButtonAction)];
To stop:
[commandCenter.togglePlayPauseCommand removeTarget:self];
or:
[commandCenter.togglePlayPauseCommand removeTarget:self action:#selector(toggleButtonAction)];
You'll need to add this to the includes area of your file:
#import MediaPlayer;
My iPad sound application (iOS 7.1) is able to record while in background. Everything is ok as far as recording is not interrupted while in background. For example if one's has the (stupid?) idea to start listening music while recording something.
I tried to manage this interruption in different ways, with no success. The problem is that the
- (void)audioRecorderEndInterruption:(AVAudioPlayer *)p withOptions:(NSUInteger)flags
is never fired when application was in background as the interruption occurred. I then tried, in another solution, to implement the AVAudioSessionInterruptionNotification and a handleInterruption: method as
- (void) viewDidAppear:(BOOL)animated
{
......
AVAudioSession *session=[AVAudioSession sharedInstance];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleInterruption:)
name:AVAudioSessionInterruptionNotification
object:session];
.......
}
// interruption handling
- (void)handleInterruption:(NSNotification *)notification
{
try {
UInt8 theInterruptionType = [[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] intValue];
NSLog(#"Session interrupted > --- %s ---\n", theInterruptionType == AVAudioSessionInterruptionTypeBegan ? "Begin Interruption" : "End Interruption");
.... MANAGE interruption begin
interruptionBeganWhileInBackgroundMode = TRUE;
}
if (theInterruptionType == AVAudioSessionInterruptionTypeEnded) {
.... MANAGE interruption end
}
} catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
}
In this case the handleInterruption: is fired when interruption begins, but it is not when interruption ends (shoulld be fired with theInterruptionType set to AVAudioSessionInterruptionTypeEnded)
To circumvent this problem, I decided to set a flag (interruptionBeganWhileInBackgroundMode) when interruption begins to get the application informed that an interruption has occurred when coming foreground. So that I can manage the end of interruption.
It may seem clever, but it is not! Here is why...
I tried two implementations.
Solution 1. Put a [recorder pause] in handleInterruption: when interruption begins, and manage a [recorder record] in the comingForeground: method when the interruption flag is set.
In this situation, the recording is going on immediately when application comes back to foreground, BUT instead of resuming, the recorder erase the file and restart a new recording, so that everything recorded before interruption is lost.
Solution 2. Put a [recorder stop] in handleInterruption: when interruption begins, to save the file, to be sure to preserve the recorded data. This recording is saved, BUT when application is coming foreground, it stalls for about ten seconds before the user can interact again, as if there was a process (the file saving?) that keep the UI frozen.
Last point: I have exactly the same problems in iPhone version of this application: when application is foreground and a phone call occurs, everything goes OK. But when a phone call occurs as my application is background I see the same bad behaviour as in iPad's version.
I notice that Apple's Voice Memo app in iOS7 correctly manage these background interruptions (it stops and saves the recording) despite it displays a 00:00:00 file length when coming foreground. The iTalk application manages it perfectly, automatically resuming recording when coming foreground.
Did anybody find a workaround for the audio interruption management for backgrounded recording applications? I found plenty of people looking for that in many developer websites, but no answer... Thanks!
I have been going through the same issue myself. It seems as if there is a bug in the iOS7 AVAudioRecorder in how it deals with interrupts. Instead of pausing as I believe the documentation says that is should, it closes the file. I have not been able to figure out what is stalling the app when it comes back to the foreground. In my case, I would see AVAudioRecorder finish (with the success flag set to NO), after 10 seconds.
I ended up rewriting the audio recorder using Audio Queues. I found some sample code here (git#github.com:vecter/Audio-Queue-Services-Example.git) that helped with setting it up in an Objective-C environment and the Apple SpeakHere demo has some code to handle the interrupt notifications.
Essentially, I am stopping the recording on interrupt began and opening an alert for the user to save the file. This alert is deferred until UIApplicationDidBecomeActiveNotification is passed if the interrupt started while the app was in the background.
One other thing to note, there seems to be a minor bug in Audio Queues that the AudioQueueStart method will return -50 sometimes. If you add
AudioSessionInitialize(NULL, NULL,nil,(__bridge void *)(self));
UInt32 sessionCategory = kAudioSessionCategory_PlayAndRecord;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
sizeof(sessionCategory),
&sessionCategory
);
AudioSessionSetActive(true);
Before any AudioQueue methods, the error goes away. These methods are marked as deprecated but seem to be necessary.
Don't know / think this is going to be relevant to the original author of the topic, but here is my experience with it:
I ended up here because of the AVAudioSessionInterruptionNotification thing; the .began flavour came as expected, but the .ended one did not.
In my case, it happened because I was using two AVPlayer instances: one to play music, and the other one to play silence while the first struggled to start streaming a new track (otherwise iOS would suspend my app while in background, if next track loading did not happen fast enough).
Turns out it's not the brightest solution, and it somehow messes up the notification system. Giving up the second AVPlayer (the silence playing one) resulted in .ended being triggered as expected.
Of course, I found the answer / solution for the notification problem, but I'm now left with the old one... :-)
So in my app, running on iOS 6, everything seems to work fine with audio. I use the old C API format to catch interruptions using a callback; setting up via: AudioSessionInitialize(NULL, NULL, interruptionListenerCallback, (__bridge void *)self) and it was great. Using iOS 7 SDK though, it seems that my interruption callback is never called when the device receives calls or an alarm goes off.
After some looking around, I heard that the old C api were deprecated and that you should move to the newer AVAudioSession functions. More reading revealed that the AVAudioSession delegate is deprecated and that you should use the NSNotification for AVAudioSessionInterruptionNotification to catch interruptions and do whatever needs to be done.
For me, it seems that this notification is never actually fired and therefore I never get properly interrupted, which then breaks all of my audio stuff after the call ends.
I sign up for the notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(AudioInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
For now, the AudioInterruption: function simply logs that it got fired. Neither the log nor any breakpoints are triggering.
To be clear, initially audio playback and recording works fine. When an interruption occurs (such as an incoming call or an alarm), no interruption notification is fired. If more surrounding code is necessary, let me know.
Do you have an AVCaptureSession instance in your application?
If that is the case, I would suggest the same answer that I received for my linked question:
Try to set NO to the usesApplicationAudioSession property of your AVCaptureSession instance.
It is a property available since iOS 7. In the previous iOS versions, each AVCaptureSession made use of a private AVAudioSession. Since iOS 7, the Capture Sessions make use of the shared application's AVAudioSession.
The usesApplicationAudioSession property is enabled by default, so if you want to keep the old behavior you must disable it, by setting NO.
I hope this will work for you as well.
You didn’t pass the AVAudioSession instance in your addObserver call as the last parameter.
Try this:
AVAudioSession *session = [AVAudioSession sharedInstance];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(AudioInterruption:)
name:AVAudioSessionInterruptionNotification
object:session];
For me, activating audio session and adding observer when user actually push play button has resolved the issue; notification was fired. I think apple doc say something about that here.
I am developing a music player application which is able to play music in the background. As long as the music is playing the application won't get terminated anyway, but if the playback is stopped and the application is in background it may get terminated. To get the application more user-friendly I want to save the playback queue and state when the app is being terminated by the system so I implemented the following lines in the app delegate:
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
/*
Archive the current queue controller to be able to continue the playback like the app never has been terminated
when user is launching it again.
*/
SMKQueueController *queueController = [[UIApplication sharedApplication] queueController];
[coder encodeObject:queueController forKey:#"queueController"];
return YES;
}
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
/*
Restore the recently used queue controller to be able to continue the playback like the app never has been
terminated when use is launching it again.
*/
SMKQueueController *queueController = [coder decodeObjectForKey:#"queueController"];
[[UIApplication sharedApplication] setQueueController:queueController];
return YES;
}
Sometimes (especially when I kill it manually via the double-tap-home-button menu) it works as I would expect it to work. But sometimes this methods aren't called when the application is being terminated.
So my question is: Did I misunderstood how the methods work or what they are for? Or is there any better place to implement such a functionality?
I attended a WWDC 2012 session describing exactly how this should be done. If you are a registered Apple developer, then you can access the videos from the sessions. This one is titled, "Saving and Restoring Application State on iOS". I found this to be a very informative session on this topic.