AVPlayerItemDidPlayToEndTimeNotification only works if forced

♀尐吖头ヾ 提交于 2019-12-10 11:56:20

问题


I been searching everywhere and I still can't find a solution to this problem. I have a AVQueuePlayer for a radio app that I am making. It has about 20 AVPlayerItems queued at a time. I added the AVPlayerItemDidPlayToEndTimeNotification observer to each one of those items. The notification gets fired and executes the code to remove key observers for metadata and status so it doesn't crash and advances to next item in the queue. However it doesn't want to play. There is no error, the status is ready to play, the AVPlayer URL is loaded perfectly. If I click the button to call the advancenextitem it works perfectly and plays perfectly too. Now the strangest thing is: if I post the notification manually the notification code works perfectly. I would be grateful for any help or input as I been trying everything to get this working.

if (playlist != nil) {
    playlistInterval = [playlist count]/4.0;
    NSMutableArray *playerItems = [[NSMutableArray alloc] initWithCapacity:playlistInterval];

        for(int i = 0; i < playlistInterval; i++) {
            NSString *tempString = [playlist objectAtIndex:i];
            //NSLog(@"%@", tempString);
            NSURL *temp = [[NSURL alloc] initWithString:tempString];
            AVPlayerItem *itemTemp = [AVPlayerItem playerItemWithURL:temp];
            [itemTemp addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionNew context:nil];
            [itemTemp addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
            [[NSNotificationCenter defaultCenter] addObserver: self
                                                     selector: @selector(playerItemDidReachEnd:)
                                                         name: AVPlayerItemDidPlayToEndTimeNotification
                                                       object: itemTemp];
            [playerItems addObject:itemTemp];
            playlistCounter++;
        }
        qPlayer = [AVQueuePlayer queuePlayerWithItems:playerItems];

        qPlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
        [   qPlayer addObserver:self
                     forKeyPath:@"rate"
                        options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                        context:rateDidChangeKVO];

        [qPlayer addObserver:self forKeyPath:@"currentItem.duration"
                     options:0
                     context:durationDidChangeKVO];
    return YES;
}
return NO;

}

- (IBAction)advancePlayer:(id)sender {
    unsigned long size = [[qPlayer items] count];
    NSLog(@"%tu",size);
    if (size <= 0) {
        [self initializePlayerWithItems:currentKey];
        [qPlayer play];
    } else {
        //NSLog(@"%@",[qPlayer currentItem]);
        [[qPlayer currentItem] removeObserver:self forKeyPath:@"status"];
        [[qPlayer currentItem] removeObserver:self forKeyPath:@"timedMetadata"];
        [qPlayer advanceToNextItem];
        [qPlayer play];
    }

}

- (void)playerItemDidReachEnd:(NSNotification *)notification {
[self advancePlayer:nil];
NSLog(@"IT REACHED THE END");

}

Now if I call this everything works perfectly from a button or something:

[[NSNotificationCenter defaultCenter]
 postNotificationName:@"AVPlayerItemDidPlayToEndTimeNotification"
 object:[qPlayer currentItem]];

回答1:


I don't have a test project to test this theory, however I am guessing the notifications are getting lost because they are not the current item of the player and are only local variables when you add the observer.

I would recommend adding the observer "after" you init the player like so.

...
qPlayer = [AVQueuePlayer queuePlayerWithItems:playerItems];

qPlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;

[[NSNotificationCenter defaultCenter] addObserver: self
                                         selector: @selector(playerItemDidReachEnd:)
                                             name: AVPlayerItemDidPlayToEndTimeNotification
                                           object: [qPlayer currentItem]];

Then after you advance the player you can add the observer again for the next current item.

- (IBAction)advancePlayer:(id)sender {
    unsigned long size = [[qPlayer items] count];
    NSLog(@"%tu",size);
    if (size <= 0) {
        [self initializePlayerWithItems:currentKey];
        [qPlayer play];
    } else {
        //NSLog(@"%@",[qPlayer currentItem]);
        [[qPlayer currentItem] removeObserver:self forKeyPath:@"status"];
        [[qPlayer currentItem] removeObserver:self forKeyPath:@"timedMetadata"];
        [qPlayer advanceToNextItem];

        [[NSNotificationCenter defaultCenter] addObserver: self
                                         selector: @selector(playerItemDidReachEnd:)
                                             name: AVPlayerItemDidPlayToEndTimeNotification
                                           object: [qPlayer currentItem]];
        [qPlayer play];
}

Hopefully that helps and fixes the problem.




回答2:


Make sure you're registering your notification on the main thread.

dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(itemDidFinishPlaying:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:self.playerItem];

});



