问题
I've got an AVQueuePlayer which I'm creating from an array of 4 AVPlayerItems, and it all plays fine.
I want to do something when the last item in the queue finishes playing, I've looked a load of answers on here and this is the one that looks most promising for what I want: The best way to execute code AFTER a sound has finished playing
In my button handler i have this code:
static const NSString *ItemStatusContext;
[thePlayerItemA addObserver:self forKeyPath:@"status" options:0 context:&ItemStatusContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd)
name:AVPlayerItemDidPlayToEndTimeNotification
object:thePlayerItemA];
theQueuePlayer = [AVQueuePlayer playerWithPlayerItem:thePlayerItemA];
[theQueuePlayer play];
and then I have a function to handle playerItemDidReachEnd:
- (void)playerItemDidReachEnd:(NSNotification *)notification {
// Do stuff here
NSLog(@"IT REACHED THE END");
}
But when I run this I get an Internal Inconsistency Exception:
An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: status
Observed object: <AVPlayerItem: 0x735a130, asset = <AVURLAsset: 0x73559c0, URL = file://localhost/Users/mike/Library/Application%20Support/iPhone%20Simulator/5.0/Applications/A0DBEC13-2DA6-4887-B29D-B43A78E173B8/Phonics%2001.app/yes.mp3>>
Change: {
kind = 1;
}
What am I doing wrong?
回答1:
This approach seems to work for me, within my button handler i've got a bunch of stuff to create URLs for the 4 mp3 files I want to play, then:
AVPlayerItem *thePlayerItemA = [[AVPlayerItem alloc] initWithURL:urlA];
AVPlayerItem *thePlayerItemB = [[AVPlayerItem alloc] initWithURL:urlB];
AVPlayerItem *thePlayerItemC = [[AVPlayerItem alloc] initWithURL:urlC];
AVPlayerItem *thePlayerItemD = [[AVPlayerItem alloc] initWithURL:urlD];
NSArray *theItems = [NSArray arrayWithObjects:thePlayerItemA, thePlayerItemB, thePlayerItemC, thePlayerItemD, nil];
theQueuePlayer = [AVQueuePlayer queuePlayerWithItems:theItems];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[theItems lastObject]];
[theQueuePlayer play];
After which I have implement the 'playerItemDidReachEnd' selector like this:
- (void)playerItemDidReachEnd:(NSNotification *)notification {
// Do stuff here
NSLog(@"IT REACHED THE END");
}
This queues up the 4 MP3 files and then when the last piece of audio has finished it calls the selector and my message appears in the console.
I hope that this is useful for someone else.
回答2:
Observing for status will not tell you when item is finished playing(it only tells when item is readyToPlay)
All you have to do it is to register for AVPlayerItemDidPlayToEndTimeNotification
for the lastItem in your playbackQueue, which use to initialise your AVQueuePlayer
instance.
AVQueuePlayer* player = [AVQueuePlayer queuePlayerWithItems:currentPlaybackQueue];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:currentPlaybackQueue.lastObject];
(note that selector has playerItemDidReachEnd:
, you forgot two dots)
So then -playerItemDidReachEnd:
will only be called exactly once in the end as you need it.
(also don't forget to remove yourself as an observer - you can do it in -playerItemDidReachEnd:
using notification.object
)
Have to say that there is other way to do this:
Using key-value observing for currentItem, then checking if the currentItem changed to [NSNull null]
in observeValueForKeyPath:
.
[player addObserver:self forKeyPath:@"currentItem" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"currentItem"]) {
if (change[NSKeyValueChangeNewKey] == [NSNull null]) {
//last item finished playing - do something
}
}
}
normally for AVQueuePlayer
you will register for AVPlayerItemDidPlayToEndTimeNotification
for each item in playbackQueue
, so unless you don't want to store playbackQueue
locally and check its lastObject with notification.object
in -playerItemDidReachEnd:
, you would use the second way
回答3:
You likely didn't implement the -observeValueForKeyPath:ofObject:change:context:
method in your class. Check out the Apple KVO guide for further details.
来源:https://stackoverflow.com/questions/12187686/how-to-do-something-when-avqueueplayer-finishes-the-last-playeritem