I'm having a little difficulty stopping AVPlayer.
I have a method that records and plays music simultaneously. I'm using AVPlayer to play the music because I want to use the addPeriodicTimeObserverForInterval Function. I have it set up as follows:
- (IBAction) recordVoice:(id)sender {
if(!recorder.isRecording){
//set up the file name to record to
NSString *recordingLocation = [self createFileName];
recordingName = recordingLocation;
NSArray *pathComponents = [NSArray arrayWithObjects:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject],
recordingLocation, nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
recordingURL = outputFileURL;
// Setup audio session
session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker
error:nil];
// Define the recording settings to record as m4a
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
// initiate and prepare the recorder
recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:NULL];
recorder.delegate = self;
recorder.meteringEnabled = YES;
[recorder prepareToRecord];
[session setActive:YES error:nil];
[recorder record];
// find which song to play and initiate an AVPlayer to play it
NSString *playerLocation = self.TitleLabel.text;
NSString *path = [[NSBundle mainBundle] pathForResource:playerLocation ofType:#"m4a"];
player = [[AVPlayer alloc] initWithURL:[NSURL fileURLWithPath:path]];
lastTime = nil;
//check where the player is at and update the song lines accordingly
[player addPeriodicTimeObserverForInterval:CMTimeMake(3, 10) queue:NULL usingBlock:^(CMTime time){
NSTimeInterval seconds = CMTimeGetSeconds(time);
for (NSDictionary *item in robotR33) {
NSNumber *time = item[#"time"];
if ( seconds > [time doubleValue] && [time doubleValue] >= [lastTime doubleValue] ) {
lastTime = #(seconds);
NSString *str = item[#"line"];
[self nextLine:str];
};
}
}];
[player play];
[_recordButton setImage:[UIImage imageNamed:#"micRecording.gif"] forState:UIControlStateNormal];
}
else{
[recorder stop];
player = nil;
[session setActive:NO error:nil];
}
}
If the recorder is not recording I set up both a new recorder AVAudioRecorder and an AVPlayer. In the AVPlayer I set up an AddPeriodicTimeObserverForInterval which updates the UI based on the position of the player.
If the recorder is recording I stop the recorder and I set the player to nil. This stops the audio from playing but I notice that the addPeriodicTimeObserverInterval is still running because the UI continues to update. Should I destroy the AVPlayer altogether and if so how should I do that? Many thanks in advance.
Also as an aside, I have a warning inside the addPeriodicTimeObserverForInterval block. I am looping over an Array called robotR33. Xcode tells me that 'Capturing self strongly in this block is likely to lead to a retain cycle". Could this be part of my problem?
When finished playing the observer needs to be removed from the player.
Adding [player removeTimeObserver:self.timeObserver] works.
Related
I'm new to objective c and try to code a programm which can play a sound with an effect on it, like a reverb or delay. Unfortunately I'm only able to play the sound, but without the effect. I'm stuck for 3 days now and can't find a solution. Does anyone can tell what I'm doing wrong?
I tried this code, but with it there's no sound at all:
- (void)viewDidLoad {
[super viewDidLoad];
AVAudioEngine *engine = [AVAudioEngine new];
AVAudioPlayerNode *playerA = [AVAudioPlayerNode new];
playerA.volume = 0.5;
NSURL *Mia1url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"MIA1" ofType:#"m4a"]];
AVAudioFile *MIA1 = [[AVAudioFile alloc] initForReading:Mia1url error:nil];
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:MIA1.processingFormat frameCapacity:1024];
[MIA1 readIntoBuffer:buffer error:nil];
AVAudioUnitDelay *delay = [AVAudioUnitDelay new];
delay.delayTime = 100;
delay.wetDryMix = 90;
[engine attachNode:playerA];
[engine attachNode:delay];
[engine connect: playerA to: delay format:MIA1.processingFormat];
[engine connect: delay to: engine.mainMixerNode format: MIA1.processingFormat];
[playerA scheduleBuffer:buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[engine prepare];
[engine startAndReturnError:nil];
[playerA play];
}
after that I tried this code but the sound is only coming without the effect:
- (void)viewDidLoad {
[super viewDidLoad];
AVAudioEngine *engine = [AVAudioEngine new];
AVAudioPlayerNode *playerA = [AVAudioPlayerNode new];
playerA.volume = 0.5;
NSURL *Mia1url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"MIA1" ofType:#"m4a"]];
AVAudioFile *MIA1 = [[AVAudioFile alloc] initForReading:Mia1url error:nil];
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:MIA1.processingFormat frameCapacity:1024];
[MIA1 readIntoBuffer:buffer error:nil];
AVAudioUnitDelay *delay = [AVAudioUnitDelay new];
delay.delayTime = 100;
delay.wetDryMix = 90;
[engine attachNode:playerA];
[engine attachNode:delay];
[engine connect: playerA to: delay format:MIA1.processingFormat];
[engine connect: delay to: engine.mainMixerNode format: MIA1.processingFormat];
[playerA scheduleBuffer:buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[engine prepare];
[engine startAndReturnError:nil];
//change
self.playerA = [[AVAudioPlayer alloc] initWithContentsOfURL:Mia1url error:nil];
//change from [playerA play] to:
[self.playerA play];
}
You forgot to instantiate your objects:
//AVAudioEngine *engine; // no instance exists.
AVAudioEngine *engine = [AVAudioEngine new];
AVAudioPlayerNode *playerA = [AVAudioPlayerNode new];
...
AVAudioUnitDelay *delay = [AVAudioUnitDelay new];
...
Also, you have to initialize playerA only once and before using it.
For digging deeper you might want to checkout Apples code samples.
I would like to use the same button to start and stop recording. I would like to use another button to play back the recording. Here is what I have:
- (IBAction)recordVideo:(id)sender {
if(!self.movieOutput.isRecording) {
NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"output.mp4"];
NSFileManager *manager = [[NSFileManager alloc] init];
if ([manager fileExistsAtPath:outputPath])
{
[manager removeItemAtPath:outputPath error:nil];
}
[self.movieOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputPath]
recordingDelegate:self];
Float64 maximumVideoLength = 5; //Whatever value you wish to set as the maximum, in seconds
int32_t prefferedTimeScale = 30; //Frames per second
CMTime maxDuration = CMTimeMakeWithSeconds(maximumVideoLength, prefferedTimeScale);
self.movieFileOutput.maxRecordedDuration = maxDuration;
self.movieFileOutput.minFreeDiskSpaceLimit = 1024*1024;
}
else
{
[self.movieOutput stopRecording];
}
- (void) captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections error:(NSError *)error
{
NSLog(#"Recording to file ended");
[_captureSession stopRunning];
}
Then to play:
- (IBAction)playVideo:(id)sender {
NSURL *fileURL = [NSURL URLWithString:#"outputPath"];
self.avPlayer = [AVPlayer playerWithURL:fileURL];
AVPlayerLayer *movieLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
self.avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
movieLayer.frame = self.cameraView.bounds;
movieLayer.videoGravity = AVLayerVideoGravityResize;
[self.cameraView.layer addSublayer:movieLayer];
[_avPlayer play];
When I run and press the playback button I get no errors and I see no avplayer.
You are recording and saving file in temporary directory
NSString *outputPath = [NSTemporaryDirectory()stringByAppendingPathComponent:#"output.mp4"];
and trying to play from bundle path.Use the same path to play recording also.
First, check Is your video is recorded and saved properly or not.From your code, the video is saved Temporary directory.Check the video at the Path.If it is exist or not.
NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"output.mp4"];
NSLog(#"%#", outputPath);
In your code, you are trying to play video from outPutPath, which is not defined and initialize in your code.If you have defined outPutPath as property or variable, then you need to initialise _outPutPath, with the same path you save the video.
NSString *outputPath = [NSTemporaryDirectory()stringByAppendingPathComponent:#"output.mp4"];
_outputPath = outputPath;
To Play Video Try this,
if ([[NSFileManager defaultManager]fileExistsAtPath: _ouputPath]) {
AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:_ouputPath]];
_avPlayer = [[AVPlayer alloc]initWithPlayerItem:[[AVPlayerItem alloc]initWithAsset:asset]];
AVPlayerLayer *movieLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
movieLayer.frame = self.cameraView.bounds;
[self.cameraView.layer addSublayer:movieLayer];
[self.avPlayer play];
}
Replace your this line :
NSURL *url = [NSURL fileURLWithPath:filePath];
with this:
NSURL *url=[NSURL URLWithString:filePath];
& then try.
I want to record a short clip (30 sec) that should be automatically stopped after 30 sec. I started the camera using AVCAPTURESESSION and now I want to start video recording that should be automatically.
Here's my code:
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetHigh;
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
[session addInput:input];
AVCaptureVideoPreviewLayer *newCaptureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
newCaptureVideoPreviewLayer.frame = self.view.bounds;
[self.view.layer addSublayer:newCaptureVideoPreviewLayer];
[session startRunning];
How do I record video of 30 sec.
My Try:
NSString *documentsDirPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSURL *documentsDirUrl = [NSURL fileURLWithPath:documentsDirPath isDirectory:YES];
NSURL *url = [NSURL URLWithString:#"out.mp4" relativeToURL:documentsDirUrl];
[self.movieFileOutput startRecordingToOutputFileURL:url recordingDelegate:self];
- (AVCaptureMovieFileOutput *)movieFileOutput {
AVCaptureMovieFileOutput *_movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
if (!_movieFileOutput) {
_movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
Float64 TotalSeconds = 30; //Total seconds
int32_t preferredTimeScale = 30; //Frames per second
CMTime maxDuration = CMTimeMakeWithSeconds(TotalSeconds, preferredTimeScale);
_movieFileOutput.maxRecordedDuration = maxDuration;
_movieFileOutput.minFreeDiskSpaceLimit = 1024 * 1024;
}
return _movieFileOutput;
}
But it gives me error, [AVCaptureMovieFileOutput startRecordingToOutputFileURL:recordingDelegate:] - no active/enabled connections.'
How can I record a video clip of 30 sec.
I find the reason of this error. check your session's "setSessionPreset" setting, photo's resolution setting is different from video, for iPhone5, video resolution of the back camera is 1920*1080, the front camere is 1280*720, and photo's max resolution is 3264*2488, so if you set error resolution to video, the connect will not be actived.
I'm creating an iOS application that starts recording audio when a prompt occurs rather than when a button is pressed. Because of this, I've put the AVAudioRecorder in a scheduler object that is called from it's own class. However, when I assign the scheduler as the delegate for the recorder, I get the warning Assigning to 'id<AVAudioRecorderDelegate>' from incompatible type 'LWPScheduler *__strong'. Here is the implementation for the scheduler:
#implementation LWPScheduler
#synthesize tempo;
#synthesize userName;
+(LWPScheduler *)masterScheduler{
static LWPScheduler *masterScheduler = nil;
if (masterScheduler == nil)
{
masterScheduler = [[self alloc] init];
}
return masterScheduler;
}
- (id)init
{
self = [super init];
if (self) {
self.tempo = 120;
self.userName = #"Tim Burland";
//recording init
// Set the audio file
NSArray *pathComponents = [NSArray arrayWithObjects:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
#"MyAudioMemo.m4a",
nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
//audio session
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
//recorder settings
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];
// Initiate and prepare the recorder
_recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:NULL];
_recorder.delegate = self; //Incompatible Type Warning Here
_recorder.meteringEnabled = YES;
[_recorder prepareToRecord];
}
return self;
}
#end
My question is whether I have to migrate the audio handling to the controller for the view the recorder would be contained in. Thanks for your help!
On your interface (or private class extension) tell the compiler that your are conforming to the protocol the delegate expects. For e.g.:
#interface LWPScheduler : NSObject <AVAudioRecorderDelegate>
// ...
#end
The protocol defines required and/or optional methods you may have to implement (Xcode will warn you about the required one). After telling the interface the class confirms to the protocol, _recorder.delegate = self; will just work.
I've a bit of code which was working fine with iOS 4.3. I had a look on the Internet, I found others having the same problem without answer which worked for me. I think that I can record something but I cannot play it. Here is my code:
DetailViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreAudio/CoreAudioTypes.h>
#import <AudioToolbox/AudioServices.h>
#interface DetailViewController : UIViewController <UISplitViewControllerDelegate, AVAudioRecorderDelegate> {
id detailItem;
UILabel *detailDescriptionLabel;
IBOutlet UIButton *btnStart;
IBOutlet UIButton *btnPlay;
//Variables setup for access in the class:
NSURL * recordedTmpFile;
AVAudioRecorder * recorder;
BOOL toggle;
}
// Needed properties
#property (nonatomic, retain) IBOutlet UIButton *btnStart;
#property (nonatomic, retain) IBOutlet UIButton *btnPlay;
#property (strong, nonatomic) id detailItem;
#property (strong, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
-(IBAction) start_button_pressed;
-(IBAction) play_button_pressed;
#end
DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
toggle = YES;
btnPlay.hidden = YES;
NSError *error;
// Create the Audio Session
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
// Set up the type of session
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
// Activate the session.
[audioSession setActive:YES error:&error];
[self configureView];
}
-(IBAction) start_button_pressed{
if (toggle) {
toggle = NO;
[btnStart setTitle:#"Press to stop recording" forState:UIControlStateNormal];
btnPlay.enabled = toggle;
btnPlay.hidden = !toggle;
NSError *error;
NSMutableDictionary *recordSettings = [[NSMutableDictionary alloc] init];
[recordSettings setValue:[NSNumber numberWithInt:kAudioFormatAppleIMA4] forKey:AVFormatIDKey];
[recordSettings setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSettings setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
// Create a temporary files to save the recording.
recordedTmpFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: #"%.0f.%#", [NSDate timeIntervalSinceReferenceDate] * 1000.0, #"caf"]]];
NSLog(#"The temporary file used is: %#", recordedTmpFile);
recorder = [[AVAudioRecorder alloc] initWithURL:recordedTmpFile settings:recordSettings error:&error];
[recorder setDelegate:self];
[recorder prepareToRecord];
[recorder record];
}
else {
toggle = YES;
[btnStart setTitle:#"Start recording" forState:UIControlStateNormal];
btnPlay.hidden = !toggle;
btnPlay.enabled = toggle;
NSLog(#"Recording stopped and saved in file: %#", recordedTmpFile);
[recorder stop];
}
}
-(IBAction) play_button_pressed{
NSError *error;
AVAudioPlayer * avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:recordedTmpFile error:&error];
if (!error)
{
[avPlayer prepareToPlay];
[avPlayer play];
NSLog(#"File is playing");
}
}
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player
successfully: (BOOL) flag {
NSLog (#"audioPlayerDidFinishPlaying:successfully:");
}
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *) aRecorder successfully: (BOOL)flag
{
NSLog (#"audioRecorderDidFinishRecording:successfully:");
}
Here is the of my program running:
2011-11-25 11:58:02.005 Bluetooth1[897:707] The temporary file used is: file://localhost/private/var/mobile/Applications/D81023F8-C53D-4AC4-B1F7-14D66EB4844A/tmp/343915082005.caf
2011-11-25 11:58:05.956 Bluetooth1[897:707] Recording stopped and saved in file: file://localhost/private/var/mobile/Applications/D81023F8-C53D-4AC4-B1F7-14D66EB4844A/tmp/343915082005.caf
2011-11-25 11:58:05.998 Bluetooth1[897:707] audioRecorderDidFinishRecording:successfully:
2011-11-25 11:58:11.785 Bluetooth1[897:707] File is playing
For some reason, the function audioPlayerDidFinishPlaying is never called. However it seems that something has been recorded. Right now I do not know which part is not working but I guess this has something to do with AVAudioPlayer.
[EDIT] It's getting weirder and weirder. I wanted to make sure that something was recorded so I look for taking the duration of the record. Here is the new play function:
-(IBAction) play_button_pressed{
NSError *error;
AVAudioPlayer * avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: recordedTmpFile error:&error];
if (!error)
{
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:recordedTmpFile options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
[avPlayer prepareToPlay];
[avPlayer play];
NSString *something = [NSString stringWithFormat:#"%f",audioDurationSeconds];
NSLog(#"File is playing: %#", something);
}
else
{
NSLog(#"Error playing.");
}
}
Now, the length of the record is recorded and it make sense (if I record for 10s it shows something around 10s). However, when I put these lines of code for the first time I forgot to do the conversion float to NSString. So it crashed... and the app play the sound... After different tests I can conclude that my app can record and play a sound but is as to crash to play the recorded sound. I've no idea what can be the problem. I found that AVPlayer is asynchronous, is their something to do with that? I'm completely lost...
Replace the urlpath with the following code:
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filepath = [documentsDirectory stringByAppendingPathComponent:#"urfile.xxx"];
NSURL *url = [NSURL fileURLWithPath:filepath];
Try the solution here:
Recording and playback
OK, that is not really cool to answer you own questions. Moreover when the answer is not clean but it is working... In order to play what I have recorded I have used the following block of code:
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:recordedTmpFile options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
[avPlayer prepareToPlay];
[avPlayer play];
// Block for audioDurationSeconds seconds
[NSThread sleepForTimeInterval:audioDurationSeconds];
I am calculating the length of the recorded file and I am waiting for this amount of time... it is dirty but it is doing the trick. Plus, if it launched in another thread it will not block the application.
I anyone has something I would gladly take it!