How to do something when AVQueuePlayer finishes th

2019-04-28 05:11发布

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?

3条回答
走好不送
2楼-- · 2019-04-28 05:27

You likely didn't implement the -observeValueForKeyPath:ofObject:change:context: method in your class. Check out the Apple KVO guide for further details.

查看更多
放我归山
3楼-- · 2019-04-28 05:42

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.

查看更多
一纸荒年 Trace。
4楼-- · 2019-04-28 05:51

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

查看更多
登录 后发表回答