AVPlayer class events - objective-c

Are there any delegate methods in AVPlayer class? I need to handle interruptions such as phone call etc. AVAudioPlayer supports. If AVPlayer doesn't support it, how to stream audio with AVAudioPlayer?

AVPlayer doesn't have the methods you want but you can use AVAudioSession object instead
1) Select AVAudioSession object (for example [AVAudioSession sharedInstance])
2) Set it active by calling setActive:error: method
3) Set its delegate (class implementing AVAudioSessionDelegate protocol)
4) Implement delegate's methods such as
-(void)beginInterruption;
-(void)endInterruptionWithFlags:(NSUInteger)flags;
-(void)endInterruption;

EDIT
I don't see any delegates available in AVPlayer class
So how to stream audio with AVAudioPlayer? Because we don't know how you need to stream it, and most important from where, providind some inspiration
see related questions:
stopping an AVAudioPlayer
Reusing an AVAudioPlayer for a different sound
avaudioplayer playingsong
Streaming with an AVAudioplayer
http://blog.guvenergokce.com/avaudioplayer-on-iphone-simulator/57/
http://www.iphonedevsdk.com/forum/iphone-sdk-development/15991-sample-code-avaudioplayer.html
and tutorial
http://mobileorchard.com/easy-audio-playback-with-avaudioplayer/
AVAudioPlayerDelegate Protocol Reference http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVAudioPlayerDelegateProtocolReference/Reference/Reference.html#//apple_ref/doc/uid/TP40008068
Responding to Sound Playback Completion
– audioPlayerDidFinishPlaying:successfully:
Responding to an Audio Decoding Error
– audioPlayerDecodeErrorDidOccur:error:
Handling Audio Interruptions
– audioPlayerBeginInterruption:
– audioPlayerEndInterruption:
– audioPlayerEndInterruption:withFlags:

