iOS AVPlayer streaming music issues - iphone

I'm having some really weird issues with AVPlayer. I have a very simple music player (which streams sample music from the iTunes store) working with it correctly on the simulator (both iPhone and iPad with iOS 5.1) but it behaves abnormally on a real device.
On an iPad 2 with iOS 5.1.1 it plays correctly even if I connect my headphones to the device. But as soon as I disconnect them it won't play a sound through the speakers (even though if I connect them again I can listen to the song).
On an iPhone 4 with iOS 5.1 installed it doesn't seem to play at all through the speakers but I can listen to the music through my headphones. Although it doesn't seem to be playing through the speakers I can hear now and then for a very brief moment the music playing (and can confirm that it in fact is playing because my UI responds accordingly) although it seems to be random.
I'm using AVPlayer since it seems to fit the requirements. Should I use another library? Do I need to manually route the sound? Why am I having these types of problems? And am I using Audio Sessions correctly?
Media.h:
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#define NOT_PLAYING -1
#protocol MediaDelegate <NSObject>
#optional
- (void) didEndPlayingAtIndex:(int) index;
- (void) startedToPlayAtIndex:(int) index;
- (void) stoppedToPlayAtIndex:(int) index;
- (void) startedToPlayAtIndex:(int) to fromIndex:(int) from;
- (void) pausedAtIndex:(int) index;
#end
#interface Media : NSObject <AVAudioSessionDelegate>
#property (nonatomic, assign) int currentItem;
#property (nonatomic, strong) NSURL *url;
#property (nonatomic, strong) AVPlayer *player;
#property (nonatomic, assign) id<MediaDelegate> delegate;
- (void) toggle:(int) index;
- (void) stop;
#end
Media.m:
#import "Media.h"
#implementation Media
#synthesize currentItem;
#synthesize player;
#synthesize delegate;
#synthesize url;
- (id)init
{
self = [super init];
if (self) {
NSError *activationError = nil;
NSError *setCategoryError = nil;
AVAudioSession *session = [AVAudioSession sharedInstance];
session.delegate = self;
[session setActive:YES error:&activationError];
[session setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
if(activationError || setCategoryError)
NSLog(#"ERROR: %#, %#",activationError,setCategoryError);
self.currentItem = NOT_PLAYING;
}
return self;
}
- (void)dealloc{
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive:NO error:&activationError];
if(activationError)
NSLog(#"ERROR: %#",activationError);
[self.player removeObserver:self forKeyPath:#"status"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
switch (player.status) {
case AVPlayerItemStatusReadyToPlay:
[self.player play];
if([self.delegate respondsToSelector:#selector(startedToPlayAtIndex:)])
[self.delegate startedToPlayAtIndex:self.currentItem];
break;
default:
break;
}
}
- (void) toggle:(int) index{
if (self.currentItem == NOT_PLAYING) {
self.player = [AVPlayer playerWithPlayerItem:[AVPlayerItem playerItemWithURL:self.url]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.player];
self.currentItem = index;
[self.player addObserver:self forKeyPath:#"status" options:0 context:nil];
}
else {
if (self.currentItem == index) {
[self.player pause];
if([self.delegate respondsToSelector:#selector(stoppedToPlayAtIndex:)])
[self.delegate stoppedToPlayAtIndex:index];
self.currentItem = NOT_PLAYING;
[self.player removeObserver:self forKeyPath:#"status"];
self.player = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
else{
[self.player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:self.url]];
if([self.delegate respondsToSelector:#selector(startedToPlayAtIndex:fromIndex:)])
[self.delegate startedToPlayAtIndex:index fromIndex:self.currentItem];
self.currentItem = index;
}
}
}
- (void) stop{
[self.player pause];
if([self.delegate respondsToSelector:#selector(stoppedToPlayAtIndex:)])
[self.delegate stoppedToPlayAtIndex:self.currentItem];
}
-(void) didEnd:(id)sender{
if([self.delegate respondsToSelector:#selector(didEndPlayingAtIndex:)])
[self.delegate didEndPlayingAtIndex:self.currentItem];
self.currentItem = NOT_PLAYING;
[self.player removeObserver:self forKeyPath:#"status"];
self.player = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - AVAudioSession delegate
-(void)beginInterruption{
NSLog(#"Interruption");
if(self.currentItem != NOT_PLAYING){
[self.player pause];
if([self.delegate respondsToSelector:#selector(pausedAtIndex:)])
[self.delegate pausedAtIndex:self.currentItem];
}
}
-(void)endInterruption{
NSLog(#"Ended interruption");
if(self.currentItem != NOT_PLAYING){
[self.player play];
if([self.delegate respondsToSelector:#selector(startedToPlayAtIndex:)])
[self.delegate startedToPlayAtIndex:self.currentItem];
}
}
#end

It looks like you have a problem with correct audio routing. I suggest to add an audio route change listener callback. First of all declare a method:
void audioRouteChangeListenerCallback(void *inUserData, AudioSessionPropertyID inPropertyID, UInt32 inPropertyValueSize, const void *inPropertyValue);
Then in your init method add a callback:
// Prevent from audio issues when you pull out earphone
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, audioRouteChangeListenerCallback, (__bridge void *)(self));
And finally, add a callback implementation:
void audioRouteChangeListenerCallback(void *inUserData, AudioSessionPropertyID inPropertyID, UInt32 inPropertyValueSize, const void *inPropertyValue) {
if (inPropertyID != kAudioSessionProperty_AudioRouteChange) {
return;
}
CFDictionaryRef routeChangeDictionary = inPropertyValue;
CFNumberRef routeChangeReasonRef = CFDictionaryGetValue(routeChangeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
SInt32 routeChangeReason;
CFNumberGetValue(routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);
if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
// Headset is unplugged..
NSLog(#"Headset is unplugged..");
// Delayed play: 0.5 s.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[CORE.player play]; // NOTE: change this line according your current player implementation
NSLog(#"resumed play");
});
}
if (routeChangeReason == kAudioSessionRouteChangeReason_NewDeviceAvailable) {
// Headset is plugged in..
NSLog(#"Headset is plugged in..");
}
}
Hope this help! At least for other people it will be helpful tip.

Related

AVAudioPlayer not responding to stop command?

I'm currently trying to use a custom class for the AVAudioPlayer and all works great for playing a audio file but when it comes to stoping the said file it just skips over the stop command and plays another on top of the current file,
If anyone has any ideas on why this is happening I'd really appreciate the help.
Below is my AudioPlayer1.h file:
#import <Foundation/Foundation.h>
#import "AVFoundation/AVAudioPlayer.h"
#import <AVFoundation/AVFoundation.h>
#interface AudioPlayer1 : NSObject <AVAudioPlayerDelegate> {
AVAudioPlayer *player;
BOOL playing;
NSTimeInterval duration;
}
#property (nonatomic, assign) AVAudioPlayer *player;
#property(readonly, getter=isPlaying) BOOL playing;
#property (readonly) NSTimeInterval duration;
-(void)GetSoundFileDuration:(NSString *) sound_file_name;
-(void)play;
-(void)stop;
-(void)setVolume:(float) volume;
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
- (void)PlaySoundFile:(NSString *) sound_file_name;
- (void)notPlaying;
#end
Below is the contents of AudioPlayer1.m file:
#import "AudioPlayer1.h"
#implementation AudioPlayer1
#synthesize player;
#synthesize duration;
#synthesize playing;
- (id)init
{
self = [super init];
if (self != nil)
{
player = [[AVAudioPlayer alloc] init];
[player initWithContentsOfURL:nil error:nil];
player.delegate = self;
}
return self;
}
- (void) setVolume:(float)volume
{
[player setVolume:volume];
}
- (void)PlaySoundFile:(NSString *) sound_file_name
{
[player stop];
NSURL *sound_file = [[NSURL alloc] initFileURLWithPath: [[NSBundle mainBundle] pathForResource:sound_file_name ofType:#"mp3"]];
[player initWithContentsOfURL:sound_file error:nil];
[player prepareToPlay];
playing = YES;
[sound_file release];
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
NSLog(#"audioPlayerDidFinishPlaying");
playing = NO;
}
- (void) play
{
NSLog(#"Play Called");
[player setVolume:0.1];
[player play];
playing = YES;
}
- (void) stop
{
NSLog(#"Stop Called");
[player setVolume:0.0];
[player stop];
playing = NO;
}
-(void)GetSoundFileDuration:(NSString *) sound_file_name
{
NSLog(#"%#",duration);
}
-(void) notPlaying
{
playing = NO;
}
#end
And below is the code that would start the audio:
- (IBAction)WPNaritive01:(id)sender
{
AudioPlayer1 * player = [[AudioPlayer1 alloc] init ];
if (player.playing == YES)
{
[player stop];
}
if (player.playing == NO)
{
[player PlaySoundFile:#"1"];
[player setVolume:0.1];
[player play];
}
}
I apologize about the odd layout to the code as I'm still learning. I've only now found out about making these classes by chance from reading another question. lol
Ok, I've figured it out.
Rather than creating the object over and over like I was perhaps doing in the code above I should have been alloc and init the player in viewdidload rather than doing it in the IBAction like I did.
This is what I did incase anyone else comes across the same problem:
LockonLocation.h
#interface LockOnLocation : UIViewController {
AudioPlayer1 *player;
...
LockonLocation.m
- (void)viewDidLoad
player = [[AudioPlayer1 alloc] init ];
...
And I used the same code as above to play the audio so that was correct.
If anyone has any other advice for me or any other members I'd love to hear from you...

iOS : How to reference a music background from a singleton class?

I have done a singleton class called MyBgMusic.h & MyBgMusic.m. How to reference that singleton class to my SecondViewController or the rest of the XIB.
Here is my singleton class:
H file :
#import <Foundation/Foundation.h>
#import <AVFoundation/AVAudioPlayer.h>
#interface MyBgMusic : UIViewController <AVAudioPlayerDelegate> {
AVAudioPlayer *player;
UIButton *playBgMusic;
}
#property (nonatomic, retain) IBOutlet AVAudioPlayer *player;
#property (nonatomic, retain) IBOutlet UIButton *playBgMusic;
+ (id)sharedManager;
-(IBAction) toggleMusic;
#end
M file :
#import "MyBgMusic.h"
static MyBgMusic *sharedMyManager = nil;
#implementation MyBgMusic
#synthesize player,playBgMusic;
#pragma mark -
#pragma mark Singleton Methods
+ (MyBgMusic*)sharedManager {
static MyBgMusic *sharedMyManager;
if(!sharedMyManager) {
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedMyManager = [[super allocWithZone:nil] init];
});
}
return sharedMyManager;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedManager];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#if (!__has_feature(objc_arc))
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (id)autorelease {
return self;
}
- (void)dealloc
{
[MyBgMusic release];
[playBgMusic release];
[player release];
[super dealloc];
}
#endif
#pragma mark -
#pragma mark Custom Methods
- (IBAction)toggleMusic {
if ([self.player isPlaying] == YES) {
[self.player stop];
} else {
[self.player play];
}
self.playBgMusic.enabled = YES;
}
- (void)viewDidLoad
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"music" ofType:#"mp3"];
self.player=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
player.delegate = self;
[player play];
player.numberOfLoops = -1;
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
SecondViewController.m ( I want to reference from the singleton class so that I can use it all over again without mess up the background music when pressing on and off. )
- (IBAction)toggleMusic{
if ([self.player isPlaying] == YES) {
[self.player stop];
} else {
[self.player play];
}
self.playBgMusic.enabled = YES;
}
Should I declare like this :
-(IBAction) sharedMusic {
[[MyBgMusic sharedManager] toggleMusic]; // instance method shareManager not found. What does it mean?
}
Create an IBAction in your SecondViewController hook up the xib then in code then have it call
[[MyBgMusic sharedInstance] toggleMusic];
In SecondViewController you should call [[MyBgMusic sharedInstance] toggleMusic];in the IBAction when you want to toggle music. Just as you used self.player.
You have to import your class and reference it wherever you want using this line :
[[MyBgMusic sharedManager] toggleMusic]
If you want, you can add even property and reference it without create an instance of your class.

iPhone App: not resuming audio session in AVAudio Player

In my iPhone App I am Playing music using AVAudioPlayer. when my App runs in background that time also it is running for tht I wrote this code
in AppDelegate on
-- application didFinishLaunchingWithOptions
audioManager= [[AudioManager alloc] init];
audioManager.delegate=self;
audioManager.soundFile=[[[NSBundle mainBundle] pathForResource:#"mysong" ofType:#"wav"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[audioManager setFileForPlay];
[audioManager.player setNumberOfLoops:-1];
[audioManager.player setVolume:.5];
[audioManager play];
-(void) playerBeginInterruption:(AVAudioPlayer *)p{
[usdef setValue:#"START" forKey:#"LOCATIONMANAGER"];
[locationManager startUpdatingLocation];
[p pause];
// audioManager=nil;
}
-(void) playerBeginInterruptionInBackground:(AVAudioPlayer *)p{
[usdef setValue:#"START" forKey:#"LOCATIONMANAGER"];
[locationManager startUpdatingLocation];
[p pause];
//audioManager=nil;
}
-(void) playerEndInterruption:(AVAudioPlayer *)p{
NSLog(#"Inteerpt END");
[p prepareToPlay];
[p play];
[usdef setValue:#"STOP" forKey:#"LOCATIONMANAGER"];
}
-(void) playerEndInterruptionInBackgroud:(AVAudioPlayer *)p{
NSLog(#"Inteerpt END BG");
// [self performSelector:#selector(allocaudioManager)];
[p prepareToPlay];
[p play];
[usdef setValue:#"STOP" forKey:#"LOCATIONMANAGER"];
// [locationManager stopUpdatingLocation];
}
now i have created saperate classes for audio manager which handels the delegate methods
in .h file
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#protocol AudioManagerDelegate;
#interface AudioManager : NSObject <AVAudioPlayerDelegate> {
AVAudioPlayer *player;
NSString *soundFile;
BOOL inBackground;
id<AudioManagerDelegate> delegate;
}
#property (nonatomic, assign) id<AudioManagerDelegate>delegate;
#property (nonatomic, retain) NSString *soundFile;
#property (nonatomic, assign) AVAudioPlayer *player;
#property (nonatomic, assign) BOOL inBackground;
- (void)play;
- (void)pause;
- (void)stop;
- (void)registerForBackgroundNotifications;
-( void) setFileForPlay;
#end
#protocol AudioManagerDelegate <NSObject>
#optional
- (void)playerDidFinishPlayingInBackground:(AVAudioPlayer*)p;
- (void)playerDidFinishPlaying:(AVAudioPlayer*)p;
- (void)playerBeginInterruption:(AVAudioPlayer*)p;
- (void)playerBeginInterruptionInBackground:(AVAudioPlayer*)p;
- (void)playerEndInterruption:(AVAudioPlayer *)p;
- (void)playerEndInterruptionInBackgroud:(AVAudioPlayer *)p;
#end
in .m file
#implementation AudioManager
#synthesize delegate;
#synthesize player;
#synthesize inBackground;
#synthesize soundFile;
void RouteChangeListener(void *inClientData, AudioSessionPropertyID inID,UInt32 inDataSize, const void *inData);
-(id) init
{
if((self=[super init]))
{
OSStatus result = AudioSessionInitialize(NULL, NULL, NULL, NULL);
if (result)
NSLog(#"Error initializing audio session! %ld", result);
[[AVAudioSession sharedInstance] setDelegate: self];
NSError *setCategoryError = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryError];
if (setCategoryError)
NSLog(#"Error setting category! %#", setCategoryError);
result = AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, RouteChangeListener, self);
if (result)
NSLog(#"Could not add property listener! %ld", result);
return self;
}
return nil;
}
-(void) setFileForPlay{
self.player=nil;
player = [[AVAudioPlayer alloc] initWithData:[NSData dataWithContentsOfFile:self.soundFile] error:nil];//initWithContentsOfURL:[NSURL URLWithString:self.soundFile] error:nil];
if (self.player)
{
player.delegate = self;
}
}
-(void) play{
[self.player play];
}
-(void)pause{
[self.player pause];
NSLog(#"in Pause");
}
-(void)stop{
[self.player stop];
NSLog(#"in stop");
}
void RouteChangeListener(void *inClientData, AudioSessionPropertyID inID,UInt32 inDataSize, const void *inData){
AudioManager* This = (AudioManager*)inClientData;
if (inID == kAudioSessionProperty_AudioRouteChange) {
CFDictionaryRef routeDict = (CFDictionaryRef)inData;
NSNumber* reasonValue = (NSNumber*)CFDictionaryGetValue(routeDict, CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
int reason = [reasonValue intValue];
if (reason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
[This stop];
}
}
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)p successfully:(BOOL)flag
{
if (flag == NO)
NSLog(#"Playback finished unsuccessfully");
[p setCurrentTime:0.];
if (inBackground)
{
[delegate playerDidFinishPlayingInBackground:p];
}
else
{
[delegate playerDidFinishPlaying:p];
}
}
- (void)playerDecodeErrorDidOccur:(AVAudioPlayer *)p error:(NSError *)error
{
NSLog(#"ERROR IN DECODE: %#\n", error);
}
// we will only get these notifications if playback was interrupted
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption begin ");
// the object has already been paused, we just need to update UI
if (inBackground)
{
[delegate playerBeginInterruptionInBackground:p];
}
else
{
[delegate playerBeginInterruption:p];
}
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)p withFlags:(NSUInteger)flags
{
if(inBackground)
{
[delegate playerEndInterruptionInBackgroud:p];
}
else
{
[delegate playerEndInterruption:p];
}
NSLog(#"Interruption ended. Resuming playback");
}
- (void)registerForBackgroundNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(setInBackgroundFlag)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(clearInBackgroundFlag)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)setInBackgroundFlag
{
inBackground = true;
}
- (void)clearInBackgroundFlag
{
inBackground = false;
}
#end
Now the problem is if I am Playing any songs form ipod library and stop the song and closing the ipod library, my original avaudioplayer which was playing before starting ipos library is not resuming means it is not playong song again
what could be wrong?
is there any demo project or sample code which can help me?
Please help and suggest.
You shouldn't have to call pause on the AVAudioPlayer (p in your code) in audioPlayerBeginInterruption: the player should not be playing at that stage because of the interruption. In audioPlayerEndInterruption: the following usually works for me:
[p prepareToPlay];
[p play];
The Audio Session Cookbook in the iOS docs has a section on "Handling Interruptions with the AVAudioPlayer Class" that should also help.

Adding delegate to AVAudioPlayer cause "_NSAutoreleaseNoPool(): Object 0x55e060 of class NSCFString autoreleased with no pool in place - just leaking"

I have been using a class to play sounds using AVAudioPlayer. Since I want to release these sounds right after they are played, I added a delegate. That causes a "_NSAutoreleaseNoPool(): Object 0x55e060 of class NSCFString autoreleased with no pool in place - just leaking" error right after the sound completes playing, but before my -audioPlayerDidFinishPlaying is called.
Here are some sources:
#interface MyAVAudioPlayer : NSObject <AVAudioPlayerDelegate> {
AVAudioPlayer *player;
float savedVolume;
BOOL releaseWhenDone;
}
The main class .m:
- (MyAVAudioPlayer *) initPlayerWithName: (NSString *) name;
{
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: name ofType: #"caf"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
player = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error: nil];
[fileURL release];
[player prepareToPlay];
return (self);
}
- (MyAVAudioPlayer *)getAndPlayAndRelease:(NSString *)name withVolume:(float) vol;
{
MyAVAudioPlayer *newMyAVPlayer = [self initPlayerWithName:name];
player.volume = vol;
[player play];
releaseWhenDone = YES;
[player setDelegate: self];
return newMyAVPlayer;
}
+ (void) getAndPlayAndReleaseAuto:(NSString *)name withVolume:(float) vol;
{
MyAVAudioPlayer *newMyAVPlayer = [[MyAVAudioPlayer alloc] getAndPlayAndRelease:name withVolume:vol];
// [newMyAVPlayer autorelease];
}
#pragma mark -
#pragma mark AVAudioPlayer Delegate Methods
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)playedPlayer successfully:(BOOL)flag {
if (releaseWhenDone) {
NSLog(#"releasing");
[playedPlayer release];
// [self release];
NSLog(#"released");
}
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
NSLog(#"Error while decoding: %#", [error localizedDescription] );
}
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {
NSLog(#"Interrupted!");
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player {
NSLog(#"EndInterruption!");
}
- (BOOL) play;
{
player.currentTime = 0.0;
return [player play];
}
Commenting out the [player setDelegate: self]; makes the error go away, but then my audioPlayerDidFinishPlaying doesn't get called.
Any thoughts? Am I suddenly running in another thread?
I found the problem. My bug, of course.
In a lot of my class files, I was adding:
-(BOOL) respondsToSelector:(SEL) aSelector
{
NSLog(#"Class: %# subclass of %#, Selector: %#", [self class], [super class], NSStringFromSelector(aSelector));
return [super respondsToSelector:aSelector];
}
Mainly out of curiousity.
Well, when I added a delegate to my sound, then this method gets called before the delegate, and it gets called from whatever runloop the AVAudioPlayer happened to be in, and likely, a runloop with no autorelease pool.

AVAudioPlayer - playing multiple audio files, in sequence

I want to play multiple MP3 files, in sequence (one after the other) , using AVAudioPlayer. I tried it, and it stops after playing the first MP3. However, if I go into debugger, it works fine.. any ideas? I read somewhere AVAudioPlayer plays audio in the background.. how do I prevent it from doing this?
Vas
I think the AVQueuePlayer (subclass of AVPlayer) does exactly this job (play a sequence of items) since iOS 4.1 :
http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVQueuePlayer_Class/Reference/Reference.html
I didn't try it myself however but will definitely give a try to it.
Use one AVAudioPlayer per sound.
Well, your code example didn't work out of the box for me. Soooo, I figured I'd respond with a fixed version:
Looper.h:
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface Looper : NSObject <AVAudioPlayerDelegate> {
AVAudioPlayer* player;
NSArray* fileNameQueue;
int index;
}
#property (nonatomic, retain) AVAudioPlayer* player;
#property (nonatomic, retain) NSArray* fileNameQueue;
- (id)initWithFileNameQueue:(NSArray*)queue;
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
- (void)play:(int)i;
- (void)stop;
#end
Looper.m:
#import "Looper.h"
#implementation Looper
#synthesize player, fileNameQueue;
- (id)initWithFileNameQueue:(NSArray*)queue {
if ((self = [super init])) {
self.fileNameQueue = queue;
index = 0;
[self play:index];
}
return self;
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
if (index < fileNameQueue.count) {
[self play:index];
} else {
//reached end of queue
}
}
- (void)play:(int)i {
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:[fileNameQueue objectAtIndex:i] ofType:nil]] error:nil];
[player release];
player.delegate = self;
[player prepareToPlay];
[player play];
index++;
}
- (void)stop {
if (self.player.playing) [player stop];
}
- (void)dealloc {
self.fileNameQueue = nil;
self.player = nil;
[super dealloc];
}
#end
And here's how I would call it:
Looper * looper = [[Looper alloc] initWithFileNameQueue:[NSArray arrayWithObjects: audioFile, audioFile2, nil ]];
I only have slightly over a year of experience with iPhone/iPad development using Objective-C so feel free to respond with additional criticism.
I've implemented a class to handle this.
To use just do something like this:
[looper playAudioFiles:[NSArray arrayWithObjects:
#"add.mp3",
[NSString stringWithFormat:#"%d.mp3", numeral1.tag],
#"and.mp3",
[NSString stringWithFormat:#"%d.mp3", numeral2.tag],
nil
]];
Looper.m
#import "Looper.h"
#implementation Looper
#synthesize player, fileNameQueue;
- (id)initWithFileNameQueue:(NSArray*)queue {
if ((self = [super init])) {
self.fileNameQueue = queue;
index = 0;
[self play:index];
}
return self;
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
if (index < fileNameQueue.count) {
[self play:index];
} else {
//reached end of queue
}
}
- (void)play:(int)i {
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:[fileNameQueue objectAtIndex:i] ofType:nil]] error:nil];
[player release];
player.delegate = self;
[player prepareToPlay];
[player play];
index++;
}
- (void)stop {
if (self.player.playing) [player stop];
}
- (void)dealloc {
self.fileNameQueue = nil;
self.player = nil;
[super dealloc];
}
#end
Looper.h
#import <Foundation/Foundation.h>
#interface Looper : NSObject <AVAudioPlayerDelegate> {
AVAudioPlayer* player;
NSArray* fileNameQueue;
int index;
}
#property (nonatomic, retain) AVAudioPlayer* player;
#property (nonatomic, retain) NSArray* fileNameQueue;
- (id)initWithFileNameQueue:(NSArray*)queue;
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
- (void)play:(int)i;
- (void)stop;
#end
It is a good idea to initialize, prepare the items, and queue ahead of time, for example on the viewDidLoad method.
If you are working on Swift,
override func viewDidLoad() {
super.viewDidLoad()
let item0 = AVPlayerItem.init(URL: NSBundle.mainBundle().URLForResource("url", withExtension: "wav")!)
let item1 = AVPlayerItem.init(URL: NSBundle.mainBundle().URLForResource("dog", withExtension: "aifc")!)
let item2 = AVPlayerItem.init(URL: NSBundle.mainBundle().URLForResource("GreatJob", withExtension: "wav")!)
let itemsToPlay:[AVPlayerItem] = [item0, item1, item2]
queuePlayer = AVQueuePlayer.init(items: itemsToPlay)
}
and then when an event occurs,
queuePlayer.play()
Notice that if you use the queue, you still might have some gaps between the sounds.
You can find the Objective-C version in the question How to do something when AVQueuePlayer finishes the last playeritem
Hope it helps.