Skip to content

Commit 271e8d8

Browse files
committed
Try to fix display link callback duration calculation issue, using next timestamp for iOS 10+ / watchOS, previous timestamp for iOS 9
1 parent 8bd4e72 commit 271e8d8

File tree

2 files changed

+44
-33
lines changed

2 files changed

+44
-33
lines changed

SDWebImage/Private/SDDisplayLink.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
@property (readonly, nonatomic, weak, nullable) id target;
1717
@property (readonly, nonatomic, assign, nonnull) SEL selector;
18-
@property (readonly, nonatomic) CFTimeInterval duration;
18+
@property (readonly, nonatomic) NSTimeInterval duration; // elapsed time in seconds of previous callback. (or it's first callback, use the time between `start` and callback)
1919
@property (readonly, nonatomic) BOOL isRunning;
2020

2121
+ (nonnull instancetype)displayLinkWithTarget:(nonnull id)target selector:(nonnull SEL)sel;

SDWebImage/Private/SDDisplayLink.m

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,32 @@
1010
#import "SDWeakProxy.h"
1111
#if SD_MAC
1212
#import <CoreVideo/CoreVideo.h>
13-
#elif SD_IOS || SD_TV
14-
#import <QuartzCore/QuartzCore.h>
1513
#endif
14+
#include <mach/mach_time.h>
1615

1716
#if SD_MAC
1817
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
1918
#endif
2019

20+
#if SD_WATCH
21+
static CFTimeInterval CACurrentMediaTime(void)
22+
{
23+
mach_timebase_info_data_t timebase;
24+
mach_timebase_info(&timebase);
25+
26+
uint64_t time = mach_absolute_time();
27+
double seconds = (double)time * (double)timebase.numer / (double)timebase.denom / 1e9;
28+
return seconds;
29+
}
30+
#endif
31+
2132
#define kSDDisplayLinkInterval 1.0 / 60
2233

2334
@interface SDDisplayLink ()
2435

36+
@property (nonatomic, assign) NSTimeInterval previousFireTime;
37+
@property (nonatomic, assign) NSTimeInterval nextFireTime;
38+
2539
#if SD_MAC
2640
@property (nonatomic, assign) CVDisplayLinkRef displayLink;
2741
@property (nonatomic, assign) CVTimeStamp outputTime;
@@ -32,7 +46,6 @@ @interface SDDisplayLink ()
3246
@property (nonatomic, strong) NSTimer *displayLink;
3347
@property (nonatomic, strong) NSRunLoop *runloop;
3448
@property (nonatomic, copy) NSRunLoopMode runloopMode;
35-
@property (nonatomic, assign) NSTimeInterval currentFireDate;
3649
#endif
3750

3851
@end
@@ -78,32 +91,24 @@ + (instancetype)displayLinkWithTarget:(id)target selector:(SEL)sel {
7891
return displayLink;
7992
}
8093

81-
- (CFTimeInterval)duration {
94+
- (NSTimeInterval)duration {
95+
NSTimeInterval duration;
8296
#if SD_MAC
8397
CVTimeStamp outputTime = self.outputTime;
84-
NSTimeInterval duration = 0;
8598
double periodPerSecond = (double)outputTime.videoTimeScale * outputTime.rateScalar;
86-
if (periodPerSecond > 0) {
87-
duration = (double)outputTime.videoRefreshPeriod / periodPerSecond;
88-
}
89-
#elif SD_IOS || SD_TV
99+
duration = (double)outputTime.videoRefreshPeriod / periodPerSecond;
100+
#else
90101
#pragma clang diagnostic push
91102
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
92-
NSTimeInterval duration = 0;
93-
if (@available(iOS 10.0, tvOS 10.0, *)) {
94-
duration = self.displayLink.targetTimestamp - CACurrentMediaTime();
103+
// iOS 10+/watchOS use `nextTime`
104+
if (@available(iOS 10.0, tvOS 10.0, watchOS 2.0, *)) {
105+
duration = self.nextFireTime - CACurrentMediaTime();
95106
} else {
96-
duration = self.displayLink.duration * self.displayLink.frameInterval;
97-
}
98-
#pragma clang diagnostic pop
99-
#else
100-
NSTimeInterval duration = 0;
101-
if (self.displayLink.isValid && self.currentFireDate != 0) {
102-
NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
103-
duration = nextFireDate - self.currentFireDate;
107+
// iOS 9 use `previousTime`
108+
duration = CACurrentMediaTime() - self.previousFireTime;
104109
}
105110
#endif
106-
if (duration <= 0) {
111+
if (duration < 0) {
107112
duration = kSDDisplayLinkInterval;
108113
}
109114
return duration;
@@ -189,24 +194,25 @@ - (void)stop {
189194
#else
190195
[self.displayLink invalidate];
191196
#endif
197+
self.previousFireTime = 0;
198+
self.nextFireTime = 0;
192199
}
193200

194201
- (void)displayLinkDidRefresh:(id)displayLink {
195-
#if SD_MAC
196-
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
197-
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
198-
NSString *runloopMode = self.runloopMode;
199-
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
200-
return;
202+
#if SD_IOS || SD_TV
203+
if (@available(iOS 10.0, tvOS 10.0, *)) {
204+
self.nextFireTime = self.displayLink.targetTimestamp;
205+
} else {
206+
self.previousFireTime = self.displayLink.timestamp;
201207
}
202208
#endif
209+
#if SD_WATCH
210+
self.nextFireTime = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
211+
#endif
203212
#pragma clang diagnostic push
204213
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
205214
[_target performSelector:_selector withObject:self];
206215
#pragma clang diagnostic pop
207-
#if SD_WATCH
208-
self.currentFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
209-
#endif
210216
}
211217

212218
@end
@@ -215,11 +221,16 @@ - (void)displayLinkDidRefresh:(id)displayLink {
215221
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
216222
// CVDisplayLink callback is not on main queue
217223
SDDisplayLink *object = (__bridge SDDisplayLink *)displayLinkContext;
218-
if (inOutputTime) {
219-
object.outputTime = *inOutputTime;
224+
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
225+
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
226+
NSString *runloopMode = object.runloopMode;
227+
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
228+
return kCVReturnSuccess;
220229
}
230+
CVTimeStamp outputTime = inOutputTime ? *inOutputTime : *inNow;
221231
__weak SDDisplayLink *weakObject = object;
222232
dispatch_async(dispatch_get_main_queue(), ^{
233+
weakObject.outputTime = outputTime;
223234
[weakObject displayLinkDidRefresh:(__bridge id)(displayLink)];
224235
});
225236
return kCVReturnSuccess;

0 commit comments

Comments
 (0)