I don't think AVPlayer will get you there. Take a look at AVAudioPlayerDelegate, The audioPlayerBeginInterruption would be the delegate method you are looking for.
Here's a sample of code I use for AVAudioPlayer (I'm assuming you already know how to build your url):
// Instantiates the AVAudioPlayer object, initializing it with the sound
NSError * errAV = nil;
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfUrl: mUrl error: &errAV];
if (newPlayer == nil) {
NSString * msg = [[NSString alloc] initWithFormat:#"An internal error has occured: %#", [errAV localizedDescription]];
UIAlertView *uiav = [[UIAlertView alloc] initWithTitle:#"Play Sound"
message:msg delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[uiav show];
[uiav release];
[msg release];
} else {
self.appSoundPlayer = newPlayer;
[newPlayer release];
// "Preparing to play" attaches to the audio hardware and ensures that playback
// starts quickly when the user taps Play
[appSoundPlayer prepareToPlay];
[appSoundPlayer setVolume: 1.0];
[appSoundPlayer setDelegate: self];
[appSoundPlayer play];
}

Even when using AVAudioPlayer, you can initialize an Audio Session, where in you can specify the kind of playback (or recording, for that matter) you will be doing, and a callback for handling interruptions like phone calls.
Have a look at AudioSessionInitialize() and it's third parameter, a callback function for handling interruptions. In your callback, you can handle both the start and end of an interruption.
The salient different here, between using an AudioSession and relying on the AVAudioPlayer callbacks, is that the former occurs at a lower level, perhaps before the latter's delegate methods are called. So with the AudioSession callback, you have finer control, I think, but then you have to do more, perhaps, depending on the complexity of your app's audio setup.

It has been a long while since the question was posted. However, for the sake of completion, I would like to add: AVPlayer can be used to handle interruptions by adding a TimeObserver as follows:
When initialising the AVPlayer:
AVPlayer *_aplayer;
id _aplayerObserver;
_aplayer = [[AVPlayer alloc] initWithURL:mediaURL];
_aplayerObserver = [_aplayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:NULL usingBlock:^(CMTime time)
{
if (((time.value/time.timescale) >= (_aplayer.currentItem.asset.duration.value/_aplayer.currentItem.asset.duration.timescale))
{
// media file played to its end
// you can add here code that should run after the media file is completed,
// thus mimicing AVAudioPlayer's audioPlayerDidFinishPlaying event
}
else
{
if (_aplayer.rate == 0)
// audio player was interrupted
}
}
If you choose this solution, please take note of what addPeriodicTimeObserverForInterval's documentation says:
You must retain the returned value [i.e. _aplayerObserver] as long as you want the time observer to be invoked by the player. Each invocation of this method should be paired with a corresponding call to removeTimeObserver:.

Related

How do I detect when a Quick Look panel is dismissed?

I've written a Quick Look plugin that attempts to play music like this:
OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
NSURL *fileURL = (__bridge NSURL*)url;
AudioPlayer *player = // load player with fileURL
// Create a semaphore
sema = dispatch_semaphore_create(0);
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Start playback and signal the semaphore once finished
[player play:^{
dispatch_semaphore_signal(sema);
}];
// Wait here until the player completion block signals the semaphore to stop waiting
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(#"%#", #"done!");
return kQLReturnNoError;
}
For various reasons, it's not practical for me to transcode these audio files into a format that macOS knows, or else I could just hand the OS an MP3 file and get the system's plugin to play it for me. So instead I'm using a dirty hack with semaphores to halt execution to keep my player object around, or else it'd abruptly stop immediately after starting playback.
The problem with that is that the file will just continue playing after the Quick Look panel stops previewing it due to the quicklookd process still running.
Is there a way to stop playback the way the system plugins do when they're dismissed?
Have you tried to use following delegate methods:
According to Apple Documentation:
func previewControllerWillDismiss(QLPreviewController)
Called before the preview controller is closed.
func previewControllerDidDismiss(QLPreviewController)
Called after the preview controller is closed.

how to stop the infinite loop and allow to accept the touches during this loop

while (true)
{
endtime = CFAbsoluteTimeGetCurrent();
double difftime = endtime - starttime;
NSLog(#"The tine difference = %f",difftime);
if (difftime >= 10)
{
NSURL *url= [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/a0.wav" , [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops=0;
[audioPlayer play];
starttime = endtime;
}
}
when i call this method my application enters in an infinite loop and doesnot accept any touches again how can solve this please?
It looks like you want it to play a sound every 10 seconds. You would be better using a timer like this...
NSTimer *yourTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(repeatedAction)
userInfo:nil
repeats:YES];
Then have your repeating function...
- (void)repeatedAction
{
// do your stuff in here
}
The UI will be responsive all the time and you can have a cancel button with an action like this...
- (void)cancelRepeatedAction
{
[self.yourTimer invalidateTimer];
}
This will stop the time rand the repeated action.
HOWEVER
You are also trying to download a file EVERY time you run your action.
A better approach would be to download the file once and store it.
Even better would be to download the file asynchronously.
This is not how you structure an event-driven application. You are blocking the event processing queue (NSRunLoop) by not returning out of this function. So no touch events will get a chance to be processed until you return from this loop.
You need to play the sounds asynchronously - which AVAudioPlayer can do for you. Read up on the ADC documentation about queueing and playing music in AVFoundation.
For a start, you don't need the while loop at all. Just start the playback, and register for notifications of playback progress. Make the _audioPlayer an ivar of your controller class, as its lifetime persists beyond the method used to load the file and initiate playback:
NSURL *url= [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/a0.wav" , [[NSBundle mainBundle] resourcePath]]];
NSError *error;
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
_audioPlayer.numberOfLoops=0;
[_audioPlayer play];
Then define a delegate method which implements the AVAudioPlayerDelegate protocol, and provide at least this method:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
}
You can also provide a touch event handler in your view, which calls a pauseAudio: method in the controller class, which then calls:
[audioPlayer pause];
Have a good read through the docs, and study the sample code:
iOS Developer Library - ADC AVAudioPlayer Class Reference
Have a look at how this app is structured, with a controller registering for notifications and using a delegate for callbacks. It also uses a timer and features GUI updates.
iOS Developer Library - avTouch - media player sample code

AVAudioPlayer - must you create a property for it to work? (Xcode)

This is the code I'm using now, and it's not working (nothing happens when I press the button that calls this method). Previously, I had a property for audioPlayer and it worked (all the audioPlayers below were self.audioPlayer obviously). The problem was that when I tried to play the sound twice, it would end the first sound playing.
This is no good because I'm making a soundboard and want sounds to be able to overlap. I thought I could just make audioPlayer a local variable instead of a property and all would be ok but now the sound doesn't work at all and I can't figure out why. In all tutorials I've found for AVAudioPlayer, a property is made but no one explains why. If this can't work, what alternatives do I have to make sounds that can overlap?
- (void)loadSound:(NSString *)sound ofType:(NSString *)type withDelegate:(BOOL)delegate {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:sound
ofType:type]];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];
}
The reason you need a property or ivar is for the strong reference it provides. When using ARC any object without a strong pointer to it is fair game for deallocation, and in fact that is what you are seeing.
You are also correct that an AVAudioPlayer strong pointer will only allow one audio player to be referenced at a time.
The solution, if you choose to continue to use AVAudioPlayer is to use some sort of collection object to hold strong reference to all the player instances. You could use an NSMutableArray as shown here:
Edit I tweaked the code slightly so method that plays the sound takes an NSString soundName parameter.
#synthesize audioPlayers = _audioPlayers;
-(NSMutableArray *)audioPlayers{
if (!_audioPlayers){
_audioPlayers = [[NSMutableArray alloc] init];
}
return _audioPlayers;
}
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
[self.audioPlayers removeObject:player];
}
-(void)playSoundNamed:(NSString *)soundName{
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:soundName
ofType:#"wav"]];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (audioPlayer){
[audioPlayer setDelegate:self];
[audioPlayer prepareToPlay];
[audioPlayer play];
[self.audioPlayers addObject:audioPlayer];
}
}
Generally an AVAudioPlayer is overkill for an sound-effect/soundboard application. For quick sound "drops" you will likely find the audio toolbox framework, as outlined in my answer to this question.
From looking at the System Sound class reference, it seems like you
can only play one sound at a time.
It can only play one SystemSoundID at a time. So for example if you have soundOne and soundTwo. You can play soundOne while soundTwo is playing, but you cannot play more than one instance of either sound at a time.
What's the best way to be able to play sounds that can overlap while
still being efficient with the amount of code and memory?
Best is opinion.
If you need two instances of the same sound to play at the same time, then I would say the code posted in this answer would be the code to use. Due to the fact that each overlapping instance of the same sound requires creating a new resource, code like this with its audioPlayerDidFinishPlaying: is much more manageable(the memory can easily be reclaimed).
If overlapping instances of the same sound are not a deal-breaker then I think just using AudioServicesCreateSystemSoundID() to create one instance of each sound is more efficient.
I definitely would not try to manage the creation of and disposal of SystemSoundIDs with each press of a button. That would go wrong in a hurry. In that instance AVAudioPlayer is the clear winner on just maintainability alone.
I am assuming you are using ARC. The reason that the audio player doesn't work is because the AVAudioPlayer object is being released and then subsequently destroyed once the loadSound: method terminates. This is happening due to ARC's object management. Before ARC, the code:
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];
would play the sound as expected. However, the AVAudioPlayer object would still exist long after the loadSound: method terminates. That means every time you play a sound, you would be leaking memory.
A little something about properties
Properties were introduced to reduce the amount of code the developer had to write and maintain. Before properties, a developer would have to hand write the setters and getters for each of their instance variables. That's a lot of redundant code. A fortunate side-effect of properties was that they took care of a lot of the memory management code needed to write setters/getters for object-based instance variables. This meant that a lot of developers started using properties exclusively, even for variables that didn't need to be public.
Since ARC handles all the memory management details for you, properties should only be used for their original purpose, cutting down on the amount of redundant code. Traditional iVars will be strongly referenced by default, which means a simple assignment such as:
title = [NSString stringWithFormat:#"hello"];
is essentially the same as the code:
self.title = [NSString stringWithFormat:#"hello"];.
OK, back to your question.
If you are going to be creating AVAudioPlayer instances in the loadSound: method, you'll need to keep a strong reference to each AVAudioPlayer instance or else ARC will destroy it. I suggest adding the newly created AVAudioPlayer objects into a NSMutableArray array. If you adopt the AVAudioPlayerDelegate protocol, you can implement the audioPlayerDidFinishPlaying:successfully: method. In which you can remove the AVAudioPlayer object from the array, letting ARC know that it's OK to destroy the object.

How do I loop a sound, while some condition, using the AVAudioPlayer framework here?

EDIT : trying to implement Adam B's answer now...
I have a crashSound.wav file that I have put in my Supporting Files folder of my xCode project
I'm now trying to make it play inside a while loop, but the documentation isn't very clear as to how exactly I can do that. I know I have to create a delegate object somewhere (I guess my Game class) and get notifications as to whether stopButtonPressed is false and whether the file has finsihed playing so that it can loop and play again while the stopButtonPressed condition is false.. and I know I shouldn't be doing that by calling the [crashSound play] method but I'm not sure how to do it.. Any help?
#interface Game()
{
// code...
AVAudioPlayer *crashSound;
// code...
}
#end
#implementation Game
- (id) init
{
// code...
NSURL *crashSoundFile = [[NSURL alloc] initWithString:#"crashSound" ];
crashSound = [[AVAudioPlayer alloc] initWithContentsOfURL:crashSoundFile error:NULL];
// code...
}
-(void) play // this is my "main" method that will be called once the playButton is pressed
{
while(!self.stopButonPressed)
{
[crashSound play];
}
}
#end
You're structuring your code wrong for how most multimedia libraries would work (including AVPlayer, for example).
Instead of having a while loop, you would start the sound playing, and check if your condition is true each time it completes. You may need to trigger the "next" thing based on a timer or in that callback when the sound completes, instead of having the loop.
See this question and good answer for an example of how to setup AVPlayer and the notification function playerItemDidReachEnd:: Looping a video with AVFoundation AVPlayer?

Is there a general template for creating a UIPickerview which selects short sound files?

Is there a general template or tutorial or web page that describes the procedure for creating a UIPickerview which selects short sound files and plays them upon selection or with a player? Thanks
You'll need a delegate/data-source class for your picker view - something that implements the protocols UIPickerViewDelegate and UIPickerViewDataSource. This can be whatever view controller you've got handling everything else or a separate class - either way, set the UIPickerView's delegate and dataSource properties to your instance of that class.
The class should have three instance variables - an NSArray soundArr to contain the sounds, an NSTimer timer to provide a delay after selection before the sound plays (more on that below), and an AVAudioPlayer audioPlayer to play the selected sound (for which you'll need to import the AVFoundation framework - it's only available in 2.2, as sound playback used to be a lot more complicated).
When you first load the sounds (in your controller class's -init method or whatever), stick 'em in an array along with the title you want them to display, something like this:
NSBundle *bdl = [NSBundle mainBundle];
soundArr = [[NSArray alloc] initWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:#"Sound One",#"title",[NSURL URLWithString:[bdl pathForResource:#"sound1" ofType:#"wav"]],#"url",nil],
[NSDictionary dictionaryWithObjectsAndKeys:#"Sound Two",#"title",[NSURL URLWithString:[bdl pathForResource:#"sound2" ofType:#"wav"]],#"url",nil],
nil];
The methods you'll need to implement are:
-pickerView:numberOfRowsInComponent: - should return the size of soundArr
-pickerView:titleForRow:forComponent: - should return [[soundArr objectAtIndex:row] objectForKey:#"title"]
-numberOfComponentsInPickerView: - should return 1, since you've only got one column (component) to select from
-pickerView:didSelectRow:inComponent: - see below
You don't want the sound to start immediately when the row-selection delegate method gets called, or snippets of sounds will play continuously as the user scrolls the picker. Instead, use a timer with a short delay, something like this:
if(timer != nil)
{
[timer invalidate]; // remove any timer from an earlier selection
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:0.4 target:self selector:#selector(startSoundAtURL:) userInfo:[[soundArr objectAtIndex:row] objectForKey:#"url"] repeats:NO]; // and create the new one
Then, implement a -startSoundAtURL: method that sets up the AVAudioPlayer to play that sound:
- (void)startSoundAtURL:(NSURL *)url
{
if(audioPlayer != nil)
{
[audioPlayer stop];
[audioPlayer release];
}
NSError *err;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&err];
if(err != nil)
{
NSLog([err description]);
return;
}
[audioPlayer play];
}
That should pretty much do it.