Skip to Previous AVPlayerItem on AVQueuePlayer / Play selected Item from queue
Asked Answered
R

8

16

I am playing a Tv-show that has been sliced to different chapters on my project using an AVQueuePlayer. I also want to offer the possibility to skip to the previous/next chapter or to select a different chapter on the fly, while the AVQueuePlayer is already playing.

Skipping to next Item is no problem with the advanceToNextItem provided by AVQueuePlayer, but there is nothing alike for skipping back or playing a certainitem from the queue.

So I am not quite sure what would be the best approach here:

  1. Using an AVPlayer instead of AVQueuePlayer, invoke replaceCurrentItemWithPlayerItem: at actionAtItemEnd to play the nextItem and just use 'replaceCurrentItemWithPlayerItem' to let the User select a certain Chapter

or

  1. reorganise the queue or the current player by using 'insertItem:afterItem:' and 'removeAllItems'

Additional information: I store the Path to the different videos in the order they should appear in a NSArray The user is supposed to jump to certain chapters by pressing buttons that represent the chapter. The Buttons have tags, that are also the indexes of the corresponding videos in the array.

Hope I could make myself clear? Anyone having any experience with this situation? If anyone knows where to buy a good IOS VideoPlayerFramework which provides the functionality, I would also appreciate the link.

Razor answered 29/8, 2012 at 11:17 Comment(2)
It fascinates me that there is not a seek to item at index method for AVQueuePlayer.Hombre
May I just ask, did you had custom controls to detect what the user pressed or did you somehow manage to detect next button press from default controls on AVQeuePlayer?Cleaves
V
23

If you want your program can play previous item and play the selected item from your playeritems(NSArray),you can do this.

- (void)playAtIndex:(NSInteger)index
{
    [player removeAllItems];
    for (int i = index; i <playerItems.count; i ++) {
        AVPlayerItem* obj = [playerItems objectAtIndex:i];
        if ([player canInsertItem:obj afterItem:nil]) {
        [obj seekToTime:kCMTimeZero];
        [player insertItem:obj afterItem:nil];
        }
    }
}

edit: playerItems is the NSMutableArray(NSArray) where you store your AVPlayerItems.

Vibes answered 31/8, 2012 at 5:49 Comment(3)
Thank you. I was doing something similar to that. But in the end I ended up using just one videofile and a array with pointers to the beginningtime of the chapters and the seekTo function.Razor
If you tend to play gaplessly, your solution seems the only way to do.Vibes
Is there anyway of doing this without clearing the Queue ?Liard
B
3

The first answer removes all items from AVQueuePlayer, and repopulates queue starting with iteration passed as index arg. This would start the newly populated queue with previous item(assuming you passed correct index) as well the rest of the items in existing playerItems array from that point forward, BUT it does not allow for multiple reverses, e.g. you are on track 10 and want to go back and replay track 9, then replay track 5, with above you cannot accomplish. But here you can...

-(IBAction) prevSongTapped: (id) sender
{
    if (globalSongCount>1){ //keep running tally of items already played
    [self resetAVQueue]; //removes all items from AVQueuePlayer

        for (int i = 1; i < globalSongCount-1; i++){
            [audioQueuePlayer advanceToNextItem];
        }
   globalSongCount--;
   [audioQueuePlayer play];


    }
}
Bus answered 11/10, 2012 at 8:59 Comment(0)
N
2

The following code allows you to jump to any item in your. No playerhead advancing. Plain and simple. playerItemList is your NSArray with AVPlayerItem objects.

- (void)playAtIndex:(NSInteger)index
{
    [audioPlayer removeAllItems];
    AVPlayerItem* obj = [playerItemList objectAtIndex:index];
    [obj seekToTime:kCMTimeZero];
    [audioPlayer insertItem:obj afterItem:nil];
    [audioPlayer play];
}
Nananne answered 10/6, 2013 at 13:20 Comment(1)
this make sense. but then you are removing everything from the list every time you want to play that indexed song. why not just use AVPlayer in this case? I gave you +1 vote thoughSolvency
D
2

djiovann created a subclass of AVQueuePlayer that provides exactly this functionality.

You can find it on github.

I haven't tested it yet but from browsing through the code it seems to get the job done. Also the code is well documented, so it should at least serve as a good reference for a custom implementation of the functionality (I suggest using a category instead of subclassing though).

Delvecchio answered 11/3, 2014 at 11:25 Comment(0)
Q
1

This should be the responsability of the AVQueuePlayer object and not your view controller itself, thus you should make it reusable and expose other answers implementations through an extension and use it in a similar way of advanceToNextItem() :

extension AVQueuePlayer {
func advanceToPreviousItem(for currentItem: Int, with initialItems: [AVPlayerItem]) {
    self.removeAllItems()
    for i in currentItem..<initialItems.count {
        let obj: AVPlayerItem? = initialItems[i]
        if self.canInsert(obj!, after: nil) {
            obj?.seek(to: kCMTimeZero, completionHandler: nil)
            self.insert(obj!, after: nil)
        }
    }
}
}

Usage (you only have to store an index and a reference to initial queue player items) :

self.queuePlayer.advanceToPreviousItem(for: self.currentIndex, with: self.playerItems)

One way of maintaining an index is to observe the AVPlayerItemDidPlayToEndTime notification for each of your video items :

func addDidFinishObserver() {
    queuePlayer.items().forEach { item in
        NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: item)
    }
}

func removeDidFinishObserver() {
    queuePlayer.items().forEach { item in
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)
    }
}

@objc func playerDidFinishPlaying(note: NSNotification) {
    if queuePlayer.currentItem == queuePlayer.items().last {
        print("last item finished")
    } else {
        print("item \(currentIndex) finished")
        currentIndex += 1
    }
}

This observation can also be really useful for other use cases (progress bar, current video timer reset ...).

Quean answered 13/9, 2018 at 13:13 Comment(0)
E
1

Swift 5.2

var playerItems = [AVPlayerItem]()
func play(at itemIndex: Int) {

    player.removeAllItems()

    for index in itemIndex...playerItems.count {
        if let item = playerItems[safe: index] {
            if player.canInsert(item, after: nil) {
                item.seek(to: .zero, completionHandler: nil)
                player.insert(item, after: nil)
            }
        }
    }
}
Erlking answered 25/4, 2020 at 18:13 Comment(0)
O
0

If you want to play a song from any index using AVQueuePlayer.Then this below code can help to.

NSMutableArray *musicListarray (add song that you want to play in queue);
AVQueuePlayer *audioPlayer;
AVPlayerItem *item;

-(void) nextTapped
{
  nowPlayingIndex = nowPlayingIndex + 1;

  if (nowPlayingIndex > musicListarray.count)
  {
  }
  else
  {
    [self playTrack];
  }
}

-(void) playback
{
  if (nowPlayingIndex < 0)
  {
  }
  else
  {
    nowPlayingIndex = nowPlayingIndex - 1;
    [self playTrack];
  }
}

-(void) playTrack
{
  @try
  {
    if (musicArray.count > 0)
    {
      item =[[AVPlayerItem alloc] initWithURL: [NSURL URLWithString:musicListarray
      [nowPlayingIndex]]];
      [audioPlayer replaceCurrentItemWithPlayerItem:item];
      [audioPlayer play];
    }
  }
  @catch (NSException *exception)
  {
  }
}

-(void) PlaySongAtIndex
{
  //yore code...
  nowPlayingIndex = i (stating index from queue)[audioPlayer play];
  [self playTrack];
}

Here PlaySongAtIndex call when you want to play a song.

Ovid answered 17/12, 2016 at 5:33 Comment(0)
I
0

@saiday's answer works for me, here is swift version of his answer

 func play(at index: Int) {
    queue.removeAllItems()
    for i in index..<items.count {
        let obj: AVPlayerItem? = items[i]
        if queue.canInsert(obj!, after: nil) {
            obj?.seek(to: kCMTimeZero, completionHandler: nil)
            queue.insert(obj!, after: nil)
        }
    }
}
Intertidal answered 26/5, 2018 at 9:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.