Merge pull request #2895 from ZXIOU/fixAnimatedImageBugs

Fix animated image playback bugs
This commit is contained in:
DreamPiggy 2019-11-22 14:23:37 +08:00 committed by GitHub
commit 2e4107e21e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 52 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -24,6 +24,8 @@
[self.imageView sd_setImageWithURL:self.imageURL [self.imageView sd_setImageWithURL:self.imageURL
placeholderImage:nil placeholderImage:nil
options:SDWebImageProgressiveLoad]; options:SDWebImageProgressiveLoad];
self.imageView.shouldCustomLoopCount = YES;
self.imageView.animationRepeatCount = NSIntegerMax;
} }
- (void)viewDidLoad { - (void)viewDidLoad {

View File

@ -63,6 +63,7 @@
[SDWebImageDownloader sharedDownloader].config.executionOrder = SDWebImageDownloaderLIFOExecutionOrder; [SDWebImageDownloader sharedDownloader].config.executionOrder = SDWebImageDownloaderLIFOExecutionOrder;
self.objects = [NSMutableArray arrayWithObjects: self.objects = [NSMutableArray arrayWithObjects:
@"https://s2.ax1x.com/2019/11/01/KHYIgJ.gif",
@"http://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?0.35786508303135633", // requires HTTP auth, used to demo the NTLM auth @"http://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?0.35786508303135633", // requires HTTP auth, used to demo the NTLM auth
@"http://assets.sbnation.com/assets/2512203/dogflops.gif", @"http://assets.sbnation.com/assets/2512203/dogflops.gif",
@"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif", @"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif",

BIN
SDWebImage/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -23,6 +23,7 @@
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer; @property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
@property (nonatomic, assign) NSTimeInterval currentTime; @property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss; @property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) NSUInteger maxBufferCount; @property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue; @property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) dispatch_semaphore_t lock; @property (nonatomic, strong) dispatch_semaphore_t lock;
@ -165,6 +166,7 @@
self.currentLoopCount = 0; self.currentLoopCount = 0;
self.currentTime = 0; self.currentTime = 0;
self.bufferMiss = NO; self.bufferMiss = NO;
self.needsDisplayWhenImageBecomesAvailable = NO;
[self handleFrameChange]; [self handleFrameChange];
} }
@ -217,8 +219,6 @@
if (!self.isPlaying) { if (!self.isPlaying) {
return; return;
} }
// Calculate refresh duration
NSTimeInterval duration = self.displayLink.duration;
NSUInteger totalFrameCount = self.totalFrameCount; NSUInteger totalFrameCount = self.totalFrameCount;
if (totalFrameCount <= 1) { if (totalFrameCount <= 1) {
@ -226,8 +226,6 @@
[self stopPlaying]; [self stopPlaying];
return; return;
} }
NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
NSTimeInterval playbackRate = self.playbackRate; NSTimeInterval playbackRate = self.playbackRate;
if (playbackRate <= 0) { if (playbackRate <= 0) {
@ -236,33 +234,21 @@
return; return;
} }
// Check if we have the frame buffer firstly to improve performance // Calculate refresh duration
if (!self.bufferMiss) { NSTimeInterval duration = self.displayLink.duration;
// Then check if timestamp is reached
self.currentTime += duration;
NSTimeInterval currentDuration = [self.animatedProvider animatedImageDurationAtIndex:currentFrameIndex];
currentDuration = currentDuration / playbackRate;
if (self.currentTime < currentDuration) {
// Current frame timestamp not reached, return
return;
}
self.currentTime -= currentDuration;
NSTimeInterval nextDuration = [self.animatedProvider animatedImageDurationAtIndex:nextFrameIndex];
nextDuration = nextDuration / playbackRate;
if (self.currentTime > nextDuration) {
// Do not skip frame
self.currentTime = nextDuration;
}
}
// Update the current frame NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
// Check if we need to display new frame firstly
BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame; UIImage *currentFrame;
UIImage *fetchFrame;
SD_LOCK(self.lock); SD_LOCK(self.lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)]; currentFrame = self.frameBuffer[@(currentFrameIndex)];
fetchFrame = currentFrame ? self.frameBuffer[@(nextFrameIndex)] : nil;
SD_UNLOCK(self.lock); SD_UNLOCK(self.lock);
BOOL bufferFull = NO;
// Update the current frame
if (currentFrame) { if (currentFrame) {
SD_LOCK(self.lock); SD_LOCK(self.lock);
// Remove the frame buffer if need // Remove the frame buffer if need
@ -274,19 +260,47 @@
bufferFull = YES; bufferFull = YES;
} }
SD_UNLOCK(self.lock); SD_UNLOCK(self.lock);
// Update the current frame immediately
self.currentFrame = currentFrame; self.currentFrame = currentFrame;
[self handleFrameChange]; [self handleFrameChange];
self.currentFrameIndex = nextFrameIndex;
self.bufferMiss = NO; self.bufferMiss = NO;
} else { self.needsDisplayWhenImageBecomesAvailable = NO;
}
else {
self.bufferMiss = YES; self.bufferMiss = YES;
} }
}
// Check if we have the frame buffer
if (!self.bufferMiss) {
// Then check if timestamp is reached
self.currentTime += duration;
NSTimeInterval currentDuration = [self.animatedProvider animatedImageDurationAtIndex:currentFrameIndex];
currentDuration = currentDuration / playbackRate;
if (self.currentTime < currentDuration) {
// Current frame timestamp not reached, return
return;
}
// Otherwise, we shoudle be ready to display next frame
self.needsDisplayWhenImageBecomesAvailable = YES;
self.currentFrameIndex = nextFrameIndex;
self.currentTime -= currentDuration;
NSTimeInterval nextDuration = [self.animatedProvider animatedImageDurationAtIndex:nextFrameIndex];
nextDuration = nextDuration / playbackRate;
if (self.currentTime > nextDuration) {
// Do not skip frame
self.currentTime = nextDuration;
}
// Update the loop count when last frame rendered // Update the loop count when last frame rendered
if (nextFrameIndex == 0 && !self.bufferMiss) { if (nextFrameIndex == 0) {
// Update the loop count // Update the loop count
self.currentLoopCount++; self.currentLoopCount++;
[self handleLoopChnage]; [self handleLoopChnage];
// if reached the max loop count, stop animating, 0 means loop indefinitely // if reached the max loop count, stop animating, 0 means loop indefinitely
NSUInteger maxLoopCount = self.totalLoopCount; NSUInteger maxLoopCount = self.totalLoopCount;
if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) { if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
@ -294,6 +308,7 @@
return; return;
} }
} }
}
// Since we support handler, check animating state again // Since we support handler, check animating state again
if (!self.isPlaying) { if (!self.isPlaying) {
@ -301,14 +316,13 @@
} }
// Check if we should prefetch next frame or current frame // Check if we should prefetch next frame or current frame
NSUInteger fetchFrameIndex;
if (self.bufferMiss) {
// When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
fetchFrameIndex = currentFrameIndex;
} else {
// Or, most cases, the decode speed is faster than render speed, we fetch next frame // Or, most cases, the decode speed is faster than render speed, we fetch next frame
fetchFrameIndex = nextFrameIndex; NSUInteger fetchFrameIndex = self.bufferMiss? currentFrameIndex : nextFrameIndex;
} UIImage *fetchFrame;
SD_LOCK(self.lock);
fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)];
SD_UNLOCK(self.lock);
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) { if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue // Prefetch next frame in background queue

View File

@ -306,6 +306,19 @@
#pragma mark - UIImageView Method Overrides #pragma mark - UIImageView Method Overrides
#pragma mark Image Data #pragma mark Image Data
- (void)setAnimationRepeatCount:(NSInteger)animationRepeatCount
{
#if SD_UIKIT
[super setAnimationRepeatCount:animationRepeatCount];
#else
_animationRepeatCount = animationRepeatCount;
#endif
if (self.shouldCustomLoopCount) {
self.player.totalLoopCount = animationRepeatCount;
}
}
- (void)startAnimating - (void)startAnimating
{ {
if (self.player) { if (self.player) {