跳转到 AVQueuePlayer 上的上一个 AVPlayerItem / 从队列中播放所选项目

16
我正在使用AVQueuePlayer在我的项目中播放一个已经分成不同章节的电视剧。同时,我还希望能够在AVQueuePlayer播放时提供跳转到上一个/下一个章节或选择其他章节的可能性。
使用AVQueuePlayer的advanceToNextItem可以轻松实现跳转到下一个项目,但是没有类似于跳回或从队列中播放特定项目的功能。
因此,我不确定最佳方法是什么:
1.使用AVPlayer而不是AVQueuePlayer,在actionAtItemEnd中调用replaceCurrentItemWithPlayerItem:来播放下一项,并使用“replaceCurrentItemWithPlayerItem”让用户选择某个章节;
2.通过使用'insertItem:afterItem:'和'removeAllItems'重新组织队列或当前播放器;
附加信息: 我将不同视频的路径按照它们应出现的顺序存储在NSArray中; 用户通过按代表章节的按钮来跳转到特定章节。按钮具有标签,也是数组中相应视频的索引。
希望我表达得清楚吗? 有人有这种情况的经验吗? 如果有人知道哪里可以购买一个提供该功能的好的IOS VideoPlayerFramework,我也会感激提供链接。

3
AVQueuePlayer没有针对特定索引位置的寻找方法,这让我感到很惊讶。 - Morkrom
我可以问一下,您是否使用自定义控件来检测用户按了什么键,还是您通过 AVQeuePlayer 中的默认控件成功地检测到了下一个按钮的按压事件? - schmru
8个回答

23
如果你想要你的程序能够播放前一个项目并播放从playeritems(NSArray)中选择的项目,你可以这样做。
- (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];
        }
    }
}

编辑: playerItems是您存储AVPlayerItems的NSMutableArray(NSArray)。


谢谢。我正在做类似的事情。但最终我只使用了一个视频文件和一个指向章节开始时间的指针数组以及seekTo函数。 - Amandir
1
如果你想要无间断地播放,你的解决方案似乎是唯一可行的方法。 - saiday
1
有没有任何方法可以在不清除队列的情况下完成这个? - Salman Khakwani

3

第一个答案从AVQueuePlayer中删除所有项目,并重新填充队列,从传递的迭代器索引开始。这将以前一个项目为起点(假设您传递了正确的索引),以及从该点向前的现有playerItems数组中的其余项目开始新填充的队列,但它不允许多个反转,例如您在第10个轨道上并想回到并重播第9个轨道,然后重播第5个轨道,使用以上方法无法实现。但在这里可以...

-(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];


    }
}

2
以下代码允许您跳转到您的任何项目。没有播放器头部推进。简单明了。playerItemList是包含AVPlayerItem对象的NSArray。
- (void)playAtIndex:(NSInteger)index
{
    [audioPlayer removeAllItems];
    AVPlayerItem* obj = [playerItemList objectAtIndex:index];
    [obj seekToTime:kCMTimeZero];
    [audioPlayer insertItem:obj afterItem:nil];
    [audioPlayer play];
}

这是有道理的。但每次想播放索引歌曲时,您都会从列表中删除所有内容。为什么不在这种情况下使用AVPlayer?虽然我投了+1票。 - Yoon Lee

2

djiovann创建了一个AVQueuePlayer的子类,提供了这个功能。

你可以在github上找到它。

我还没有测试过,但从浏览代码中看来,它似乎完成了任务。此外,代码有很好的文档说明,因此它至少可以作为自定义实现该功能的良好参考(我建议使用类别而不是子类化)。


1
这应该是AVQueuePlayer对象的责任,而不是您的视图控制器本身,因此您应该使其可重用,并通过扩展公开其他答案实现,并以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)
        }
    }
}
}

使用方法(您只需要存储索引和对初始队列玩家项的引用):

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

维护索引的一种方法是观察每个视频项的AVPlayerItemDidPlayToEndTime通知:

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
    }
}

这个观察结果对于其他用例(进度条、当前视频计时器重置等)也非常有用。

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)
            }
        }
    }
}

0

@saiday的答案对我有用,这是他答案的Swift版本

 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)
        }
    }
}

0
如果您想使用AVQueuePlayer从任何索引播放歌曲,则以下代码可以帮助您。
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];
}

在需要播放歌曲时,请调用PlaySongAtIndex函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接