@Override public void doFrame(long frameTimeNanos) {
    postFrameCallback();
    if (composition == null || !isRunning()) {
      return;
    }

    if (L.isTraceEnabled()) {
      L.beginSection("LottieValueAnimator#doFrame");
    }
    long timeSinceFrame = lastFrameTimeNs == 0 ? 0 : frameTimeNanos - lastFrameTimeNs;
    float frameDuration = getFrameDurationNs();
    float dFrames = timeSinceFrame / frameDuration;

    float newFrameRaw = frameRaw + (isReversed() ? -dFrames : dFrames);
    boolean ended = !MiscUtils.contains(newFrameRaw, getMinFrame(), getMaxFrame());
    float previousFrameRaw = frameRaw;
    frameRaw = MiscUtils.clamp(newFrameRaw, getMinFrame(), getMaxFrame());
    frame = useCompositionFrameRate ? (float) Math.floor(frameRaw) : frameRaw;

    lastFrameTimeNs = frameTimeNanos;

    if (ended) {
      if (getRepeatCount() != INFINITE && repeatCount >= getRepeatCount()) {
        frameRaw = speed < 0 ? getMinFrame() : getMaxFrame();
        frame = frameRaw;
        removeFrameCallback();
        checkNotifyUpdate(previousFrameRaw);
        notifyEnd(isReversed());
      } else {
        if (getRepeatMode() == REVERSE) {
          speedReversedForRepeatMode = !speedReversedForRepeatMode;
          reverseAnimationSpeed();
        } else {
          frameRaw = isReversed() ? getMaxFrame() : getMinFrame();
          frame = frameRaw;
        }
        lastFrameTimeNs = frameTimeNanos;
        checkNotifyUpdate(previousFrameRaw);
        notifyRepeat();
        repeatCount++;
      }
    } else {
      checkNotifyUpdate(previousFrameRaw);
    }

    verifyFrame();
    if (L.isTraceEnabled()) {
      L.endSection("LottieValueAnimator#doFrame");
    }
  }
Returns the current value of the currently playing animation taking into
account direction, min and max frames.
lottie/src/main/java/com/airbnb/lottie/utils/LottieValueAnimator.java