Reputation: 411
I'm using AVPlayer
to create a video player, but the seekToTime
method is pretty slow. I'm impressed by the seeking performance of Apple's app "Photos". Does anyone have any idea how Apple managed to do such a quick seeking?
Does it has anything to do with threads? I tried to put the seekToTime
call in a dispatch queue, it does't help either.
Upvotes: 5
Views: 2741
Reputation: 561
Achieving smooth seeking like what Apple is able to do in its standard iOS user interface requires a combination of several tricks:
AVPlayerItem
declared capabilities you should adjust seek tolerances as follows:true
from canStepForward
then seek with .zero
for both tolerances. This provides for a step-by-step seek user experience for such content.true
from canPlayFastForward
then seek with .zero
as toleranceBefore
and .positiveInfinity
as toleranceAfter
. This ensures that the player can use available I-frame playlists for a snappier seek user experience (trick mode)..positiveInfinity
for both tolerances, as this ensures the player can reach the desired location as fast as possible (but without additional benefits).Note that the above is not officially documented by Apple AFAIK. I simply had a look at AVPlayerViewController
behavior on iOS 16+, observing how the attached AVPlayer
is used when seeking within different kinds of streams.
You can check our implementation on GitHub for implementation details.
Upvotes: 2
Reputation: 129
This code is taken from: https://developer.apple.com/library/archive/qa/qa1820/_index.html
It helps a little and seek forward looks smooth. But seek backward still tooks too many time (here SeekToTime working smoothly just for forwards, freezy on backwards is explanation why).
import AVFoundation
class MyClass {
var isSeekInProgress = false
let player = <#A valid player object #>
var chaseTime = kCMTimeZero
// your player.currentItem.status
var playerCurrentItemStatus:AVPlayerItemStatus = .Unknown
...
func stopPlayingAndSeekSmoothlyToTime(newChaseTime:CMTime)
{
player.pause()
if CMTimeCompare(newChaseTime, chaseTime) != 0
{
chaseTime = newChaseTime;
if !isSeekInProgress
{
trySeekToChaseTime()
}
}
}
func trySeekToChaseTime()
{
if playerCurrentItemStatus == .Unknown
{
// wait until item becomes ready (KVO player.currentItem.status)
}
else if playerCurrentItemStatus == .ReadyToPlay
{
actuallySeekToTime()
}
}
func actuallySeekToTime()
{
isSeekInProgress = true
let seekTimeInProgress = chaseTime
player.seekToTime(seekTimeInProgress, toleranceBefore: kCMTimeZero,
toleranceAfter: kCMTimeZero, completionHandler:
{ (isFinished:Bool) -> Void in
if CMTimeCompare(seekTimeInProgress, chaseTime) == 0
{
isSeekInProgress = false
}
else
{
trySeekToChaseTime()
}
})
}
}
Upvotes: 0
Reputation: 411
I have found the solution.
If I use seekToTime
to do the scrubbing, it's pretty slow. What I should use is a method called stepByCount
from AVPlayerItem
.
Upvotes: 2