/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.fresco.animation.frame; import com.facebook.common.internal.VisibleForTesting; import com.facebook.fresco.animation.backend.AnimationInformation; /** * Frame scheduler that maps time values to frames. */ public class DropFramesFrameScheduler implements FrameScheduler { private static final int UNSET = -1; private final AnimationInformation mAnimationInformation; private long mLoopDurationMs = UNSET; public DropFramesFrameScheduler(AnimationInformation animationInformation) { mAnimationInformation = animationInformation; } @Override public int getFrameNumberToRender(long animationTimeMs, long lastFrameTimeMs) { if (!isInfiniteAnimation()) { long loopCount = animationTimeMs / getLoopDurationMs(); if (loopCount >= mAnimationInformation.getLoopCount()) { return FRAME_NUMBER_DONE; } } long timeInCurrentLoopMs = animationTimeMs % getLoopDurationMs(); return getFrameNumberWithinLoop(timeInCurrentLoopMs); } @Override public long getLoopDurationMs() { if (mLoopDurationMs != UNSET) { return mLoopDurationMs; } mLoopDurationMs = 0; int frameCount = mAnimationInformation.getFrameCount(); for (int i = 0; i < frameCount; i++) { mLoopDurationMs += mAnimationInformation.getFrameDurationMs(i); } return mLoopDurationMs; } @Override public long getTargetRenderTimeMs(int frameNumber) { long targetRenderTimeMs = 0; for (int i = 0; i < frameNumber; i++) { targetRenderTimeMs += mAnimationInformation.getFrameDurationMs(frameNumber); } return targetRenderTimeMs; } @Override public long getTargetRenderTimeForNextFrameMs(long animationTimeMs) { long loopDurationMs = getLoopDurationMs(); // Sanity check. if (loopDurationMs == 0) { return NO_NEXT_TARGET_RENDER_TIME; } if (!isInfiniteAnimation()) { long loopCount = animationTimeMs / getLoopDurationMs(); if (loopCount >= mAnimationInformation.getLoopCount()) { return NO_NEXT_TARGET_RENDER_TIME; } } // The animation time in the current loop long timePassedInCurrentLoopMs = animationTimeMs % loopDurationMs; // The animation time in the current loop for the next frame long timeOfNextFrameInLoopMs = 0; int frameCount = mAnimationInformation.getFrameCount(); for (int i = 0; i < frameCount && timeOfNextFrameInLoopMs <= timePassedInCurrentLoopMs; i++) { timeOfNextFrameInLoopMs += mAnimationInformation.getFrameDurationMs(i); } // Difference between current time in loop and next frame in loop long timeUntilNextFrameInLoopMs = timeOfNextFrameInLoopMs - timePassedInCurrentLoopMs; // Add the difference to the current animation time return animationTimeMs + timeUntilNextFrameInLoopMs; } @Override public boolean isInfiniteAnimation() { return mAnimationInformation.getLoopCount() == AnimationInformation.LOOP_COUNT_INFINITE; } @VisibleForTesting int getFrameNumberWithinLoop(long timeInCurrentLoopMs) { int frame = 0; long currentDuration = 0; do { currentDuration += mAnimationInformation.getFrameDurationMs(frame); frame++; } while (timeInCurrentLoopMs >= currentDuration); return frame - 1; } }