Try to optimize the time when calculate the maxBufferCount, it's cheap to get free memory so we check each time before prefetch
Also, move the memory warning handler into frame pool class (100 player may use one frame pool)
This commit is contained in:
parent
a206229905
commit
858b64aef7
|
@ -14,7 +14,6 @@
|
|||
#import "SDInternalMacros.h"
|
||||
|
||||
@interface SDAnimatedImagePlayer () {
|
||||
// SD_LOCK_DECLARE(_lock);
|
||||
NSRunLoopMode _runLoopMode;
|
||||
}
|
||||
|
||||
|
@ -24,6 +23,7 @@
|
|||
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
|
||||
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
|
||||
@property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider;
|
||||
@property (nonatomic, assign) NSUInteger currentFrameBytes;
|
||||
@property (nonatomic, assign) NSTimeInterval currentTime;
|
||||
@property (nonatomic, assign) BOOL bufferMiss;
|
||||
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
|
||||
|
@ -48,10 +48,6 @@
|
|||
self.animatedProvider = provider;
|
||||
self.playbackRate = 1.0;
|
||||
self.framePool = [SDImageFramePool registerProvider:provider];
|
||||
// SD_LOCK_INIT(_lock);
|
||||
#if SD_UIKIT
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -61,32 +57,6 @@
|
|||
return player;
|
||||
}
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
|
||||
- (void)dealloc {
|
||||
#if SD_UIKIT
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
|
||||
[self clearFrameBuffer];
|
||||
// [_fetchQueue cancelAllOperations];
|
||||
// NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
// NSNumber *currentFrameIndex = @(self.currentFrameIndex);
|
||||
// SD_LOCK(self->_lock);
|
||||
// NSArray *keys = self.frameBuffer.allKeys;
|
||||
// // only keep the next frame for later rendering
|
||||
// for (NSNumber * key in keys) {
|
||||
// if (![key isEqualToNumber:currentFrameIndex]) {
|
||||
// [self.frameBuffer removeObjectForKey:key];
|
||||
// }
|
||||
// }
|
||||
// SD_UNLOCK(self->_lock);
|
||||
// }];
|
||||
// [_fetchQueue addOperation:operation];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (SDDisplayLink *)displayLink {
|
||||
|
@ -140,12 +110,11 @@
|
|||
UIImage *posterFrame = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
|
||||
#endif
|
||||
if (posterFrame) {
|
||||
// Calculate max buffer size
|
||||
[self calculateMaxBufferCountWithFrame:posterFrame];
|
||||
// HACK: The first frame should not check duration and immediately display
|
||||
self.needsDisplayWhenImageBecomesAvailable = YES;
|
||||
[self.framePool setFrame:posterFrame atIndex:self.currentFrameIndex];
|
||||
// SD_LOCK(self->_lock);
|
||||
// self.frameBuffer[@(self.currentFrameIndex)] = posterFrame;
|
||||
// SD_UNLOCK(self->_lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,9 +132,6 @@
|
|||
|
||||
- (void)clearFrameBuffer {
|
||||
[self.framePool removeAllFrames];
|
||||
// SD_LOCK(_lock);
|
||||
// [_frameBuffer removeAllObjects];
|
||||
// SD_UNLOCK(_lock);
|
||||
}
|
||||
|
||||
#pragma mark - Animation Control
|
||||
|
@ -173,12 +139,9 @@
|
|||
[self.displayLink start];
|
||||
// Setup frame
|
||||
[self setupCurrentFrame];
|
||||
// Calculate max buffer size
|
||||
[self calculateMaxBufferCount];
|
||||
}
|
||||
|
||||
- (void)stopPlaying {
|
||||
// [_fetchQueue cancelAllOperations];
|
||||
// Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
|
||||
[_displayLink stop];
|
||||
// We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
|
||||
|
@ -186,7 +149,6 @@
|
|||
}
|
||||
|
||||
- (void)pausePlaying {
|
||||
// [_fetchQueue cancelAllOperations];
|
||||
[_displayLink stop];
|
||||
}
|
||||
|
||||
|
@ -248,29 +210,11 @@
|
|||
|
||||
|
||||
// Check if we need to display new frame firstly
|
||||
BOOL bufferFull = NO;
|
||||
if (self.needsDisplayWhenImageBecomesAvailable) {
|
||||
UIImage *currentFrame = [self.framePool frameAtIndex:currentFrameIndex];
|
||||
// SD_LOCK(_lock);
|
||||
// currentFrame = self.frameBuffer[@(currentFrameIndex)];
|
||||
// SD_UNLOCK(_lock);
|
||||
|
||||
// Update the current frame
|
||||
if (currentFrame) {
|
||||
if (self.framePool.currentFrameCount == totalFrameCount) {
|
||||
bufferFull = YES;
|
||||
}
|
||||
// SD_LOCK(_lock);
|
||||
// // Remove the frame buffer if need
|
||||
// if (self.frameBuffer.count > self.maxBufferCount) {
|
||||
// self.frameBuffer[@(currentFrameIndex)] = nil;
|
||||
// }
|
||||
// // Check whether we can stop fetch
|
||||
// if (self.frameBuffer.count == totalFrameCount) {
|
||||
// bufferFull = YES;
|
||||
// }
|
||||
// SD_UNLOCK(_lock);
|
||||
|
||||
// Update the current frame immediately
|
||||
self.currentFrame = currentFrame;
|
||||
[self handleFrameChange];
|
||||
|
@ -292,8 +236,7 @@
|
|||
if (self.currentTime < currentDuration) {
|
||||
// Current frame timestamp not reached, prefetch frame in advance.
|
||||
[self prefetchFrameAtIndex:currentFrameIndex
|
||||
nextIndex:nextFrameIndex
|
||||
bufferFull:bufferFull];
|
||||
nextIndex:nextFrameIndex];
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -329,49 +272,31 @@
|
|||
}
|
||||
|
||||
[self prefetchFrameAtIndex:currentFrameIndex
|
||||
nextIndex:nextFrameIndex
|
||||
bufferFull:bufferFull];
|
||||
nextIndex:nextFrameIndex];
|
||||
}
|
||||
|
||||
// Check if we should prefetch next frame or current frame
|
||||
// When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
|
||||
// Or, most cases, the decode speed is faster than render speed, we fetch next frame
|
||||
- (void)prefetchFrameAtIndex:(NSUInteger)currentIndex
|
||||
nextIndex:(NSUInteger)nextIndex
|
||||
bufferFull:(BOOL)bufferFull {
|
||||
nextIndex:(NSUInteger)nextIndex {
|
||||
NSUInteger fetchFrameIndex = currentIndex;
|
||||
UIImage *fetchFrame = nil;
|
||||
if (!self.bufferMiss) {
|
||||
fetchFrameIndex = nextIndex;
|
||||
fetchFrame = [self.framePool frameAtIndex:nextIndex];
|
||||
// SD_LOCK(_lock);
|
||||
// fetchFrame = self.frameBuffer[@(nextIndex)];
|
||||
// SD_UNLOCK(_lock);
|
||||
}
|
||||
BOOL bufferFull = NO;
|
||||
if (self.framePool.currentFrameCount == self.totalFrameCount) {
|
||||
bufferFull = YES;
|
||||
}
|
||||
if (!fetchFrame && !bufferFull) {
|
||||
// Calculate max buffer size
|
||||
[self calculateMaxBufferCountWithFrame:self.currentFrame];
|
||||
// Prefetch next frame
|
||||
[self.framePool prefetchFrameAtIndex:fetchFrameIndex];
|
||||
}
|
||||
}
|
||||
// if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
|
||||
// // Prefetch next frame in background queue
|
||||
// id<SDAnimatedImageProvider> animatedProvider = self.animatedProvider;
|
||||
// @weakify(self);
|
||||
// NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
// @strongify(self);
|
||||
// if (!self) {
|
||||
// return;
|
||||
// }
|
||||
// UIImage *frame = [animatedProvider animatedImageFrameAtIndex:fetchFrameIndex];
|
||||
//
|
||||
// BOOL isAnimating = self.displayLink.isRunning;
|
||||
// if (isAnimating) {
|
||||
// SD_LOCK(self->_lock);
|
||||
// self.frameBuffer[@(fetchFrameIndex)] = frame;
|
||||
// SD_UNLOCK(self->_lock);
|
||||
// }
|
||||
// }];
|
||||
// [self.fetchQueue addOperation:operation];
|
||||
// }
|
||||
|
||||
- (void)handleFrameChange {
|
||||
if (self.animationFrameHandler) {
|
||||
|
@ -386,9 +311,17 @@
|
|||
}
|
||||
|
||||
#pragma mark - Util
|
||||
- (void)calculateMaxBufferCount {
|
||||
NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
|
||||
if (bytes == 0) bytes = 1024;
|
||||
- (void)calculateMaxBufferCountWithFrame:(nonnull UIImage *)frame {
|
||||
NSUInteger bytes = self.currentFrameBytes;
|
||||
if (bytes == 0) {
|
||||
bytes = CGImageGetBytesPerRow(frame.CGImage) * CGImageGetHeight(frame.CGImage);
|
||||
if (bytes == 0) {
|
||||
bytes = 1024;
|
||||
} else {
|
||||
// Cache since most animated image each frame bytes is the same
|
||||
self.currentFrameBytes = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
NSUInteger max = 0;
|
||||
if (self.maxBufferSize > 0) {
|
||||
|
@ -406,6 +339,7 @@
|
|||
maxBufferCount = 1;
|
||||
}
|
||||
|
||||
printf("current maxBufferCount: %lu\n", (unsigned long)maxBufferCount);
|
||||
self.framePool.maxBufferCount = maxBufferCount;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
return providerFramePoolMap;
|
||||
}
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
@ -40,10 +41,23 @@
|
|||
_fetchQueue = [[NSOperationQueue alloc] init];
|
||||
_fetchQueue.maxConcurrentOperationCount = 1;
|
||||
_fetchQueue.name = @"com.hackemist.SDImageFramePool.fetchQueue";
|
||||
#if SD_UIKIT
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
#if SD_UIKIT
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
|
||||
[self removeAllFrames];
|
||||
}
|
||||
|
||||
+ (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider {
|
||||
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
|
||||
if (!framePool) {
|
||||
|
@ -71,7 +85,9 @@
|
|||
NSUInteger frameCount = self.frameBuffer.count;
|
||||
if (frameCount > self.maxBufferCount) {
|
||||
// Remove the frame buffer if need
|
||||
self.frameBuffer[@(index)] = nil;
|
||||
// TODO, use LRU or better algorithm to detect which frames to clear
|
||||
self.frameBuffer[@(index - 1)] = nil;
|
||||
self.frameBuffer[@(index + 1)] = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue