Swift AVPlayer seekToTime issue

12,048

Solution 1

For AVPlayer, there are different ways to let the play seeking time;

player.seekToTime(<#time: CMTime#CMTime#>)

for this method,player will seek to seekingTime quickly but not seeking the exactly where the seekingTime is, it means there will be some offset of the seekingTime.

Apple's Doc

Use this method to seek to a specified time for the current player item.

The time seeked to may differ from the specified time for efficiency.

For sample accurate seeking see seekToTime:toleranceBefore:toleranceAfter:.

player.seekToTime(<#T##time: CMTime##CMTime#>, completionHandler: <#T##(Bool) -> Void#>)

for this method,player will seek to seekingTime quickly and also,with the offset,you can do something with completionHandler

Apple's Doc

Use this method to seek to a specified time for the current player item and to be notified when the seek operation is complete.

The completion handler for any prior seek request that is still in process will be invoked immediately with the finished parameter

set to NO. If the new request completes without being interrupted by another seek request or by any other operation the specified

completion handler will be invoked with the finished parameter set to YES.

If you want to seeking more accurate, use the following method instead

player.seekToTime(<#T##time: CMTime##CMTime#>, toleranceBefore: <#T##CMTime#>, toleranceAfter: <#T##CMTime#>)

player.seekToTime(<#T##time: CMTime##CMTime#>, toleranceBefore: <#T##CMTime#>, toleranceAfter: <#T##CMTime#>, completionHandler: <#T##(Bool) -> Void#>)

this method will let the player seeking to specified time you asking,but will be slower(well..mostly depending on current media)

Apple's Doc

Use this method to seek to a specified time for the current player item.

The time seeked to will be within the range [time-toleranceBefore, time+toleranceAfter] and may differ from the specified time for efficiency.

Pass kCMTimeZero for both toleranceBefore and toleranceAfter to request sample accurate seeking which may incur additional decoding delay.

Messaging this method with beforeTolerance:kCMTimePositiveInfinity and afterTolerance:kCMTimePositiveInfinity is the same as messaging seekToTime: directly.

i've notice that current you are using

avPlayer?.currentItem?.seekToTime(CMTimeMakeWithSeconds(secondo, frameRate))

you can try to use this method to seeking the specified time (slower of course)

avPlayer?.currentItem?.seekToTime(CMTimeMakeWithSeconds(secondo, frameRate), toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)

Wish could help u out

Solution 2

In Swift 4.2 and Xcode 10.1

player?.seek(to: CMTimeMakeWithSeconds(100, preferredTimescale: 1), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero)

Solution 3

I doubt any of the answers supplied actually worked. Try this:

Technical Q&A QA1820 How do I achieve smooth video scrubbing with AVPlayer seekToTime:?

Q: My app allows the user to scrub video files using a slider control in combination with AVPlayer seekToTime: but there is a considerable lag in the display of the video frames. How can I achieve smoother scrubbing?

A: Avoid making calls to AVPlayer seekToTime: in rapid succession. This will cancel the seeks in progress, resulting in a lot of seeking and not a lot of displaying of the target frames. Instead, use the completion handler variant of AVPlayer seekToTime:, and wait for a seek in progress to complete first before issuing another. Listing 1 and Listing 2 give examples of this technique (Note: these examples assume a valid player object has been created and the player's current item status is being maintained via key-value observing. See the AV Foundation Programming Guide for more information):

Using the completion handler variant of AVPlayer seekToTime: for smoother scrubbing (Swift).

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

}
Share:
12,048
Alessandro Zago
Author by

Alessandro Zago

Updated on June 07, 2022

Comments

  • Alessandro Zago
    Alessandro Zago almost 2 years

    i have this code :

    let punto = sender.locationInView(self.barraVideo).x
            let larghezzaImg : Double = Double((self.barraVideoInvisTocco?.frame.width)!)
    
            let percTocco : Double = (100 / larghezzaImg) * Double(punto)
    
            let lunghezzaVideo : Double = Double(CGFloat((avPlayer?.currentItem?.asset.duration.value)!) / CGFloat(1000.0));
            let frameRate : Int32 = (avPlayer?.currentItem?.currentTime().timescale)!
    
            let secondo : Float64 = Float64((lunghezzaVideo/100)*percTocco)
            print("Val : \(secondo)");
    
            avPlayer?.currentItem?.seekToTime(CMTimeMakeWithSeconds(secondo, frameRate))
    
            avPlayer?.play()
            videoInPlay = true;
            playPauseBtn!.image = UIImage(named: "iconaPausa.png")!;
    

    That recive a x cordinate of a tap on the seekBar and seek the video. The code works with logs video but work very bad with short videos. When i tap on the short video the AVPlayer seek only near the begining or near the end of the video, not in the middle

    print :

    Val : 3.36008475976495
    Val : 7.14189817632069
    Val : 13.3303201306846
    Val : 3.70388597945183
    Val : 3.93308679257642
    Val : 3.24548435320265
    Val : 3.70388597945183
    Val : 18.8311396456748
    Val : 27.0823689181601
    Val : 40.1468152662618
    Val : 51.3776551093667
    Val : 17.2267339538027
    Val : 9.54850671412889
    Val : 23.4151559081666
    Val : 37.6256063218913
    

    The val rappresents the right value of seconds where i wont to seek. Sorry for bad english, i'm italian