Skip to content

Instantly share code, notes, and snippets.

@GUIEEN
Forked from shaps80/AVPlayer+Scrubbing.swift
Created August 4, 2021 18:43
Show Gist options
  • Select an option

  • Save GUIEEN/ebed4f6a9c66e0b2efda79bbc192bab9 to your computer and use it in GitHub Desktop.

Select an option

Save GUIEEN/ebed4f6a9c66e0b2efda79bbc192bab9 to your computer and use it in GitHub Desktop.

Revisions

  1. @shaps80 shaps80 revised this gist Dec 30, 2017. No changes.
  2. @shaps80 shaps80 created this gist Dec 30, 2017.
    62 changes: 62 additions & 0 deletions AVPlayer+Scrubbing.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    public enum Direction {
    case forward
    case backward
    }

    internal var player: AVPlayer?

    private var isSeekInProgress = false
    private var chaseTime = kCMTimeZero
    private var preferredFrameRate: Float = 23.98

    public func seek(to time: CMTime) {
    seekSmoothlyToTime(newChaseTime: time)
    }

    public func stepByFrame(in direction: Direction) {
    let frameRate = preferredFrameRate
    ?? player?.currentItem?.tracks
    .first(where: { $0.assetTrack.mediaType == .video })?
    .currentVideoFrameRate
    ?? -1

    let time = player?.currentItem?.currentTime() ?? kCMTimeZero
    let seconds = Double(1) / Double(frameRate)
    let timescale = Double(seconds) / Double(time.timescale) < 1 ? 600 : time.timescale
    let oneFrame = CMTime(seconds: seconds, preferredTimescale: timescale)
    let next = direction == .forward
    ? CMTimeAdd(time, oneFrame)
    : CMTimeSubtract(time, oneFrame)

    seekSmoothlyToTime(newChaseTime: next)
    }

    private func seekSmoothlyToTime(newChaseTime: CMTime) {
    if CMTimeCompare(newChaseTime, chaseTime) != 0 {
    chaseTime = newChaseTime

    if !isSeekInProgress {
    trySeekToChaseTime()
    }
    }
    }

    private func trySeekToChaseTime() {
    guard player?.status == .readyToPlay else { return }
    actuallySeekToTime()
    }

    private func actuallySeekToTime() {
    isSeekInProgress = true
    let seekTimeInProgress = chaseTime

    player?.seek(to: seekTimeInProgress, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) { [weak self] _ in
    guard let `self` = self else { return }

    if CMTimeCompare(seekTimeInProgress, self.chaseTime) == 0 {
    self.isSeekInProgress = false
    } else {
    self.trySeekToChaseTime()
    }
    }
    }