21

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)

}
JAL
  • 41,701
  • 23
  • 172
  • 300
Tyler
  • 2,346
  • 6
  • 33
  • 59
  • I have been facing this problem for a while now as well. I have resorted to recreating the queue player upon a user tapping for previous item. I then use a delay function, and a progress bar on the screen to allow for the queue player to have at least 1.0 second to recreate the queue and buffer a bit of this item. Of course this doesn't solve your problem of wanting faster speed. I will dig around a bit more. – MikeG Jan 29 '16 at 20:15
  • 1
    The best I have found so far is to hold the avplayeritem in memory after it is done playing so this way it is already buffered then just reinsert the items like I did in the question rather than recreating the queue all together. – Tyler Jan 29 '16 at 20:16
  • An idea -> when a item is playing, store the currently playing item in variable and load it into an AVPlayer. When user taps rewind, you can immediately play the AVPlayer, and then you can simply pause and rewind the AVQueuePlayer behind the scenes, so that when the AVPlayer finishes, the queue player is ready to go... A bit hacky sure – MikeG Jan 29 '16 at 20:19
  • https://github.com/dgiovann/AVQueuePlayerPrevious ...a bit old but this may help you – MikeG Jan 29 '16 at 20:35

6 Answers6

30

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()
    }
}
JAL
  • 41,701
  • 23
  • 172
  • 300
  • This is what I was thinking but was trying to avoid. Is there any example of the source of avqueueplayer? I'd like to build off that – Tyler Jan 26 '16 at 22:30
  • @Tyler the source of `AVQueuePlayer`? I don't think anyone has that as it's internal to Apple. What I can provide for you are the private headers for [`AVQueuePlayer`](https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/AVFoundation.framework/AVQueuePlayer.h) and [`AVQueuePlayerInternal`](https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/AVFoundation.framework/AVQueuePlayerInternal.h). – JAL Jan 26 '16 at 23:02
  • 1
    This is the best solution 100%. Key is to keep the avplayeritems in memory rather than reloading them on the skip to previous. Just need to be careful not too many are kept in memory and some are thrown away eventually. Thanks! – Tyler Feb 01 '16 at 22:23
  • @Sulthan What if you're on track 0? If you're on the first track and try to go back, you'll go out of bounds unless you go tot he last item in the playlist. This was also quick stub code. – JAL Feb 02 '17 at 21:53
  • @JAL You are right, I came to this code from http://stackoverflow.com/questions/42010744/how-to-convert-or-in-swift-3. It seems this could be greatly improved. – Sulthan Feb 02 '17 at 21:55
  • @Sulthan I wrote this over a year ago for Swift 2. When I get the time, I will try to update this answer for Swift 3. Feel free to add your own answer. – JAL Feb 02 '17 at 21:56
  • @Sulthan I just did a quick Swift 3 migration of this answer. I don't see any really glaring issues, but I don't have the time to sit down and re-test this for all edge cases right now. If you see any obvious improvements, feel free to edit my post and make them (I know code edits are generally frowned-upon, but I give you permission). – JAL Feb 02 '17 at 22:05
  • 1
    @JAL Your second version is much better. I would probably use `let previous = (currentTrack - 1 + playerItems.count) % playerItems.count` and `let next = (currentTrack + 1) % playerItems.count` but that's a matter of choice. – Sulthan Feb 02 '17 at 22:08
  • Oh nice use of modulo! That's a great alternative. – JAL Feb 02 '17 at 22:09
  • can you please update your answer and explain how you listen to the events of nextTrack etc. ? – XcodeNOOB Jun 19 '17 at 14:39
  • This is not exactly correct, it **is** possible to get the recently-played-and-removed-from-queue player item by observing `currentItem` key on `AVQueuePlayer`. Such workaround has been demonstrated as an Apple-way to create a seamless audio loop during WWDC 2016 (Treamdill Pattern, [Advances in AVFoundation Playback](https://developer.apple.com/videos/play/wwdc2016/503/)). – Stanisław Chmiela Nov 13 '17 at 09:07
  • @JAL, great solution, I apply this into my AVAudioPlayer app for rewind and forward function button. But I found that ```if currentTrack + 1 > playerItems.count``` in Swift.3 version should be changed to ```if currentTrack + 1 == playerItems.count```. Let's say ```playerItems.count``` is ```3```, then ```currentTrack (0, 1, 2) + 1``` will never bigger than ```3```. Then you will get index out of bound exception when currentTrack increased to 3. I get index out of bound in my project when involving this solution, so I found this. – Zhou Haibo Oct 03 '19 at 14:55
2

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)
}
Mihriban Minaz
  • 3,043
  • 2
  • 32
  • 52
Allen
  • 6,505
  • 16
  • 19
  • 2
    Why would this have any speed improvements? The reason it is slow is because it is not pre buffering the previous avplayeritem – Tyler Jan 26 '16 at 21:44
2

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!

Casey
  • 6,531
  • 24
  • 43
2

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.

Pradeep K
  • 3,671
  • 1
  • 11
  • 15
1

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)
     }
  }
-1
  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().
Max Efimov
  • 21
  • 5