We are recording audio with AVAudioRecorder but when ever we receive any incoming call Recording gets stopped. If we try to Resume recording again then it will start from the beginning. I have checked the Voice Memos(iOS Apple Application) & find the same problem. I have also checked the Audio Queue services Sample Code (SpeakHere) But same problem is there.
So Please help how to resume audio recording after incoming call?
Note:- I Know there are many question related to audio recording But unable to find solution.So please Help, Your help is greatly appreciated. Thanks in advance.
It is possible in iOS,to resume a audio recording after incoming call.Basic concept behind this is the use of AVAudioSession and AudioQueue ,when you receive a incoming call on your device it would give a kAudioSessionBeginInterruption and on disconnecting the call you got a kAudioSessionEndInterruption......on receiving a incoming call ,just pause the audio queue and then [[AVAudioSession sharedInstance]setActive:NO error:nil];
......Then on the kAudioSessionEndInterruption ,when the call ends,just resume the paused queue and [[AVAudioSession sharedInstance]setActive:YES error:nil];
This will work......I have successfully run the program and able to continue the recording after receiving an incoming call......
Hope this will help you.......
Apple explain AVAudioSessionInterruption observer as a new method to handle interruptions. Apple explains it in Swift at this page but for someones that search for objective-c code this github issue must be useful.
You need to handle audio interrupts like
AVAudioSession *session = [AVAudioSession sharedInstance];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(audioSessionInterruptionNotification:)
name:AVAudioSessionInterruptionNotification
object:session];
where your audio session interruption notification AVAudioSessionInterruptionNotification is something like
-(void)audioSessionInterruptionNotification:(NSNotification*)notification {
NSString* seccReason = #"";
//Check the type of notification, especially if you are sending multiple AVAudioSession events here
NSLog(#"Interruption notification name %#", notification.name);
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
seccReason = #"Interruption notification received";
//Check to see if it was a Begin interruption
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
seccReason = #"Interruption began";
} else if([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]){
seccReason = #"Interruption ended!";
//Resume your audio
}
}
}
You need to write your handling code then for interruption begin and end.
Use the AVFoundation Delegate Methods,
-(void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder
{
[audiorecorder pause];
}
-(void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder
{
[audiorecorder record];
}
Related
I have created a camera using AVCaptureSession. I have configured that for both Photo and Video recording modes.
Camera and App is running fine. Also I allowed background music play (If user play song using Music App in iPhone) while open camera or recording video. It is also working fine. (Attached image 2)
I allowed background Music play with the help of this code
AVAudioSession *session1 = [AVAudioSession sharedInstance];
[session1 setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers|AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetooth error:nil];
[session1 setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
Now if i receive a call, minimize phone call screen by tapping on Home button and open app and want to open camera screen to capture image / record video, It opens but freeze with a image (Attached image(1)).
Now my requirement is, i want to capture image / record video while on phone call. I looked for another apps, and Snapchat is doing same, and i am able to record video while i am on call.
please help me, how can i achieve this.
You need to use the AVCaptureSessionWasInterruptedNotification and AVCaptureSessionInterruptionEndedNotification callbacks and disconnect the audio capture while the session is interrupted:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:self.session];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:self.session];
// note that self.session is an AVCaptureSession
-
- (void)sessionWasInterrupted:(NSNotification *)notification {
NSLog(#"session was interrupted");
AVCaptureDevice *device = [[self audioInput] device];
if ([device hasMediaType:AVMediaTypeAudio]) {
[[self session] removeInput:[self audioInput]];
[self setAudioInput:nil];
}
}
- (void)sessionInterruptionEnded:(NSNotification *)notification {
NSLog(#"session interuption ended");
}
// note that [self audioInput] is a getter for an AVCaptureDeviceInput
This will allow the camera to continue running and allows it to capture stills / silent video
Now as for how to reconnect the audio after the call ends.. let me know if you figure it out, seemed impossible as of iOS 10: Callback when phone call ends? (to resume AVCaptureSession)
I have added an observer for the interrupt notification when recording audio.
This works fine when performing an outgoing-call, getting an incoming call and not answering, Siri, etc..
Now my app is running in the background with the red bar at the top of the screen, and continuing the recording in the states described above is not a problem.
But when I actually answer an incoming-call. I get another AVAudioSessionInterruptionTypeBegan notification and then when I stop the call, I never get a notification AVAudioSessionInterruptionTypeEnded type.
I have tried using the CTCallCenter to detect when a call has started, but I am unable to restart the recording from that callback.
Does anyone know how to get the interrupt mechanism to work with an incoming call that is actually getting answered?
This is (part of) the code I am using;
CFNotificationCenterAddObserver(
CFNotificationCenterGetLocalCenter(),
this,
&handle_interrupt,
(__bridge CFStringRef) AVAudioSessionInterruptionNotification,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately );
...
static void handle_interrupt( CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo )
{
au_recorder *recorder = static_cast<au_recorder*>( observer );
NSNumber* interruptionType = [( ( __bridge NSDictionary* ) userInfo ) objectForKey:AVAudioSessionInterruptionTypeKey];
switch ( [interruptionType unsignedIntegerValue] )
{
case AVAudioSessionInterruptionTypeBegan:
{
// pause recorder without stopping recording (already done by OS)
recorder->pause();
break;
}
case AVAudioSessionInterruptionTypeEnded:
{
NSNumber* interruptionOption = [( ( __bridge NSDictionary* ) userInfo ) objectForKey:AVAudioSessionInterruptionOptionKey];
if ( interruptionOption.unsignedIntegerValue == AVAudioSessionInterruptionOptionShouldResume )
{
recorder->resume();
}
break;
}
}
}
I have tried binding the notification to either the AppDelegate, a UIViewController and a separate class, but that doesn't appear to help.
Edit
This is what I tried using the CTCallCenter, but this is very flaky. When recorder->resume() is called from the callback, it either works, crashes violently or doesn't do anything at all until the app is put back in the foreground again manually.
callCenter = [[CTCallCenter alloc] init];
callCenter.callEventHandler = ^(CTCall *call)
{
if ([call.callState isEqualToString:CTCallStateDisconnected])
{
recorder->resume();
}
};
UPDATE
If you hackily wait for a second or so, you can restart the recording from your callEventHandler (although you haven't described your violent crashes, it works fine for me):
if ([call.callState isEqualToString:CTCallStateDisconnected])
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
self.recorder->resume();
});
}
This is all without a background task or changing to an exclusive audio session. The delay works around the fact that the call ending notification comes in before Phone.app deactivates its audio session & 1 second seems to be small enough to fall into some kind of background descheduling grace period. I've lost count of how many implementation details are assumed in this, so maybe you'd like to read on to the
Solution that seems to be backed by documentation:
N.B While there's a link to "suggestive" documentation for this solution, it was mostly guided by these two errors popping up and their accompanying comments in the header files:
AVAudioSessionErrorCodeCannotInterruptOthers
The app's audio session is non-mixable and trying to go active while in the background.
This is allowed only when the app is the NowPlaying app.
AVAudioSessionErrorInsufficientPriority
The app was not allowed to set the audio category because another app (Phone, etc.) is controlling it.
You haven't shown how your AVAudioSession is configured, but the fact that you're not getting an AVAudioSessionInterruptionTypeEnded notification suggests you're not using an exclusive audio session (i.e. you're setting "mix with others"):
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
The obsoleted CTCallCenter and newer CXCallObserver callbacks seem to happen too early, before the interruption ends, and maybe with some further work you could find a way to run a background task that restarts the recording a little while after the call ends (my initial attempts at this failed).
So right now, the only way I know to restart the recording after receiving a phone call is:
Use an exclusive audio session (this gives you an end interruption):
if (!([session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]
&& [session setActive:YES error:&error])) {
NSLog(#"Audio session error: %#", error);
}
and integrate with "Now Playing" (not joking, this is a structural part of the solution):
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand addTargetWithHandler:^(MPRemoteCommandEvent *event) {
NSLog(#"PLAY");
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.pauseCommand addTargetWithHandler:^(MPRemoteCommandEvent *event) {
NSLog(#"PAUSE");
return MPRemoteCommandHandlerStatusSuccess;
}];
// isn't this mutually exclusive with mwo? or maybe that's remote control keys
// not seeing anything. maybe it needs actual playback?
NSDictionary* nowPlaying = #{
MPMediaItemPropertyTitle: #"foo",
MPMediaItemPropertyArtist: #"Me",
MPMediaItemPropertyAlbumTitle: #"brown album",
};
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlaying;
Pieces of this solution were cherry picked from the Audio Guidelines for User-Controlled Playback and Recording Apps documentation.
p.s. maybe the lock screen integration or non-exclusive audio session are deal breakers for you, but there are other situations where you'll never get an end interruption, e.g. launching another exclusive app, like Music (although this may not be an issue with mixWithOthers, so maybe you should go with the code-smell delay solution.
I am working on iOS app which will be compatible with iOS 6/7 and stream audio .mp3 files from a website.
I have already gotten this to work using the following code:
-(NSString*)documentsFolder
{
NSString* dataPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:NULL];
return dataPath;
}
-(NSString*)createURLFile:(NSString*)songURL
{
NSString* M3U_FILE = #"song.m3u";
NSString* path = [NSString stringWithFormat:#"%#",[[self documentsFolder] stringByAppendingPathComponent:M3U_FILE]];
if([[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil])
{
NSFileHandle* outFile = [NSFileHandle fileHandleForWritingAtPath:path];
if(outFile != nil)
{
NSData* buffer = [songURL dataUsingEncoding:NSUTF8StringEncoding];
[outFile writeData:buffer];
return path;
}
}
return nil;
}
- (void)createStreamer
{
// Remove any previous references.
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Create a new player.
NSString* fileURL = [self createURLFile:self.aSong.songpath];
self.songPlayer = [[AVPlayer alloc]initWithURL:[NSURL fileURLWithPath:fileURL]];
NSAssert(self.songPlayer != nil, #"NIL AVPlayer Created!!!");
// Observer for when the song ends...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.songPlayer currentItem]];
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
I store the url for the .mp3 file in a local .m3u file and use that to load up the AVPlayer. In earlier versions of iOS, I was told that the AVPlayer would load the song first and then play it, not stream it immediately. While this does not appear to be true in iOS 6/7 (the song starts streaming almost immediately), the .m3u file was being created in case there were any problems created by not having it done this way.
With this, a loop is monitoring the status of the AVPlayer and after a few seconds, the audio starts to play out the phone without a problem.
For testing purposes, I set up an MPVolumeView on the page which plays songs:
MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:CGRectMake(0, 0, 310, 20)] autorelease];
volumeView.center = CGPointMake(160,62);
[volumeView sizeToFit];
[self.view addSubview:volumeView];
The reason for this is that the volume slider will also show an indicator if the bluetooth is connected as an audio output source and allow me to change the audio route between the phone and the bluetooth device. So far, so good.
I connected my phone to my Jawbox Jambone via bluetooth, start the AVPlayer on a song, and the song comes out of the Jawbox as expected. The volume control has the small "rectangle with arrow" indicating that I can switch the audio output and indeed, while the song is playing, I can switch between the phone and the Jawbox. Happiness.
The problem occurs when I try to connect it to a car. I have two experiences with this:
The car is already paired with the phone for making/receiving calls. The phone even indicates it is paired when I get into the car. But when I use the same code to play the same audio files, they only come out of the phone. The volume slider does not show the "bluetooth route" indicator at all (like it does not recognize the car as a audio output route).
In another car, the audio was streaming from another app (some radio streaming app). The other app was stopped and this one started. The audio started playing for the same song tested above, but stopped after a second or two. Again, there was no indicator on the volume slider that the bluetooth was connected at this point.
Can somebody explain to me why the audio could stream fine out to one bluetooth device but not to another?
Have I missed something (an entitlement?) in the profile for my app that will allow it to stream audio via bluetooth to a car?
There is this project at GIT.
Play iOS project is a streaming client for Play that runs on your iPhone/iPad. It supports background audio as well as the media keys when backgrounded.
It supports:
Streams shoutcast stream
Displays currently playing track
Background audio
Lock screen album art & play controls
AirPlay (along with Bluetooth) streaming. Supports sending metadata
and album art
You can download the project here.
I have not tested this on CAR bluetooth audio player though. Hope it may be of any help to you.
In the first example, your car may simply be a remote player. You would need to register for remote events like this (consider using an AVAudioPlayer instead of an AVPlayer also)
Setup the AudioSession to recognize a bluetooth audio route:
- (BOOL)prepareAudioSession {
// deactivate existing session
NSError *setCategoryError = nil;
NSError *activationError = nil;
BOOL success = [[AVAudioSession sharedInstance] setActive:NO error: nil];
if (!success) {
NSLog(#"deactivationError");
}
// set audio session category AVAudioSessionCategoryPlayAndRecord options AVAudioSessionCategoryOptionAllowBluetooth
success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:&setCategoryError];
if (!success)
{
NSLog(#"setCategoryError %#",setCategoryError);
}
// activate audio session
success = [[AVAudioSession sharedInstance] setActive:YES error: &activationError];
if (!success) {
NSLog(#"activationError");
}
return success;
}
You can check the routes:
AVAudioSessionRouteDescription *mAVASRD = audioSession.currentRoute;
NSLog(#"the array is %#",mAVASRD.outputs);
for (int ctr = 0; ctr < [mAVASRD.outputs count]; ctr++)
{
AVAudioSessionPortDescription *myPortDescription = [mAVASRD.outputs objectAtIndex:ctr];
NSLog(#"the type is %#",myPortDescription.portType);
NSLog(#"the name is %#",myPortDescription.portName);
NSLog(#"the UID is %#",myPortDescription.UID);
NSLog(#"the data sources are %#",myPortDescription.dataSources);
}
Then initialize your AVAudioPlayer and turn on RemoteControlEvents (you can use the console in your car to send play/pause/etc)
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
then implement something like the delegate method for AVAudioPlayer in this stack overflow question to capture the received events and react accordingly in your code:
AVAudioPlayer on Lock Screen
In the scenario 2, when you moved one app to the background (the radio streaming app) and started your app, the likely culprit for the issue is the same cause - your app has to recognize the bluetooth route for audio.
By the way for phone calls and Siri, the iOS uses a different Bluetooth channel that the default for remote control (which is the one I am describing for your car).
When you setup this route and remote control events, you also get a bonus byproduct - your app will be controllable from the lock screen. Check out this technical note from Apple to configure your app to play in the background as well if that is also something you need to do when the screen locks: Technical QA document QA1668
Finally, for added integration via your bluetooth route, look at MPNowPlayingInfoCenter - put the title artist artwork and other good stuff on the lock screen and on most bluetooth screens in the car that are displaying that information.
I'm pretty sure MPVolumeView can only address Bluetooth devices which conform to the newer low power consumption Bluetooth spec... (Bluetooth Low Energy or BLE)...
I know the phone app doesn't use MPVolumeView, probably this other audio player doesn't either.. You may need to look into CoreBluetooth and implement your own :( good luck. There may be a solution on github
A bluetooth speaker designed as a speaker will be no problem.
However, a car will usually be a "phone" bluetooth speaker and will only accept a "phone" type of communications.
My guess would be that you would have to trick it by setting up a "phone audio" connection and having the incoming audio transfer into the void, and the outgoing music stream as a phone signal.
Mind you, signal quality might degrade and there probaly won't be a fix for that.
I'm trying to figure out if this is possible - my app activates an audio session that is initialized as:
[[[AVAudioSession alloc] init] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
I would like to be able to understand when an additional audio session that originated from another app or the OS is playing.
I know about the ability to implement the delegate methods beginInterruption: and endInterruption but these won't get invoked because of the AVAudioSessionCategoryOptionMixWithOthers option I'm using.
Is there a way to achieve this without using private API?
Thanks in advance.
The way you manage your application's Audio Session has had some significant changes since iOS 6.0, and deserves a brief mention first. Before iOS 6.0 you would make use of AVAudioSession and AudioSessionServices classes, incorporating delegation and property listening respectively. From iOS 6.0 onwards use AVAudioSession class and incorporate notifications.
The following is for iOS 6.0 onwards.
To tell if other audio outside your applications sandbox is playing use -
// query if other audio is playing
BOOL isPlayingWithOthers = [[AVAudioSession sharedInstance] isOtherAudioPlaying];
// test it with...
(isPlayingWithOthers) ? NSLog(#"other audio is playing") : NSLog(#"no other audio is playing");
As for interruption handling you'll need to observe AVAudioSessionInterruptionNotification and AVAudioSessionRouteChangeNotification. So in the class that manages your audio session you could put something like the following - this should be called once at the start of the application lifecycle and don't forget to remove observer in the dealloc method of the same class.
// ensure we already have a singleton object
[AVAudioSession sharedInstance];
// register for notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(interruption:)
name:AVAudioSessionInterruptionNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(routeChange:)
name:AVAudioSessionRouteChangeNotification
object:nil];
And finally add the following selectors interruption: and routeChange: - these will receive a NSNotification object that has a property called userInfo of type NSDictionary that you read to assist any conditionals your application has.
- (void)interruption:(NSNotification*)notification {
// get the user info dictionary
NSDictionary *interuptionDict = notification.userInfo;
// get the AVAudioSessionInterruptionTypeKey enum from the dictionary
NSInteger interuptionType = [[interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
// decide what to do based on interruption type here...
switch (interuptionType) {
case AVAudioSessionInterruptionTypeBegan:
NSLog(#"Audio Session Interruption case started.");
// fork to handling method here...
// EG:[self handleInterruptionStarted];
break;
case AVAudioSessionInterruptionTypeEnded:
NSLog(#"Audio Session Interruption case ended.");
// fork to handling method here...
// EG:[self handleInterruptionEnded];
break;
default:
NSLog(#"Audio Session Interruption Notification case default.");
break;
} }
And similarly...
- (void)routeChange:(NSNotification*)notification {
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonUnknown:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonUnknown");
break;
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
// a headset was added or removed
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonNewDeviceAvailable");
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
// a headset was added or removed
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
// called at start - also when other audio wants to play
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonCategoryChange");//AVAudioSessionRouteChangeReasonCategoryChange
break;
case AVAudioSessionRouteChangeReasonOverride:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonOverride");
break;
case AVAudioSessionRouteChangeReasonWakeFromSleep:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonWakeFromSleep");
break;
case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory");
break;
default:
break;
} }
There is no need to poll anything as long as you check the state of your applications audio session say for example in the viewDidLoad of your root view controller, at the start of you apps lifecycle. Any changes from there onwards to your applications audio session will be known via these two main notifications. Replace the NSLog statements with what ever your code needs to do based on the cases contained in the switch.
You can find more information about AVAudioSessionInterruptionTypeKey and AVAudioSessionRouteChangeReasonKey in the AVAudioSession class reference documentation.
My apologies for the long answer but I think Audio Session management in iOS is rather fiddly and Apple's Audio Session Programming Guide, at the time of writing this, does not include code examples using notifications for interruption handling.
You can check if other audio is playing like this:
UInt32 otherAudioIsPlaying;
UInt32 propertySize = sizeof (otherAudioIsPlaying);
AudioSessionGetProperty (kAudioSessionProperty_OtherAudioIsPlaying, &propertySize, &otherAudioIsPlaying );
[self handleIfAudioIsPlaying: otherAudioIsPlaying];
Then you can add a loop and check every X second if something changed.
I have an iPhone app (5.1 SDK) which plays audio in the background. Many times when there is an incoming call, the incoming call is displayed but the user cannot slide the slider to answer it and the call is missed.
I am using - (void)beginInterruption to pause all audio when an incoming call arrives but it doesn't seem to stop this issue from occurring.
Has anyone ever encountered this before?
In case you wanna mark this as the answer since it was indeed your problem I'll repost my comment.
Is your beginInterruption code being called? If so, is the music actually stopping? Are you streaming audio over the network or local files? What player are you using?
- (void) beginInterruption {
if (playing) {
playing = NO;
interruptedWhilePlaying = YES;
[self updateUserInterface];
}
}
NSError *activationError = nil;
- (void) endInterruption {
if (interruptedWhilePlaying) {
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) { /* handle the error in activationError */ }
[player play];
playing = YES;
interruptedWhilePlaying = NO;
[self updateUserInterface];
}
}