From b0d0e51a7724dcefe3ce1c2dfb334a731b2a385c Mon Sep 17 00:00:00 2001 From: Paige Sun Date: Wed, 6 May 2020 10:35:19 -0700 Subject: [PATCH] iOS: Animated image should animate at the same speed regardless of framerate Summary: In iOS 11, [CADisplayLink](https://developer.apple.com/documentation/quartzcore/cadisplaylink)'s frameInterval was deprecated in favor of preferredFramesPerSecond, but these two properties have different underlying assumptions. - set frameInterval to 2 for 30fps - set preferredFramesPerSecond to 30 for 30fps. When you use preferredFramesPerSecond, assume frameInterval is 1. This fix ensures gifs in component will animate at same speed regardless of framerate. Reviewed By: shergin Differential Revision: D21414014 fbshipit-source-id: 40ab23bab1990cf65d2802830b6835f350999537 --- Libraries/Image/RCTUIImageViewAnimated.m | 26 ++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/Libraries/Image/RCTUIImageViewAnimated.m b/Libraries/Image/RCTUIImageViewAnimated.m index 8402d0e28bb958..c55ece0f82954d 100644 --- a/Libraries/Image/RCTUIImageViewAnimated.m +++ b/Libraries/Image/RCTUIImageViewAnimated.m @@ -181,22 +181,17 @@ - (void)displayDidRefresh:(CADisplayLink *)displayLink { #if TARGET_OS_UIKITFORMAC // TODO: `displayLink.frameInterval` is not available on UIKitForMac - NSTimeInterval duration = displayLink.duration; + NSTimeInterval durationToNextRefresh = displayLink.duration; #else - NSTimeInterval duration = displayLink.duration; + // displaylink.duration -- time interval between frames, assuming maximumFramesPerSecond + // displayLink.preferredFramesPerSecond (>= iOS 10) -- Set to 30 for displayDidRefresh to be called at 30 fps + // displayLink.frameInterval (< iOS 10) -- # of frames that must pass before each displayDidRefresh. After iOS 10, when this is set to 2, preferredFramesPerSecond becomes 30 fps. + // durationToNextRefresh -- Time interval to the next time displayDidRefresh is called + NSTimeInterval durationToNextRefresh; if (@available(iOS 10.0, *)) { - // Per https://developer.apple.com/documentation/quartzcore/cadisplaylink - // displayLink.duration provides the amount of time between frames at the maximumFramesPerSecond - // Thus we need to calculate true duration based on preferredFramesPerSecond - if (displayLink.preferredFramesPerSecond != 0) { - double maxFrameRate = 60.0; // default to 60 fps - if (@available(iOS 10.3, tvOS 10.3, *)) { - maxFrameRate = self.window.screen.maximumFramesPerSecond; - } - duration = duration * displayLink.preferredFramesPerSecond / maxFrameRate; - } // else respect maximumFramesPerSecond - } else { // version < (ios 10) - duration = duration * displayLink.frameInterval; + durationToNextRefresh = displayLink.targetTimestamp - displayLink.timestamp; + } else { + durationToNextRefresh = displayLink.duration * displayLink.frameInterval; } #endif NSUInteger totalFrameCount = self.totalFrameCount; @@ -206,13 +201,14 @@ - (void)displayDidRefresh:(CADisplayLink *)displayLink // Check if we have the frame buffer firstly to improve performance if (!self.bufferMiss) { // Then check if timestamp is reached - self.currentTime += duration; + self.currentTime += durationToNextRefresh; NSTimeInterval currentDuration = [self.animatedImage animatedImageDurationAtIndex:currentFrameIndex]; if (self.currentTime < currentDuration) { // Current frame timestamp not reached, return return; } self.currentTime -= currentDuration; + // nextDuration - duration to wait before displaying next image NSTimeInterval nextDuration = [self.animatedImage animatedImageDurationAtIndex:nextFrameIndex]; if (self.currentTime > nextDuration) { // Do not skip frame