回答3:


After days of different combinations of code and research this is what I found out: Problem: Have a AVQueuePlayer with about 20+ items at a time with registered observers. When AVPlayerItemDidPlayToEndTimeNotification gets called the code would advance to the next item but the item wouldn't play. Since I have observers attached to my avplayeritems I couldn't just use the advanctonextitem property of the AVPlayer. I needed to use the AVPlayerItemDidPlayToEndTimeNotification. Now when this method would get called it worked perfectly but when the AVPlayerItem posted the notification, it would get called at [templayer advanceToNextItem], the next AVPlayerItem in the queue, its data would be lost. Otherwise if I posted the notification through my own code it would advance to the next item flawlessly. To get around this I tried everything but this is where I ended up and it works perfectly and it is just wanted I needed so maybe if someone else is stuck it'll help them. I used an mutable array to store the "queue" and load only one AVPlayerItem at a time.

if (playlist != nil) {
    playlistInterval = [playlist count]/4.0;
   //Array to hold AVPlayerItems
    _playerItems = [[NSMutableArray alloc] initWithCapacity:playlistInterval];
    for(int i = 0; i < playlistInterval; i++) {
        AVPlayerItem *itemTemp = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:[playlist objectAtIndex:playlistCounter]]];
        [itemTemp addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionNew context:nil];
        [itemTemp addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(playerItemDidReachEnd:)
                                                     name: AVPlayerItemDidPlayToEndTimeNotification
                                                   object: itemTemp];
        [_playerItems addObject:itemTemp];
        playlistCounter++;
    }
   //Init player to only one item
    self.player = [[AVPlayer alloc] initWithPlayerItem:[_playerItems objectAtIndex:0]];
   //Don't do anything since will be handling advancing
    self.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
    [   self.player addObserver:self
                      forKeyPath:@"rate"
                         options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                         context:rateDidChangeKVO];

    [self.player addObserver:self forKeyPath:@"currentItem.duration"
                      options:0
                      context:durationDidChangeKVO];
    return YES;
}

    - (void)playerItemDidReachEnd:(NSNotification *)notification {
//NSLog(@"notifiacation thread: %d", [NSThread isMainThread]);
if ([currentKey isEqualToString:@"morning slokha"] || selection != 0)
    return;
dispatch_async(dispatch_get_main_queue(), ^{
    unsigned long size = [_playerItems count];
    NSLog(@"%tu",size);
    [self resetSlider];
    if (_playerItems.count <= 1) {
        [self initializePlayerWithItems:currentKey];
        [self.player play];
    } else {
        //NSLog(@"%@",[qPlayer currentItem]);
        @try{
            [[_playerItems objectAtIndex:0] removeObserver:self forKeyPath:@"status"];
            [[_playerItems objectAtIndex:0] removeObserver:self forKeyPath:@"timedMetadata"];
            [[self.player currentItem] removeObserver:self forKeyPath:@"status"];
            [[self.player currentItem] removeObserver:self forKeyPath:@"timedMetadata"];
        }@catch(id anException){
            //do nothing, obviously it wasn't attached because an exception was thrown
        }
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(playerItemDidReachEnd:)
                                                     name: AVPlayerItemDidPlayToEndTimeNotification
                                                   object: [_playerItems objectAtIndex:0]];
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(playerItemDidReachEnd:)
                                                     name: AVPlayerItemDidPlayToEndTimeNotification
                                                   object: [self.player currentItem]];
        [_playerItems removeObjectAtIndex:0];
        //Replace AVPlayerItem manually
        [self.player replaceCurrentItemWithPlayerItem:[_playerItems objectAtIndex:0]];
        [self.player seekToTime:kCMTimeZero];
        [self.player play];
    }
});
}



回答4:


1 - I would be very suprised that PlayerItems get lost. They are retained somewhere, in a array, by the player,...

2 - The Appledoc clearly states that you can send notifications on any thread.

In such case, where everything should work, but it does not, I suggest to check the context. A simple case I've been through ( I've lost hours!! ) is that the view holding the player was disposed. The video was still playing in this case. So it is hard to debug.

I finally renounced to use Notification system, to switch to KVO. ( [player addObserver:self keypath:@"status" … )

I got an exception at the disposal of the view, because the view is still registered as a player observer.

I've solved the view bug, and then my view was receiving playerItem's notifications as expected.

It's often like this. Apple tells us it is simple. If it does not work, we do plenty workarounds and personal patches instead of trying to understand what we've done that can have a side effect…



来源:https://stackoverflow.com/questions/28822236/avplayeritemdidplaytoendtimenotification-only-works-if-forced

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!