0

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]];
Dharmesh Dhorajiya
  • 3,976
  • 9
  • 30
  • 39
Dhananjay Suresh
  • 1,030
  • 1
  • 14
  • 28

4 Answers4

2

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.

Skyler Lauren
  • 3,792
  • 3
  • 18
  • 30
  • Thanks for your reply. I tried both ways. The thing is the notification gets sent in both cases. In the case of manually posting the notification, everything works as it should. In the case of the notification getting sent when the item actually ends, it executes the code, however the audio won't play. But there are no errors that I can find on the player and the playeritem. The only way to get everything going is to press the button to advancePlayer. – Dhananjay Suresh Mar 03 '15 at 02:04
  • "This notification may be posted on a different thread than the one on which the observer was registered" I wonder if there is an issue with all of this happening on a background thread. – Skyler Lauren Mar 03 '15 at 02:18
  • I'll try registering the observers on the main thread. Should I be just registering the observers on the main thread or all the code (advancePlayer, initializeplayeritems, etc)? – Dhananjay Suresh Mar 03 '15 at 02:37
  • @dj2819 Did calling the code on the main thread fix the rest of the issues? – Skyler Lauren Mar 03 '15 at 20:43
  • The combination of calling it on the main thread and using a local pointer to the avqueueplayer worked. However I changed one little code somewhere and now it doesn't want to work again. – Dhananjay Suresh Mar 05 '15 at 03:15
  • It is strange, it must be something to do with the allocation of the avplayeritem in the memory. – Dhananjay Suresh Mar 05 '15 at 03:17
  • Not sure why but the code below only works sometimes. I am using the same code for my button that advances the player to the next song and it works perfectly though the button. Also the notification works 75% of the time if I seek to close to the end of the track. The notification gets posted and works perfectly. I spent days trying different combinations of things and I can't figure how to get it to work when a song plays from beginning to end. – Dhananjay Suresh Apr 08 '15 at 00:36
  • Take a look at how you are setting up your instance variables. This seems to be a common problem as of late with anyone using iVars. http://stackoverflow.com/questions/13566862/where-to-put-ivars-in-modern-objective-c What code sample looks like how you declare your ivars? – Skyler Lauren Apr 08 '15 at 00:54
  • My player is a property like such: @property (strong, nonatomic) AVQueuePlayer *qPlayer; in the .h The player items are initialized the function in my sample code – Dhananjay Suresh Apr 08 '15 at 01:03
  • But are you using any instance variables or is everything a property? Your original code showed IVars. – Skyler Lauren Apr 08 '15 at 01:05
  • Only arrays (with urls), some uiimages are ivars. The player is the only property besides objects from the interface builder. – Dhananjay Suresh Apr 08 '15 at 01:07
  • I just wanted to make clear that the notification always gets called. But the only scenarios that the advancing to next item works is if 1. i used the seekTotime to move somewhere into the track 2. if i call postNotification of nsnotificationcenter or 3 if call the same function from like a uibutton. – Dhananjay Suresh Apr 08 '15 at 01:12
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];

});
Tyler
  • 166
  • 8
0

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];
    }
});
}
Opal
  • 81,889
  • 28
  • 189
  • 210
Dhananjay Suresh
  • 1,030
  • 1
  • 14
  • 28
0

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…

Moose
  • 2,607
  • 24
  • 23