How to resume a recording using AVAudioRecorder? - iphone

I am writing an application that uses the AVAudioRecorder class. It works great except for when a phone call comes in. I am handling this per apple's guidelines of using the AVAudioRecorderDelegate methods
– (void) audioRecorderBeginInterruption:
– (void) audioRecorderEndInterruption:
It works great until the interruption ends and I attempt to "resume" the recording by calling the record method again (per the documentation). However it does not resume my recording but instead throws out the old one and starts up an entirely new one in its place. I have not been able to find a solution to this problem, if anyone has figured this out, or if it is a bug with apple's AVAudioRecorder please let me know. I really hope I do not have to write this using AudioQueues.
thanks

Looks like its a bug with apple's API. Great fun....
This was the response we received from a support ticket.
"The behavior you described is a bug and unfortunately there's nothing in the API that you can change to work around to actually append to the original recording. The interruption is resulting in capturing only the audio recorded after the interruption. You could try and stop the recording after the interruption then creating a new file after which would at least not cause the user to loose any information, but the result would be two separate files.
Please file a bug report at for this issue since bugs filed by external developers are critical when iOS engineering is evaluating critical features of fixes to address. It's easily reproducible but if you have a test app you can include please do, iOS engineering like having apps that show the bug directly.
"

My solution was:
Start record on temp file
Watch for AVAudioSessionInterruptionNotificatio
On AVAudioSessionInterruptionTypeBegan - stop the recording.
On AVAudioSessionInterruptionTypeEnded - Start new recording.
When the user stops - Marge the files.
Full Code
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(audioSessionInterruptionNotification:)
name:AVAudioSessionInterruptionNotification
object:audioSession];
-(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);
NSError *err = noErr;
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";
NSLog(#"Interruption notification name %# audio pause", notification.name);
dispatch_time_t restartTime = dispatch_time(DISPATCH_TIME_NOW,
0.01 * NSEC_PER_SEC);
dispatch_after(restartTime, dispatch_get_global_queue(0, 0), ^{
AVAudioRecorder *recorder = [[self recorderPool] objectForKey:lastRecID];
if (recorder) {
if(recorder.isRecording) {
[recorder stop];
NSLog(#"Interruption notification name Pauseing recording %#", lastRecID);
} else {
NSLog(#"Interruption notification name Already Paused %#", lastRecID);
}
}else {
NSLog(#"Interruption notification name recording %# not found", lastRecID);
}
NSLog(#"Interruption notification Pauseing recording status %d",recorder.isRecording);
});
} else if([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]){
seccReason = #"Interruption ended!";
NSLog(#"Interruption notification name %# audio resume", notification.name);
//Start New Recording
dispatch_time_t restartTime = dispatch_time(DISPATCH_TIME_NOW,
0.1 * NSEC_PER_SEC);
dispatch_after(restartTime, dispatch_get_global_queue(0, 0), ^{
AVAudioRecorder *recorder = [[self recorderPool] objectForKey:lastRecID];
NSLog(#"Interruption notification Resumeing recording status %d",recorder.isRecording);
if (recorder) {
if(!recorder.isRecording) {
NSString *filePath = [[self orgFileNames] objectForKey:lastRecID];
NSArray * fileNames =[[self fileNames] objectForKey:lastRecID];
NSString *tmpFileName = [self gnrTempFileName:filePath AndNumber:fileNames.count];
[[[self fileNames] objectForKey:lastRecID] addObject:tmpFileName];
NSURL *url = [NSURL fileURLWithPath:tmpFileName];
NSError *error = nil;
recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:&error];
if (![recorder record]) {
NSLog(#"Interruption notification Error Resumeing recording %#",tempRecorder);
return;
}
[[self recorderPool] setObject:recorder forKey:lastRecID];
NSLog(#"Interruption notification nameResumeing recording %#",lastRecID);
}else {
NSLog(#"Interruption notification Already Recording %d",recorder.isRecording);
}
}else {
NSLog(#"Interruption notification name recording %# not found",lastRecID);
}
});
}
}
}

You will try by using this piece of code
-(IBAction)pauseandplay:(id)sender
{
BOOL status= [player isPlaying];
if(status)
{
[pauseplay setImage:[UIImage imageNamed:#"play.png"]];
[player pause];
}
else
{
[pauseplay setImage:[UIImage imageNamed:#"icon-pause.png"]];
[player play];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(updateCurrentTime) userInfo:player repeats:YES];
}
}

Related

How does my app recognize that a song finished?

I´m working on my first app. It plays an mp3 song. Everything works fine, but I need the app to recognize that the sound finished, then change the button “pause” image to the “play” one. I can already do that, but just when the button is pressed, not when song ends.
Another issue is that when the song restarts after stop->play, it doesn´t continue playing at the previous volume setting (got a slider). The player starts at maximum volume. I can change the volume slider position, but I am not able to make the app respect the set volume when restarts.
Any help will be very appreciated!
Thnx a lot.
When using "AVAudioPlayerDelegate protocol delegate", please do not forget to set delegate (for example) as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"33fr" ofType:#"mp3"];
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
myPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error];
if (error) {
NSLog(#"error = %#", error);
return;
}
[myPlayer setDelegate:self]; <<=== HERE!
}
I uploaded sample code here. Please check it. ->
http://yahoo.jp/box/X7Hxfk
I have not done much of Objective-C but it looks like you need to implement a delegate.
I quote: "You implement a delegate to handle interruptions (such as an incoming phone call) and to update the user interface when a sound has finished playing."
I am just reading the AVAudioPlayer Class Reference here:
https://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVAudioPlayerClassReference/Reference/Reference.html#//apple_ref/doc/uid/TP40008067
you can use the AVAudioPlayerDelegate protocol delegate
After the audio player is finished playing the song (if it does so
successfully), the audio PlayerDidFinishPlaying:successfully: delegate
method will be called in the delegate object of the audio player. We
can implement this method like below (this method is defined in the
AVAudioPlayerDelegate protocol):
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
if (flag)
{
NSLog(#"Audio player stopped correctly.");
}
else
{ NSLog(#"Audio player did not stop correctly.");
}
if ([player isEqual:self.audioPlayer])
{ self.audioPlayer = nil;
}
else
{ /* This is not the player */ }
}
in .h file add delegate like below code
#interface yourViewController : UIViewController <AVAudioPlayerDelegate>

Buffering label in a stream using MPMoviePlayer

Im making a radio streaming application, my app is working perfectly. I have two buttons in my screen one for playing and the other for pausing, also I have a label that indicates the state of my player. I have no problem making this label show the state "Playing" or "Paused" my problem is that when I press the play button there is a time where the buffer is gathering information and I can't figure out a way to show a "Buffering..." label before the player start to stream the audio.
This is the code Im using for streaming the radio station.
NSString *url = [NSString stringWithFormat:#"http://66.7.218:8816"];
player = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:url]];
player.movieSourceType = MPMovieSourceTypeStreaming;
player.view.hidden = YES;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance]setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[player prepareToPlay];
[player play];
Im usin a function I created called changeStatus and this function is called each second to identify the status of the player.
-(void)changeStatus
{
if (player.loadState == MPMovieLoadStateUnknown) {
status.text = #"Buffering...";
}
if(player.playbackState == MPMoviePlaybackStatePlaying)
{
status.text = #"Playing.";
}
if(player.playbackState == MPMoviePlaybackStatePaused)
{
status.text = #"Paused";
}
}
I really need to solve this problem and Ive tried all I can to solve it. Hope you can help me! Thanks in advance.
I achieved to solve the problem. The part that was missing is the if clause which gives the signal to start playing only when the player is prepared to play. Without that the player starts to play before the buffer is completely loaded so it plays no audio. And that was the reason my Playing label showed immediately instead of the buffering one.
NSString *url = [NSString stringWithFormat:#"http://66.7.218:8816"];
player = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:url]];
player.movieSourceType = MPMovieSourceTypeStreaming;
player.view.hidden = YES;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance]setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[player prepareToPlay];
if(player.isPreparedToPlay)
{
[player play];
}
Adittionally, the MPMoviePlaybackStateInterrupted refers to the buffering process while doing an stream. So if you want to make something happens in the buffering process refer to this method.
-(void)changeStatus
{
if(player.playbackState == MPMoviePlaybackStatePlaying)
{
status.text = #"Playing.";
}
if(player.playbackState == MPMoviePlaybackStateInterrupted)
{
status.text = #"Buffering...";
}
}
Thanks a lot to the user That Guy who helped me solving this.
Actually there's a notification called
MPMediaPlaybackIsPreparedToPlayDidChangeNotification
The observer will get notified after it is ready to play. It is more or less the same effect but with different mechanism.

Playing back captured videos on the ipad makes my app crash

I'm working on an app for the ipad 2 that lets the user record video of himself using the device front camera, and then play it back on a video player. I have the overall functionality working, but sometimes, just sometimes my app crashes when I load the view where the video will be played back, because of this:
'CALayerInvalidGeometry', reason: 'CALayer position contains NaN: [nan 11.5]'
I have noticed that the app crashes mostly, but not exclusively, when the recorded clip playing is less than around 15 seconds long.
Anyone have an idea whats going on?
Heres the code that takes care of recording:
-(void)record{
AVCaptureMovieFileOutput *output = [[AVCaptureMovieFileOutput alloc]init];
NSMutableString *videoURL;
if(isRecording){
//here i do some stuff to generate a random system path
[session addOutput:output];
AVCaptureConnection *videoConnection;
[session beginConfiguration];
for ( AVCaptureConnection *connection in [output connections] ) {
for ( AVCaptureInputPort *port in [connection inputPorts] ) {
if ( [[port mediaType] isEqual:AVMediaTypeVideo] ) {
videoConnection = connection;
}
}
}
if([videoConnection isVideoOrientationSupported]){
[videoConnection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
videoConnection.videoMirrored = true;
}
[session commitConfiguration];
[session startRunning];
NSURL *vidURL = [[NSURL alloc]initFileURLWithPath:videoURL];
[output startRecordingToOutputFileURL:vidURL recordingDelegate:self];
NSLog(#"Recording started in %#", videoURL);
[rootRep addObject:videoURL];
[vidURL release];
[videoURL release];
}else{
isRecording = false;
[output stopRecording];
[session removeOutput:output];
[output release];
NSLog(#"Recording stopped");
[recBut setImage:[UIImage imageNamed:#"rec.png"] forState:UIControlStateNormal];
}
}
EDIT: I've implemented a method to analize all the captured videos and delete the faulty ones, my app is stable again, but I still wonder why some videos don't get created well.
I don't know why exactly the camera doesn't capture good video files every time, but I'll show you how you can safely test whether the file works or not. You have to create an AVAsset with the file path you want to test, the AVAsset has a property called 'playable', a bool, then you can cycle an array with the addresses of your captured videos asking if the current asset is playable and deleting the corrupted files.

didSelectRowAtIndexPath - access tapCount or similar

I'd like to control an instance of AVAudioPlayer from a UITableView item in a didSelectRowAtIndexPath instance.
First touch of the row item triggers 'play' of the AVAudioPlayer. Second touch of the row item triggers 'stop' of the AVAudioPlayer.
I can make the 'play' work but can't get the 'stop' to work. Also, subsequent touches of the row item starts another thread of the audio in the background.
What's the best way to ensure 1 tap starts the audio and a 2nd tap stops it?
Code samples - this method preps audio file and AVAudioPlayer for use:
- (void)makeReadyAudio {
NSString *path = [[NSBundle mainBundle] pathForResource:#"Murderers" ofType:#"mp3"];
NSURL *url = [NSURL fileURLWithPath:path];
NSError *error;
musicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
[musicPlayer prepareToPlay];
}
This block will start & stop the AVAudioPlayer within a case statement in the didSelectRowAtIndexPath section:
case 7: {
//touch to start audio sound playing, touch again to stop playing
[self makeReadyAudio];
if ([musicPlayer isPlaying]) {
[musicPlayer stop];
NSLog(#"musicPlayer tested to be playing, so stop it.");
} else {
[musicPlayer play];
NSLog(#"musicPlayer tested to be *not* playing, so play it.");
}
break;
}
How about:
- (void) didSelectRowAtIndexPath:
{
if (player.isPlaying) {
[player stop];
} else {
[player start];
}
}
In other words, keep the player around and see what it is doing.
The problem is that you call makeReadyAudio every single time the row is tapped. You need to check if you have already done that. For example by doing something like:
if (musicPlayer == nil) {
[self makeReadyAudio];
}

AVaudioPlayer regarding song overlapping

I have using AVAudioPlayer in my app but the problem is that when i play the song from list it will played while before stopping the song i am again goes to song list then select another song then both song will start playing simultaneously so please tell me the code which will terminate my first song.
I use flag variable for initializing the song file---
fileURL = [[NSURL alloc] initFileURLWithPath: [[NSBundle mainBundle] pathForResource:#" Om Namo" ofType:#"aac"]];
self._player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
if (self._player)
{
_fileName.text = [NSString stringWithFormat: #"%# (%d ch.)", [[self._player.url relativePath] lastPathComponent], self._player.numberOfChannels, nil];
[self updateViewForPlayerInfo];
[self updateViewForPlayerState];
}
[fileURL release];
break;
Play and push function---
- (void)startPlayback
{
if ([self._player play])
{
//[self stop];
[self updateViewForPlayerState];
self._player.delegate = self;
}
else
NSLog(#"Could not play %#\n", self._player.url);
}
- (void)pausePlayback
{
[self._player pause];
[self updateViewForPlayerState];
}
I also make stop function---
-(void)stop
{
[_player release];
self._player=nil;
}
Please help me .
Is correct my stop function.
and where i put our stop function or any other solution to fix this problem...
I can think of 3 things :
1) You can make code look like code by putting 4 spaces infront of each line. The reason you've gt no answers so far is probably because your question is almost unreadable ;)
2) In your stop method you might want to try this :
- (void) stop {
[_player stop];
self._player = nil;
}
3) Apple highly discourage using an underscore in front of member names. I've never run into problems but I'd rename _player to player.