Tyler
Tyler

Reputation: 2386

Better way to do skip to previous with AVQueuePlayer?

I am using an AVQueuePlayer in my app. I have a two swipe gestures to skip to next and skip to previous avplayeritems. Right now to do skip to next I am just calling advanceToNextItem on the avqueueplayer which works well.

However the skip to previous I am removing all items and adding them back in with the previous video up front, this is really slow when skipping to previous multiple times. How can I make this faster just like calling advanceToNextItem?

My code looks like this:

func skipToPrevious() {
    queuePlayer.removeAllItems()
    // move the previous playerItem to the front of the list then add them all back in
    for playerItem in playerItems:
        queuePlayer.insertItem(playerItem, afterItem: nil)

}

Upvotes: 21

Views: 9887

Answers (8)

Guitarman4
Guitarman4

Reputation: 43

Found a simple way around this whilst still using AVQueuePlayer.

func playprevioustrack() {
    
    let currenttime = myQueuePlayer?.currentItem?.currentTime().value ?? 0
    
    if currenttime > 2260536792 {
        myQueuePlayer?.seek(to: .zero)
        return
    }
    
    let trackNumber = avItems.firstIndex(of: (myQueuePlayer?.currentItem)!)!
    
    if trackNumber < 1 {
        myQueuePlayer?.seek(to: .zero)
        return
    }
    
    myQueuePlayer?.insert(avItems[trackNumber - 1], after: myQueuePlayer?.currentItem)
    MusicHelper.sharedHelper.myQueuePlayer?.advanceToNextItem()
    
    myQueuePlayer?.insert(avItems[trackNumber], after: myQueuePlayer?.currentItem)
}

With the above example, if you tap twice quickly it will play previous track. If you tap once will restart current one. It basically is putting the previous track after the current one and then replacing the current one after playing the previous one.

Upvotes: 1

CyberMoai
CyberMoai

Reputation: 506

AssetPlayer's previousTrack() function in Apple's Becoming a now playable app sample code doesn't work so here's the solution I came up with:

private func previousTrack() {
    if case .stopped = playerState { return }
    
    let previousIndex = playerItems.count - player.items().count - 1
    
    guard previousIndex >= 0, previousIndex < playerItems.count else { return }
    
    let previousItem = playerItems[previousIndex]
    previousItem.seek(to: .zero, completionHandler:nil)
    
    guard let currentItem = player.currentItem else { return }
    currentItem.seek(to: .zero, completionHandler: nil)
    
    if player.canInsert(previousItem, after: nil) {
        player.insert(previousItem, after: nil)
    }
    
    player.advanceToNextItem()
    
    if player.canInsert(currentItem, after: nil) {
        player.insert(currentItem, after: nil)
    }
    
    handlePlaybackChange()
}

Basically, get the previous item, insert it next in the queue, skip forward, and re-add the current item next.

Upvotes: 0

Bogdan Vorobiyovskiy
Bogdan Vorobiyovskiy

Reputation: 11

You can replace your current item with the previous and add the item, that was replaced:

let previousItem = AVPlayerItem(url: url )
let currentItem = AVPlayerItem(url: currentUrl)

 player.replaceCurrentItem(with: previousItem)
    if player.canInsert(currentItem, after: previousItem) {
         player.insert(currentItem, after: previousItem)
    }

// OR like this

 let previousItem = AVPlayerItem(url: url )
 if let currentItem = player.currentItem {
   player.replaceCurrentItem(with: previousItem)
     if player.canInsert(currentItem, after: previousItem) {
         player.insert(currentItem, after: previousItem)
     }
  }

Upvotes: 1

Max Efimov
Max Efimov

Reputation: 21

  1. Insert first item after first item:
    player.insert(player.items()[0], after: player.items()[0]).
  2. Insert recreated previous item after first item:
    player.insert(prevItem, after: player.items()[0])
  3. Call player.advanceToNextItem().

Upvotes: -1

JAL
JAL

Reputation: 42449

It seems like AVQueuePlayer removes the current item from the play queue when calling advanceToNextItem. Theoretically, there is no way to get this item back without rebuilding the queue.

What you could do is use a standard AVPlayer, have an array of AVPlayerItems, and an integer index which keeps the index of the current track.

Swift 3:

let player = AVPlayer()
let playerItems = [AVPlayerItem]() // your array of items
var currentTrack = 0

func previousTrack() {
    if currentTrack - 1 < 0 {
        currentTrack = (playerItems.count - 1) < 0 ? 0 : (playerItems.count - 1)
    } else {
        currentTrack -= 1
    }

    playTrack()
}

func nextTrack() {
    if currentTrack + 1 > playerItems.count {
        currentTrack = 0
    } else {
        currentTrack += 1;
    }

    playTrack()
}

func playTrack() {

    if playerItems.count > 0 {
        player.replaceCurrentItem(with: playerItems[currentTrack])
        player.play()
    }
}

Swift 2.x:

func previousTrack() {
    if currentTrack-- < 0 {
        currentTrack = (playerItems.count - 1) < 0 ? 0 : (playerItems.count - 1)
    } else {
        currentTrack--
    }

    playTrack()
}

func nextTrack() {
    if currentTrack++ > playerItems.count {
        currentTrack = 0
    } else {
        currentTrack++;
    }

    playTrack()
}

func playTrack() {

    if playerItems.count > 0 {
        player.replaceCurrentItemWithPlayerItem(playerItems[currentTrack])
        player.play()
    }
}

Upvotes: 30

Pradeep K
Pradeep K

Reputation: 3661

I am thinking a very different approach which is in fact in terms of advanceToNextItem method. You said that advanceToNextItem works fine. So I am wondering if you can implement the skip to previous using advanceToNextItem itself but by pointing the queue two items backwards of the current playing item.

E.g. if you your queue is this and the bold one is the current item

A B C D E F G H

Then set the current item to C and then use advanceToNextItem so that it plays D.

Not sure how your advanceToNextItem is implemented though. So it depends on that.

Upvotes: 2

Allen
Allen

Reputation: 6505

the replaceCurrentItemWithPlayerItem had limitation and should be avoided when possible, in Apple's document, it states "The new item must have the same compositor as the item it replaces, or have no compositor."

instead insert the playerItems one by one using a loop, just create an AVQueuePlayer would be faster:

func skipToPrevious() {
    queuePlayer = AVQueuePlayer.queuePlayerWithItems(playerItem)
}

Upvotes: 2

Casey
Casey

Reputation: 6701

does the queue handle more than one skip forward smoothly? if so, you could constantly re-insert the previous video back into the queue at index n+2. when the user wishes to play the previous track, you would skip forward twice.

if playing from track A to F without any skips, the pattern would look like this:

A B C D E F
B C D E F

// re-insert A after next track
B C A D E F
C A D E F

// remove A then re-insert B
C D E F
C D B E F
D B E F

// remove B then re-insert C
D E F
D E C F
E C F

// remove C then re-insert D
E F
E F D
F D

// remove D then re-insert E
F
FE

using this pattern you could only smoothly skip backwards once, but it could be modified to allow more.

definitely not an ideal solution, but may work!

Upvotes: 2

Related Questions