Перейти к предыдущему AVPlayerItem в AVQueuePlayer / Воспроизвести выбранный элемент из очереди

Я играю телевизионное шоу, которое было нарезано на разные главы моего проекта с использованием AVQueuePlayer. Я также хочу предложить возможность перейти к предыдущей / следующей главе или выбрать другую главу на лету, пока AVQueuePlayer уже воспроизводится.

Переход к следующему пункту не является проблемой с advanceToNextItem предоставляется AVQueuePlayer, но нет ничего похожего на пропуск или воспроизведение определенного элемента из очереди.

Так что я не совсем уверен, что будет лучшим подходом здесь:

  1. Используя AVPlayer вместо AVQueuePlayer, вызовите replaceCurrentItemWithPlayerItem: в actionAtItemEnd играть в next Item и просто использовать replaceCurrentItemWithPlayerItem, чтобы позволить пользователю выбрать определенную главу

или же

  1. реорганизовать очередь или текущего игрока, используя "insertItem:afterItem:" и "removeAllItems"

Дополнительная информация: Я сохраняю Путь к различным видео в порядке их появления в NSArray. Пользователь должен переходить к определенным главам, нажимая кнопки, которые представляют главу. Кнопки имеют теги, которые также являются индексами соответствующих видео в массиве.

Надеюсь, я смогу прояснить это? У кого-нибудь есть опыт в этой ситуации? Если кто-нибудь знает, где купить хорошую IOS VideoPlayerFramework, которая обеспечивает функциональность, я также был бы признателен за ссылку.

7 ответов

Решение

Если вы хотите, чтобы ваша программа могла воспроизводить предыдущий элемент и воспроизводить выбранный элемент из ваших элементов плеера (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];
        }
    }
}

edit: playerItems является NSMutableArray(NSArray), где вы храните ваши AVPlayerItems.

Первый ответ удаляет все элементы из AVQueuePlayer и заново заполняет очередь, начиная с итерации, переданной как index arg. Это запустит вновь заполненную очередь с предыдущим элементом (при условии, что вы передали правильный индекс), а также с остальными элементами в существующем массиве 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];


    }
}

Джованн создал подкласс AVQueuePlayer это обеспечивает именно эту функциональность.

Вы можете найти это на github.

Я еще не тестировал его, но, просматривая код, он, кажется, сделал свою работу. Кроме того, код хорошо документирован, поэтому он должен, по крайней мере, служить хорошим ориентиром для пользовательской реализации функциональности (хотя я предлагаю использовать категорию вместо подклассов).

Следующий код позволяет перейти к любому элементу в вашем. Нет продвижения игрока. Легко и просто. playerItemList - это ваш NSArray с объектами AVPlayerItem.

- (void)playAtIndex:(NSInteger)index
{
    [audioPlayer removeAllItems];
    AVPlayerItem* obj = [playerItemList objectAtIndex:index];
    [obj seekToTime:kCMTimeZero];
    [audioPlayer insertItem:obj afterItem:nil];
    [audioPlayer play];
}

Это должна быть ответственность объекта 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
    }
}

Это наблюдение также может быть действительно полезным для других случаев использования (индикатор выполнения, текущий сброс таймера видео...).

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

Ответ @saiday работает для меня, вот быстрая версия его ответа

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

Если вы хотите воспроизвести песню из любого индекса, используя 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 вызывать, когда вы хотите воспроизвести песню.

Другие вопросы по тегам