I have an avaudioplayer that loads a sound file and loops. The problem is that everytime the file loops, there is a pause/delay which makes music sound bad.
I found this question which had a similar issue, but is for 2 players. I have a single player that has a 1 second pause at each loop: AVAudioPlayer eliminating one second pause between sound files
Here is what my audio player setup looks like:
#interface HomeViewController ()
#property (strong, nonatomic) DataController *dataController;
#end
#implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.dataController = [DataController sharedInstance];
[self createAndStartAudioPlayer];
}
- (void) viewWillAppear:(BOOL)animated{
[self.dataController.player play];
}
- (void) createAndStartAudioPlayer {
NSTimeInterval shortStartDelay = 0.01; // seconds
NSTimeInterval now = self.dataController.player.deviceCurrentTime;
[self.dataController.player playAtTime: now + shortStartDelay];
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"jingle-loop" ofType:#"mp3"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
self.dataController.player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL fileTypeHint:AVFileTypeMPEGLayer3 error:nil];
self.dataController.player.numberOfLoops = -1; //infinite
[self.dataController.player prepareToPlay];
}
How do I stop this 1 second pause and play the loop like it is a continuous song?
Mr. T in the comments was in fact right. The solution was to fix the white space that is added automatically by garage band.
Related
I have a series of sound files ranging from 0.3s to 3.0s.
When trying to play with AVAudioPlayer, I get nothing, unless I add a sleep(4) call after to ensure the sound can play. Really weird.
Apart from that, no errors with the error param that gets passed in, and the [player play] returns YES every time. The delegate methods are not called, though, oddly enough.
Here's my code.
(.m file)
#interface MyClass ()
#property (nonatomic, strong) AVAudioPlayer *soundPlayer;
#end
#implementation SLInspireKit
//view did load here
- (void)playRandomSound {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
NSError *error;
NSString *musicFileName = [NSString stringWithFormat:#"my-sound-%d.aiff", arc4random_uniform(8)];
NSString *path = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], musicFileName];
NSURL *soundUrl = [NSURL fileURLWithPath:path];
self.soundPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:&error];
self.soundPlayer.delegate = self;
[self.soundPlayer setVolume:1.0];
[self.soundPlayer prepareToPlay];
if ([self.soundPlayer play]){
NSLog(#"Played fine");
} else {
NSLog(#"Did not play fine");
}
sleep(1); //uncomment this out, and nothing. Set to 1, first second of each sound, 2, 2 seconds and so on.
}
Any ideas? I don't think it's an ARC thing, but it could be :/ I have to initiate each time because the sound changes each time.
Quick summary of the comments:
Turns out the sleep(n) was needed since the entire class containing the AVAudioPlayer was deallocated.
Retaining the class fixes the issue.
Cheers!
I'm struggling to follow Apple's documentation regarding playing back a small .wav file using the AVAudioPlayer class. I'm also not sure what toolboxes I need for basic audio playback. So far I have imported:
AVFoundation.framework
CoreAudio.framework
AudioToolbox.framework
Here is my code .h and .m:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController <AVAudioPlayerDelegate>
- (IBAction)playAudio:(id)sender;
#end
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)playAudio:(id)sender {
NSLog(#"Button was Pressed");
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"XF_Loop_028" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
// create new audio player
AVAudioPlayer *myPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:fileURL error:nil];
[myPlayer play];
}
#end
I don't seem to have any errors. However, I am not getting any audio.
You're having an ARC issue here. myPlayer is being cleaned up when it's out of scope. Create a strong property, assign the AVAudioPlayer and you're probably all set!
#property(nonatomic, strong) AVAudioPlayer *myPlayer;
...
// create new audio player
self.myPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:fileURL error:nil];
[self.myPlayer play];
Is the filePath coming back with a valid value? Is the fileURL coming back with a valid value? Also, you should use the error parameter of AVAudioPlayer initWithContentsOfURL. If you use it it will likely tell you EXACTLY what the problem is.
Make sure you check for errors and non-valid values in your code. Checking for nil filepath and fileURL is the first step. Checking the error parameter is next.
Hope this helps.
What I do is create an entire miniature class just for this purpose. That way I have an object that I can retain and which itself retains the audio player.
- (void) play: (NSString*) path {
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: path];
NSError* err = nil;
AVAudioPlayer *newPlayer =
[[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error: &err];
// error-checking omitted
self.player = newPlayer; // retain policy
[self.player prepareToPlay];
[self.player setDelegate: self];
[self.player play];
}
I'm very new to programming in Xcode and have already hit a brick wall.
What I am trying to do is to get a sound to play if there is sufficient acceleration detected.
Can someone please advise me on what I am doing wrong? (I suspect its everything).
CODE:
#import "LZDViewController.h"
#interface LZDViewController ()
#end
#implementation LZDViewController
- (void)viewDidLoad;{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CMAccelerometerData *accelerometerData = self.motionManager.accelerometerData;
if (fabsf(accelerometerData.acceleration.x) > 1.2
|| fabsf(accelerometerData.acceleration.y) > 1.2
|| fabsf(accelerometerData.acceleration.z) > 1.2)
{
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/audio.wav", [[NSBundle mainBundle] resourcePath]]];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
audioPlayer.numberOfLoops = 0;
[audioPlayer play];
}
}
#end
EDIT:
Here is the code for my .h
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
#import <AVFoundation/AVFoundation.h>
#interface LZDViewController : UIViewController
//{AVAudioPlayer *audioPlayer;}
#property (strong, nonatomic) CMMotionManager *motionManager;
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
#end
I'm still not getting any joy at all, and I do apologise for the hand holding.
All I get is a white screen but the sound file never plays. I have imported the correct framework and can get the sound playing with just a button, but I really want to play with the accelerometer.
Thanks
Alright, the first thing you're going to want to do is define two properties in either your interface file, or in the interface extension at the top of your implementation file. These will be the motion manager, and the audio player.
#property (strong, nonatomic) CMMotionManager *motionManager;
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
Then we can get everything else done in viewDidLoad. The code below sets up a new operation queue for the motion manager to receive accelerometer events on, initializes the audio player and the motion manager, and then starts getting accelerometer updates at an interval of 30 times per second.
Finally, within the accelerometers update block, just use the same logic you already build to check the acceleration data and if it meets your requirements continue on to the next condition.
**I have configured this example to not start playing the sound again if the player is already playing
- (void)viewDidLoad
{
[super viewDidLoad];
NSOperationQueue *motionQueue = [NSOperationQueue new];
NSURL *url = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:#"audioFile" ofType:#"m4a"]];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[self.audioPlayer prepareToPlay];
self.motionManager = [CMMotionManager new];
[self.motionManager setAccelerometerUpdateInterval:1.0f/30.0f];
[self.motionManager startAccelerometerUpdatesToQueue:motionQueue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
if (fabsf(accelerometerData.acceleration.x) > 1.8 || fabsf(accelerometerData.acceleration.y) > 1.8 || fabsf(accelerometerData.acceleration.z) > 1.8) {
NSLog(#"%#",accelerometerData);
if (self.audioPlayer) {
if (!self.audioPlayer.isPlaying) {
[self.audioPlayer play];
}
}
}
}];
}
I am using AVAudioPlayer class to play audio but the problem is that when I play the audio I do not receive the callback in audioPlayerDidFinishPlaying when the file is finish playing and the file starts again even when the number of loops is set to 1
Here is the code.
.h
#interface AudioController : NSObject <AVAudioPlayerDelegate>
-(void) playBigGameTheme{
-(void) playMusic:(NSString*) fileName isLooping: (BOOL) isLooping;
#end
.m
#implementation AudioController{
AVAudioPlayer *audioPlayer;
}
-(void) playMusic:(NSString*) fileName isLooping: (BOOL) isLooping{
int loop = 1;
if(isLooping){
loop = -1;
}
[audioPlayer stop];
NSURL* soundUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:fileName ofType:#"mp3"]];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
audioPlayer.numberOfLoops = loop;
[audioPlayer setDelegate:self];
NSLog(#"===Number of loops : %d", audioPlayer.numberOfLoops);
[audioPlayer play];
}
- (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
NSLog(#"HERE");
}
-(void) playBigGameTheme{
[self playMusic:BGAME_MAIN_MUSIC_FILE_NAME isLooping:NO];
}
#end
when I call playBigGameTheme the audio starts playing correctly. but it restarts when the sound is finished and when the audio finishes the second time then only the audioPlayerDidFinishPlaying method is called.
I am stuck and do not know whats going wrong..
even when the number of loops is set to 1
That's exactly why. The name and the behavior of this property is a bit counter-intuitive, but if you had read the documentation, it would have been clear that it sets the number of repetitions, and not the number of playbacks. Quote from the docs:
The value of 0, which is the default, means to play the sound once.
So set it to 0 (or don't set it, zero is the default value) and it will play the audio file only once:
audioPlayer.numberOfLoops = 0;
I have 5 songs in my app that I would like to play one after the other with AVAudioPlayer.
Are there any examples of this? How can I accomplish this?
Any example code would be greatly appreciated!
Thanks!
AVQueuePlayer work for this situation.
AVQueuePlayer is a subclass of AVPlayer you use to play a number of items in sequence.
Instead of AVAudioPlayer you can use AVQueuePlayer which suits this use case better as suggested by Ken.
Here is a bit of code you can use:
#interface AVSound : NSObject
#property (nonatomic, retain) AVQueuePlayer* queuePlayer;
- (void)addToPlaylist:(NSString*)pathForResource ofType:(NSString*)ofType;
- (void)playQueue;
#end
#implementation AVSound
- (void)addToPlaylist:(NSString*)pathForResource ofType:(NSString*)ofType
{
// Path to the audio file
NSString *path = [[NSBundle mainBundle] pathForResource:pathForResource ofType:ofType];
// If we can access the file...
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
{
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:path]];
if (_queuePlayer == nil) {
_queuePlayer = [[AVQueuePlayer alloc] initWithPlayerItem:item];
}else{
[_queuePlayer insertItem:item afterItem:nil];
}
}
}
- (void)playQueue
{
[_queuePlayer play];
}
#end
Then to use it:
In your interface file:
#property (strong, nonatomic) AVSound *pageSound;
In your implementation file:
- (void)addAudio:(Book*)book pageNum:(int)pageNum
{
NSString *soundFileEven = [NSString stringWithFormat:#"%02d", pageNum-1];
NSString *soundPathEven = [NSString stringWithFormat:#"%#_%#", book.productId, soundFileEven];
NSString *soundFileOdd = [NSString stringWithFormat:#"%02d", pageNum];
NSString *soundPathOdd = [NSString stringWithFormat:#"%#_%#", book.productId, soundFileOdd];
if (_pageSound == nil) {
_pageSound = [[AVSound alloc]init];
_pageSound.player.volume = 0.5;
}
[_pageSound clearQueue];
[_pageSound addToPlaylist:soundPathEven ofType:#"mp3"];
[_pageSound addToPlaylist:soundPathOdd ofType:#"mp3"];
[_pageSound playQueue];
}
HTH
For every song you want to make make a single AVPlayer.
NSURL *url = [NSURL URLWithString:pathToYourFile];
AVPlayer *audioPlayer = [[AVPlayer alloc] initWithURL:url];
[audioPlayer play];
You can get a Notification when the player ends. Check AVPlayerItemDidPlayToEndTimeNotification when setting up the player:
audioPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[audioPlayer currentItem]];
this will prevent the player to pause at the end.
in the notification:
- (void)playerItemDidReachEnd:(NSNotification *)notification
{
// start your next song here
}
You can start your next song as soon as you get a notification that the current playing song is done. Maintain some counter which is persistent across selector calls. That way using counter % [songs count] will give you an infinite looping playlist :)
Don't forget un unregister the notification when releasing the player.
Unfortunately, AVAudioPlayer can only play one file. To play two files, you have to kill the first instance of the AVAudioPlayer and recreate it a second time (it can be initiated using - (id)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError). The problem with this approach is that there is a slight delay between when the first file finishes playing and when the second file starts playing. If you want to get rid of this delay you have to dig into Core Audio and come up with a much more complex solution.