10
10
#import " SDWeakProxy.h"
11
11
#if SD_MAC
12
12
#import < CoreVideo/CoreVideo.h>
13
- #elif SD_IOS || SD_TV
14
- #import < QuartzCore/QuartzCore.h>
15
13
#endif
14
+ #include < mach/mach_time.h>
16
15
17
16
#if SD_MAC
18
17
static CVReturn DisplayLinkCallback (CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
19
18
#endif
20
19
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
+
21
32
#define kSDDisplayLinkInterval 1.0 / 60
22
33
23
34
@interface SDDisplayLink ()
24
35
36
+ @property (nonatomic , assign ) NSTimeInterval previousFireTime;
37
+ @property (nonatomic , assign ) NSTimeInterval nextFireTime;
38
+
25
39
#if SD_MAC
26
40
@property (nonatomic , assign ) CVDisplayLinkRef displayLink;
27
41
@property (nonatomic , assign ) CVTimeStamp outputTime;
@@ -32,7 +46,6 @@ @interface SDDisplayLink ()
32
46
@property (nonatomic , strong ) NSTimer *displayLink;
33
47
@property (nonatomic , strong ) NSRunLoop *runloop;
34
48
@property (nonatomic , copy ) NSRunLoopMode runloopMode;
35
- @property (nonatomic , assign ) NSTimeInterval currentFireDate;
36
49
#endif
37
50
38
51
@end
@@ -78,32 +91,24 @@ + (instancetype)displayLinkWithTarget:(id)target selector:(SEL)sel {
78
91
return displayLink;
79
92
}
80
93
81
- - (CFTimeInterval)duration {
94
+ - (NSTimeInterval )duration {
95
+ NSTimeInterval duration;
82
96
#if SD_MAC
83
97
CVTimeStamp outputTime = self.outputTime ;
84
- NSTimeInterval duration = 0 ;
85
98
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
90
101
#pragma clang diagnostic push
91
102
#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 ();
95
106
} 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 ;
104
109
}
105
110
#endif
106
- if (duration <= 0 ) {
111
+ if (duration < 0 ) {
107
112
duration = kSDDisplayLinkInterval ;
108
113
}
109
114
return duration;
@@ -189,24 +194,25 @@ - (void)stop {
189
194
#else
190
195
[self .displayLink invalidate ];
191
196
#endif
197
+ self.previousFireTime = 0 ;
198
+ self.nextFireTime = 0 ;
192
199
}
193
200
194
201
- (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 ;
201
207
}
202
208
#endif
209
+ #if SD_WATCH
210
+ self.nextFireTime = CFRunLoopTimerGetNextFireDate ((__bridge CFRunLoopTimerRef)self.displayLink );
211
+ #endif
203
212
#pragma clang diagnostic push
204
213
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
205
214
[_target performSelector: _selector withObject: self ];
206
215
#pragma clang diagnostic pop
207
- #if SD_WATCH
208
- self.currentFireDate = CFRunLoopTimerGetNextFireDate ((__bridge CFRunLoopTimerRef)self.displayLink );
209
- #endif
210
216
}
211
217
212
218
@end
@@ -215,11 +221,16 @@ - (void)displayLinkDidRefresh:(id)displayLink {
215
221
static CVReturn DisplayLinkCallback (CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
216
222
// CVDisplayLink callback is not on main queue
217
223
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 ;
220
229
}
230
+ CVTimeStamp outputTime = inOutputTime ? *inOutputTime : *inNow;
221
231
__weak SDDisplayLink *weakObject = object;
222
232
dispatch_async (dispatch_get_main_queue (), ^{
233
+ weakObject.outputTime = outputTime;
223
234
[weakObject displayLinkDidRefresh: (__bridge id )(displayLink)];
224
235
});
225
236
return kCVReturnSuccess ;
0 commit comments