SDAnimatedImagePlayer.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #import "SDAnimatedImagePlayer.h"
  9. #import "NSImage+Compatibility.h"
  10. #import "SDDisplayLink.h"
  11. #import "SDDeviceHelper.h"
  12. #import "SDInternalMacros.h"
  13. @interface SDAnimatedImagePlayer () {
  14. SD_LOCK_DECLARE(_lock);
  15. NSRunLoopMode _runLoopMode;
  16. }
  17. @property (nonatomic, strong, readwrite) UIImage *currentFrame;
  18. @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
  19. @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
  20. @property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider;
  21. @property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
  22. @property (nonatomic, assign) NSTimeInterval currentTime;
  23. @property (nonatomic, assign) BOOL bufferMiss;
  24. @property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
  25. @property (nonatomic, assign) BOOL shouldReverse;
  26. @property (nonatomic, assign) NSUInteger maxBufferCount;
  27. @property (nonatomic, strong) NSOperationQueue *fetchQueue;
  28. @property (nonatomic, strong) SDDisplayLink *displayLink;
  29. @end
  30. @implementation SDAnimatedImagePlayer
  31. - (instancetype)initWithProvider:(id<SDAnimatedImageProvider>)provider {
  32. self = [super init];
  33. if (self) {
  34. NSUInteger animatedImageFrameCount = provider.animatedImageFrameCount;
  35. // Check the frame count
  36. if (animatedImageFrameCount <= 1) {
  37. return nil;
  38. }
  39. self.totalFrameCount = animatedImageFrameCount;
  40. // Get the current frame and loop count.
  41. self.totalLoopCount = provider.animatedImageLoopCount;
  42. self.animatedProvider = provider;
  43. self.playbackRate = 1.0;
  44. SD_LOCK_INIT(_lock);
  45. #if SD_UIKIT
  46. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  47. #endif
  48. }
  49. return self;
  50. }
  51. + (instancetype)playerWithProvider:(id<SDAnimatedImageProvider>)provider {
  52. SDAnimatedImagePlayer *player = [[SDAnimatedImagePlayer alloc] initWithProvider:provider];
  53. return player;
  54. }
  55. #pragma mark - Life Cycle
  56. - (void)dealloc {
  57. #if SD_UIKIT
  58. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  59. #endif
  60. }
  61. - (void)didReceiveMemoryWarning:(NSNotification *)notification {
  62. [_fetchQueue cancelAllOperations];
  63. NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
  64. NSNumber *currentFrameIndex = @(self.currentFrameIndex);
  65. SD_LOCK(self->_lock);
  66. NSArray *keys = self.frameBuffer.allKeys;
  67. // only keep the next frame for later rendering
  68. for (NSNumber * key in keys) {
  69. if (![key isEqualToNumber:currentFrameIndex]) {
  70. [self.frameBuffer removeObjectForKey:key];
  71. }
  72. }
  73. SD_UNLOCK(self->_lock);
  74. }];
  75. [_fetchQueue addOperation:operation];
  76. }
  77. #pragma mark - Private
  78. - (NSOperationQueue *)fetchQueue {
  79. if (!_fetchQueue) {
  80. _fetchQueue = [[NSOperationQueue alloc] init];
  81. _fetchQueue.maxConcurrentOperationCount = 1;
  82. _fetchQueue.name = @"com.hackemist.SDAnimatedImagePlayer.fetchQueue";
  83. }
  84. return _fetchQueue;
  85. }
  86. - (NSMutableDictionary<NSNumber *,UIImage *> *)frameBuffer {
  87. if (!_frameBuffer) {
  88. _frameBuffer = [NSMutableDictionary dictionary];
  89. }
  90. return _frameBuffer;
  91. }
  92. - (SDDisplayLink *)displayLink {
  93. if (!_displayLink) {
  94. _displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)];
  95. [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode];
  96. [_displayLink stop];
  97. }
  98. return _displayLink;
  99. }
  100. - (void)setRunLoopMode:(NSRunLoopMode)runLoopMode {
  101. if ([_runLoopMode isEqual:runLoopMode]) {
  102. return;
  103. }
  104. if (_displayLink) {
  105. if (_runLoopMode) {
  106. [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runLoopMode];
  107. }
  108. if (runLoopMode.length > 0) {
  109. [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode];
  110. }
  111. }
  112. _runLoopMode = [runLoopMode copy];
  113. }
  114. - (NSRunLoopMode)runLoopMode {
  115. if (!_runLoopMode) {
  116. _runLoopMode = [[self class] defaultRunLoopMode];
  117. }
  118. return _runLoopMode;
  119. }
  120. #pragma mark - State Control
  121. - (void)setupCurrentFrame {
  122. if (self.currentFrameIndex != 0) {
  123. return;
  124. }
  125. if (self.playbackMode == SDAnimatedImagePlaybackModeReverse ||
  126. self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
  127. self.currentFrameIndex = self.totalFrameCount - 1;
  128. }
  129. if (!self.currentFrame && [self.animatedProvider isKindOfClass:[UIImage class]]) {
  130. UIImage *image = (UIImage *)self.animatedProvider;
  131. // Cache the poster image if available, but should not callback to avoid caller thread issues
  132. #if SD_MAC
  133. UIImage *posterFrame = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
  134. #else
  135. UIImage *posterFrame = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
  136. #endif
  137. if (posterFrame) {
  138. // HACK: The first frame should not check duration and immediately display
  139. self.needsDisplayWhenImageBecomesAvailable = YES;
  140. SD_LOCK(self->_lock);
  141. self.frameBuffer[@(self.currentFrameIndex)] = posterFrame;
  142. SD_UNLOCK(self->_lock);
  143. }
  144. }
  145. }
  146. - (void)resetCurrentFrameStatus {
  147. // These should not trigger KVO, user don't need to receive an `index == 0, image == nil` callback.
  148. _currentFrame = nil;
  149. _currentFrameIndex = 0;
  150. _currentLoopCount = 0;
  151. _currentTime = 0;
  152. _bufferMiss = NO;
  153. _needsDisplayWhenImageBecomesAvailable = NO;
  154. }
  155. - (void)clearFrameBuffer {
  156. SD_LOCK(_lock);
  157. [_frameBuffer removeAllObjects];
  158. SD_UNLOCK(_lock);
  159. }
  160. #pragma mark - Animation Control
  161. - (void)startPlaying {
  162. [self.displayLink start];
  163. // Setup frame
  164. [self setupCurrentFrame];
  165. // Calculate max buffer size
  166. [self calculateMaxBufferCount];
  167. }
  168. - (void)stopPlaying {
  169. [_fetchQueue cancelAllOperations];
  170. // Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
  171. [_displayLink stop];
  172. // We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
  173. [self resetCurrentFrameStatus];
  174. }
  175. - (void)pausePlaying {
  176. [_fetchQueue cancelAllOperations];
  177. [_displayLink stop];
  178. }
  179. - (BOOL)isPlaying {
  180. return _displayLink.isRunning;
  181. }
  182. - (void)seekToFrameAtIndex:(NSUInteger)index loopCount:(NSUInteger)loopCount {
  183. if (index >= self.totalFrameCount) {
  184. return;
  185. }
  186. self.currentFrameIndex = index;
  187. self.currentLoopCount = loopCount;
  188. self.currentFrame = [self.animatedProvider animatedImageFrameAtIndex:index];
  189. [self handleFrameChange];
  190. }
  191. #pragma mark - Core Render
  192. - (void)displayDidRefresh:(SDDisplayLink *)displayLink {
  193. // If for some reason a wild call makes it through when we shouldn't be animating, bail.
  194. // Early return!
  195. if (!self.isPlaying) {
  196. return;
  197. }
  198. NSUInteger totalFrameCount = self.totalFrameCount;
  199. if (totalFrameCount <= 1) {
  200. // Total frame count less than 1, wrong configuration and stop animating
  201. [self stopPlaying];
  202. return;
  203. }
  204. NSTimeInterval playbackRate = self.playbackRate;
  205. if (playbackRate <= 0) {
  206. // Does not support <= 0 play rate
  207. [self stopPlaying];
  208. return;
  209. }
  210. // Calculate refresh duration
  211. NSTimeInterval duration = self.displayLink.duration;
  212. NSUInteger currentFrameIndex = self.currentFrameIndex;
  213. NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
  214. if (self.playbackMode == SDAnimatedImagePlaybackModeReverse) {
  215. nextFrameIndex = currentFrameIndex == 0 ? (totalFrameCount - 1) : (currentFrameIndex - 1) % totalFrameCount;
  216. } else if (self.playbackMode == SDAnimatedImagePlaybackModeBounce ||
  217. self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
  218. if (currentFrameIndex == 0) {
  219. self.shouldReverse = NO;
  220. } else if (currentFrameIndex == totalFrameCount - 1) {
  221. self.shouldReverse = YES;
  222. }
  223. nextFrameIndex = self.shouldReverse ? (currentFrameIndex - 1) : (currentFrameIndex + 1);
  224. nextFrameIndex %= totalFrameCount;
  225. }
  226. // Check if we need to display new frame firstly
  227. BOOL bufferFull = NO;
  228. if (self.needsDisplayWhenImageBecomesAvailable) {
  229. UIImage *currentFrame;
  230. SD_LOCK(_lock);
  231. currentFrame = self.frameBuffer[@(currentFrameIndex)];
  232. SD_UNLOCK(_lock);
  233. // Update the current frame
  234. if (currentFrame) {
  235. SD_LOCK(_lock);
  236. // Remove the frame buffer if need
  237. if (self.frameBuffer.count > self.maxBufferCount) {
  238. self.frameBuffer[@(currentFrameIndex)] = nil;
  239. }
  240. // Check whether we can stop fetch
  241. if (self.frameBuffer.count == totalFrameCount) {
  242. bufferFull = YES;
  243. }
  244. SD_UNLOCK(_lock);
  245. // Update the current frame immediately
  246. self.currentFrame = currentFrame;
  247. [self handleFrameChange];
  248. self.bufferMiss = NO;
  249. self.needsDisplayWhenImageBecomesAvailable = NO;
  250. }
  251. else {
  252. self.bufferMiss = YES;
  253. }
  254. }
  255. // Check if we have the frame buffer
  256. if (!self.bufferMiss) {
  257. // Then check if timestamp is reached
  258. self.currentTime += duration;
  259. NSTimeInterval currentDuration = [self.animatedProvider animatedImageDurationAtIndex:currentFrameIndex];
  260. currentDuration = currentDuration / playbackRate;
  261. if (self.currentTime < currentDuration) {
  262. // Current frame timestamp not reached, prefetch frame in advance.
  263. [self prefetchFrameAtIndex:currentFrameIndex
  264. nextIndex:nextFrameIndex
  265. bufferFull:bufferFull];
  266. return;
  267. }
  268. // Otherwise, we should be ready to display next frame
  269. self.needsDisplayWhenImageBecomesAvailable = YES;
  270. self.currentFrameIndex = nextFrameIndex;
  271. self.currentTime -= currentDuration;
  272. NSTimeInterval nextDuration = [self.animatedProvider animatedImageDurationAtIndex:nextFrameIndex];
  273. nextDuration = nextDuration / playbackRate;
  274. if (self.currentTime > nextDuration) {
  275. // Do not skip frame
  276. self.currentTime = nextDuration;
  277. }
  278. // Update the loop count when last frame rendered
  279. if (nextFrameIndex == 0) {
  280. // Update the loop count
  281. self.currentLoopCount++;
  282. [self handleLoopChange];
  283. // if reached the max loop count, stop animating, 0 means loop indefinitely
  284. NSUInteger maxLoopCount = self.totalLoopCount;
  285. if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
  286. [self stopPlaying];
  287. return;
  288. }
  289. }
  290. }
  291. // Since we support handler, check animating state again
  292. if (!self.isPlaying) {
  293. return;
  294. }
  295. [self prefetchFrameAtIndex:currentFrameIndex
  296. nextIndex:nextFrameIndex
  297. bufferFull:bufferFull];
  298. }
  299. // Check if we should prefetch next frame or current frame
  300. // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
  301. // Or, most cases, the decode speed is faster than render speed, we fetch next frame
  302. - (void)prefetchFrameAtIndex:(NSUInteger)currentIndex
  303. nextIndex:(NSUInteger)nextIndex
  304. bufferFull:(BOOL)bufferFull {
  305. NSUInteger fetchFrameIndex = currentIndex;
  306. UIImage *fetchFrame = nil;
  307. if (!self.bufferMiss) {
  308. fetchFrameIndex = nextIndex;
  309. SD_LOCK(_lock);
  310. fetchFrame = self.frameBuffer[@(nextIndex)];
  311. SD_UNLOCK(_lock);
  312. }
  313. if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
  314. // Prefetch next frame in background queue
  315. id<SDAnimatedImageProvider> animatedProvider = self.animatedProvider;
  316. @weakify(self);
  317. NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
  318. @strongify(self);
  319. if (!self) {
  320. return;
  321. }
  322. UIImage *frame = [animatedProvider animatedImageFrameAtIndex:fetchFrameIndex];
  323. BOOL isAnimating = self.displayLink.isRunning;
  324. if (isAnimating) {
  325. SD_LOCK(self->_lock);
  326. self.frameBuffer[@(fetchFrameIndex)] = frame;
  327. SD_UNLOCK(self->_lock);
  328. }
  329. }];
  330. [self.fetchQueue addOperation:operation];
  331. }
  332. }
  333. - (void)handleFrameChange {
  334. if (self.animationFrameHandler) {
  335. self.animationFrameHandler(self.currentFrameIndex, self.currentFrame);
  336. }
  337. }
  338. - (void)handleLoopChange {
  339. if (self.animationLoopHandler) {
  340. self.animationLoopHandler(self.currentLoopCount);
  341. }
  342. }
  343. #pragma mark - Util
  344. - (void)calculateMaxBufferCount {
  345. NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
  346. if (bytes == 0) bytes = 1024;
  347. NSUInteger max = 0;
  348. if (self.maxBufferSize > 0) {
  349. max = self.maxBufferSize;
  350. } else {
  351. // Calculate based on current memory, these factors are by experience
  352. NSUInteger total = [SDDeviceHelper totalMemory];
  353. NSUInteger free = [SDDeviceHelper freeMemory];
  354. max = MIN(total * 0.2, free * 0.6);
  355. }
  356. NSUInteger maxBufferCount = (double)max / (double)bytes;
  357. if (!maxBufferCount) {
  358. // At least 1 frame
  359. maxBufferCount = 1;
  360. }
  361. self.maxBufferCount = maxBufferCount;
  362. }
  363. + (NSString *)defaultRunLoopMode {
  364. // Key off `activeProcessorCount` (as opposed to `processorCount`) since the system could shut down cores in certain situations.
  365. return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
  366. }
  367. @end