iOS 5 - AVAPlayer not working anymore - ios

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!

Related

Playing a video recorded with AVFoundation

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.

averagePowerForChannel working with iOSimulator but don't on iPad

In my small application for iOS 8.0 I'm measuring microphone's input level and if it's greater than -40 dB I'm changing the image in UIImageView and playing mp3-file. It works great in iOSimulator but doesn't work on iPad iOS 8.0. (microphone is accessible for app). What's the issue?
Thanks.
// ViewController.m
#import "ViewController.h"
#import "AVFoundation/AVAudioPlayer.h"
#import <AVFoundation/AVFoundation.h>
#interface ViewController ()
#end
#implementation ViewController
NSTimer *meterTimer;
AVAudioRecorder *recorder;
AVAudioPlayer *player;
NSString *audioFilePath; NSURL *pathAsURL; // for audioplayer
- (void)viewDidLoad
{ [super viewDidLoad];
meterTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 // sets timer interrupt
target:self selector:#selector(timerArrived) userInfo:nil repeats:YES];
NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat: 44100.0],AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleLossless],AVFormatIDKey,
[NSNumber numberWithInt: 1],AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax],AVEncoderAudioQualityKey,nil];
NSError *error;
recorder = [[AVAudioRecorder alloc] initWithURL:[NSURL URLWithString:[NSTemporaryDirectory()
stringByAppendingPathComponent:#"tmp.caf"]] settings:settings error:&error];
[recorder prepareToRecord];
recorder.meteringEnabled = YES;
[recorder record];
}
- (void)timerArrived // called by timer
{ int intPower; float fValue;
[recorder updateMeters]; // check input level
fValue = [recorder averagePowerForChannel:0];
intPower = roundf(fValue);
if (intPower > -40)
{ _microphImage.image = [UIImage imageNamed:#"Microphone2.png"];
audioFilePath = [[NSBundle mainBundle] pathForResource:#"aga" ofType:#"mp3"];
pathAsURL = [[NSURL alloc] initFileURLWithPath:audioFilePath]; NSError *error;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:pathAsURL error:&error];
[player play];
}
else {_microphImage.image = [UIImage imageNamed:#"Microphone.png"];}
}
- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];}
#end

How to stop AVPlayer Ios and remove the periodicTimeObserverForInterval

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.

Play a song in iOS

I was trying to play a song in iOS, but it gives me an error message.
HEADER FILE .h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface PRPViewController : UIViewController{
AVAudioPlayer *audioPlayer;
IBOutlet UIButton *start;
}
-(IBAction)play;
#end
IMPLEMENTATION FILE .m
NSURL *url = [NSURL fileURLWithPath:
[NSString stringWithFormat:#"%#/bobmarley.mp3",
[[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsofURL:url error:&error];
audioPlayer.numberOfLoops = 0;
[audioPlayer play];
but it says
No visible #interface for AVAudioPlayer declares the selector 'initWithContentsofUrl:error:'
What should I do?
You should capitalize the "O" in Of. In Objective-C, spelling counts, including capitalization. initWithContentsofURL and initWithContentsOfURL are two different things.
(By the way, this is a very good reason for using autocompletion as much as possible. The autocompletion mechanism knows much better than you do how to spell the names of the declared methods!)
You should check if the file is available on your system with the method initWithContentsOfURL, yours is written wrong. Otherwise the app can crash. I created a class which handles everything for me:
#implementation AudioPlayer{
AVAudioPlayer *_sound;
NSURL *_soundURL;
NSString *_receivedValue;
float _volumeSpecific;
}
- (id)initWithAudioFile:(NSString *)fileName andExtension:(NSString *)extension{
self = [super init];
if( self ){
_receivedValue = fileName;
_soundURL = [NSURL fileURLWithPath:
[[NSBundle mainBundle] pathForResource:fileName
ofType:extension]];
if([[NSFileManager defaultManager] fileExistsAtPath:[_soundURL path]]){
_sound = [[AVAudioPlayer alloc] initWithContentsOfURL:_soundURL
error:nil];
}
}
return self;
}
- (void)playEndless{
if( [[NSUserDefaults standardUserDefaults] boolForKey:kSound] ){
_sound.numberOfLoops = -1;
[_sound play];
}
}
- (void)setVolume:(float)myVolume{
_volumeSpecific = myVolume;
[_sound setVolume:myVolume];
}
- (void)play{
if( _sound == nil ){
NSLog(#"No AudioPlayer available %#", self);
}
if( [[NSUserDefaults standardUserDefaults] boolForKey:kSound] ){
if( _volumeSpecific ){
[_sound setVolume:_volumeSpecific];
}
[_sound play];
}
}
- (NSString *)description{
return [NSString stringWithFormat:#"Received: %#, Player: %#, URL: %#",
_receivedValue, _sound, _soundURL];
}

How can I use this code to play more sounds?

//Action to play Audio//
-(IBAction)playAudio:(id)sender {
[self.loopPlayer play];
}
//Action to stop Audio//
-(IBAction)stopAudio:(id)sender {
if (self.loopPlayer.isPlaying) {
[self.loopPlayer stop];
self.loopPlayer.currentTime = 0;
self.loopPlayer.numberOfLoops = -1;
[self.loopPlayer prepareToPlay];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//Code that gets audio file "trap synth"//
NSURL* audioFileURL = [[NSBundle mainBundle] URLForResource:#"trapsynth" withExtension:#"wav"];
self.loopPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:nil];
}
This is the code i'm using with one button to play the sound when the button is tapped and stop the sound when the button is released. How would I go about adding more sounds to more buttons? I want to have more buttons that play and stop different sounds just like this.
property (nonatomic, strong) AVAudioPlayer *loopPlayer;
This code is also in my ViewController.h file
Ok although the answer provided by Miro is on the write track the code example given has issues.
Should be this in viewDidLoad -
- (void)viewDidLoad {
[super viewDidLoad];
NSURL* audioFileURL1 = [[NSBundle mainBundle] URLForResource:#"trapsynth" withExtension:#"wav"];
self.loopPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL1 error:nil];
NSURL* audioFileURL2 = [[NSBundle mainBundle] URLForResource:#"other_audio_file" withExtension:#"wav"];
self.loopPlayer2 = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL2 error:nil];
}
also stopAudio: method should be this
-(IBAction)stopAudio:(id)sender {
if (self.loopPlayer1.isPlaying && (sender.tag == 1)) {
[self.loopPlayer1 stop];
self.loopPlayer1.currentTime = 0;
self.loopPlayer1.numberOfLoops = -1;
[self.loopPlayer1 prepareToPlay];
}
if (self.loopPlayer2.isPlaying && (sender.tag == 2)) {
[self.loopPlayer2 stop];
self.loopPlayer2.currentTime = 0;
self.loopPlayer2.numberOfLoops = -1;
[self.loopPlayer2 prepareToPlay];
}
}
And finally for playAudio:
-(IBAction)playAudio:(id)sender {
if([sender tag] == 1){
[self.loopPlayer1 play];
}
if([sender tag] == 2){
[self.loopPlayer2 play];
}
}
If you want to play different sounds at the same time you should look into creating separate AVAudioPlayers - if you create a different one for each sound, then you can easily control (play/stop) each of them separately with a specific button.
On the simplest level, you could do something like this, which allows you to use the same button handlers for all your audio. The playAudio checks the tag of the Play button you press (be sure to set the tag value in IB, to 1,2,etc). There really only need be one Stop button.
You could enhance this in many ways, like attempting to reuse the AVAudioPlayer somehow, and loading the audio on the fly instead of all at the beginning. Or storing your audio file info in an array, creating an array of AVAudioPlayers for management, etc. But this is a start.
-(IBAction)playAudio:(id)sender {
// first, stop any already playing audio
[self stopAudio:sender];
if([sender tag] == 1){
[self.loopPlayer1 play];
} else if([sender tag] == 2){
[self.loopPlayer2 play];
}
}
-(IBAction)stopAudio:(id)sender {
if (self.loopPlayer1.isPlaying) {
[self.loopPlayer1 stop];
self.loopPlayer1.currentTime = 0;
self.loopPlayer1.numberOfLoops = -1;
[self.loopPlayer1 prepareToPlay];
} else if (self.loopPlayer2.isPlaying) {
[self.loopPlayer2 stop];
self.loopPlayer2.currentTime = 0;
self.loopPlayer2.numberOfLoops = -1;
[self.loopPlayer2 prepareToPlay];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
NSURL* audioFileURL1 = [[NSBundle mainBundle] URLForResource:#"trapsynth" withExtension:#"wav"];
self.loopPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:nil];
NSURL* audioFileURL2 = [[NSBundle mainBundle] URLForResource:#"trapsynth" withExtension:#"wav"];
self.loopPlayer2 = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:nil];
}
AND, in the .h file;
property (nonatomic, strong) AVAudioPlayer *loopPlayer1;
property (nonatomic, strong) AVAudioPlayer *loopPlayer2;

Resources