You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

582 lines
22 KiB

//
// OCBarrageContentView.m
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017LFC. All rights reserved.
//
#define kNextAvailableTimeKey(identifier, index) [NSString stringWithFormat:@"%@_%d", identifier, index]
#import "OCBarrageRenderView.h"
#import "OCBarrageTrackInfo.h"
@implementation OCBarrageRenderView
- (void)dealloc {
NSLog(@"%s", __func__);
}
- (instancetype)init {
self = [super init];
if (self) {
_animatingCellsLock = dispatch_semaphore_create(1);
_idleCellsLock = dispatch_semaphore_create(1);
_trackInfoLock = dispatch_semaphore_create(1);
_lowPositionView = [[UIView alloc] init];
[self addSubview:_lowPositionView];
_middlePositionView = [[UIView alloc] init];
[self addSubview:_middlePositionView];
_highPositionView = [[UIView alloc] init];
[self addSubview:_highPositionView];
_veryHighPositionView = [[UIView alloc] init];
[self addSubview:_veryHighPositionView];
self.layer.masksToBounds = YES;
_trackNextAvailableTime = [NSMutableDictionary dictionary];
}
return self;
}
- (nullable OCBarrageCell *)dequeueReusableCellWithClass:(Class)barrageCellClass {
OCBarrageCell *barrageCell = nil;
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
for (OCBarrageCell *cell in self.idleCells) {
if ([NSStringFromClass([cell class]) isEqualToString:NSStringFromClass(barrageCellClass)]) {
barrageCell = cell;
break;
}
}
if (barrageCell) {
[self.idleCells removeObject:barrageCell];
barrageCell.idleTime = 0.0;
} else {
barrageCell = [self newCellWithClass:barrageCellClass];
}
dispatch_semaphore_signal(_idleCellsLock);
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return nil;
}
return barrageCell;
}
- (OCBarrageCell *)newCellWithClass:(Class)barrageCellClass {
OCBarrageCell *barrageCell = [[barrageCellClass alloc] init];
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return nil;
}
return barrageCell;
}
- (void)start {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
return;
}
break;
case OCBarrageRenderPaused: {
[self resume];
return;
}
break;
default: {
_renderStatus = OCBarrageRenderStarted;
}
break;
}
}
- (void)pause {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
_renderStatus = OCBarrageRenderPaused;
}
break;
case OCBarrageRenderPaused: {
return;
}
break;
default: {
return;
}
break;
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
CFTimeInterval pausedTime = [cell.layer convertTime:CACurrentMediaTime() fromLayer:nil];
cell.layer.speed = 0.0;
cell.layer.timeOffset = pausedTime;
}
dispatch_semaphore_signal(_animatingCellsLock);
}
- (void)resume {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
return;
}
break;
case OCBarrageRenderPaused: {
_renderStatus = OCBarrageRenderStarted;
}
break;
default: {
return;
}
break;
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
CFTimeInterval pausedTime = cell.layer.timeOffset;
cell.layer.speed = 1.0;
cell.layer.timeOffset = 0.0;
cell.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [cell.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
cell.layer.beginTime = timeSincePause;
}
dispatch_semaphore_signal(_animatingCellsLock);
}
- (void)stop {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
_renderStatus = OCBarrageRenderStoped;
}
break;
case OCBarrageRenderPaused: {
_renderStatus = OCBarrageRenderStoped;
}
break;
default: {
return;
}
break;
}
if (_autoClear) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(clearIdleCells) object:nil];
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *animatingEnumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *animatingCell = nil;
while (animatingCell = [animatingEnumerator nextObject]){
CFTimeInterval pausedTime = [animatingCell.layer convertTime:CACurrentMediaTime() fromLayer:nil];
animatingCell.layer.speed = 0.0;
animatingCell.layer.timeOffset = pausedTime;
[animatingCell.layer removeAllAnimations];
[animatingCell removeFromSuperview];
}
[self.animatingCells removeAllObjects];
dispatch_semaphore_signal(_animatingCellsLock);
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
[self.idleCells removeAllObjects];
dispatch_semaphore_signal(_idleCellsLock);
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
[_trackNextAvailableTime removeAllObjects];
dispatch_semaphore_signal(_trackInfoLock);
}
- (void)fireBarrageCell:(OCBarrageCell *)barrageCell {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
}
break;
case OCBarrageRenderPaused: {
return;
}
break;
default:
return;
break;
}
if (!barrageCell) {
return;
}
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return;
}
[barrageCell clearContents];
[barrageCell updateSubviewsData];
[barrageCell layoutContentSubviews];
[barrageCell convertContentToImage];
[barrageCell sizeToFit];
[barrageCell removeSubViewsAndSublayers];
[barrageCell addBorderAttributes];
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
_lastestCell = [self.animatingCells lastObject];
[self.animatingCells addObject:barrageCell];
barrageCell.idle = NO;
dispatch_semaphore_signal(_animatingCellsLock);
[self addBarrageCell:barrageCell WithPositionPriority:barrageCell.barrageDescriptor.positionPriority];
CGRect cellFrame = [self calculationBarrageCellFrame:barrageCell];
barrageCell.frame = cellFrame;
[barrageCell addBarrageAnimationWithDelegate:self];
[self recordTrackInfoWithBarrageCell:barrageCell];
_lastestCell = barrageCell;
}
- (void)addBarrageCell:(OCBarrageCell *)barrageCell WithPositionPriority:(OCBarragePositionPriority)positionPriority {
switch (positionPriority) {
case OCBarragePositionMiddle: {
[self insertSubview:barrageCell aboveSubview:_middlePositionView];
}
break;
case OCBarragePositionHigh: {
[self insertSubview:barrageCell belowSubview:_highPositionView];
}
break;
case OCBarragePositionVeryHigh: {
[self insertSubview:barrageCell belowSubview:_veryHighPositionView];
}
break;
default: {
[self insertSubview:barrageCell belowSubview:_lowPositionView];
}
break;
}
}
- (CGRect)calculationBarrageCellFrame:(OCBarrageCell *)barrageCell {
CGRect cellFrame = barrageCell.bounds;
cellFrame.origin.x = CGRectGetMaxX(self.frame);
if (![[NSValue valueWithRange:barrageCell.barrageDescriptor.renderRange] isEqualToValue:[NSValue valueWithRange:NSMakeRange(0, 0)]]) {
CGFloat cellHeight = CGRectGetHeight(barrageCell.bounds);
CGFloat minOriginY = barrageCell.barrageDescriptor.renderRange.location;
CGFloat maxOriginY = barrageCell.barrageDescriptor.renderRange.length;
if (maxOriginY > CGRectGetHeight(self.bounds)) {
maxOriginY = CGRectGetHeight(self.bounds);
}
if (minOriginY < 0) {
minOriginY = 0;
}
CGFloat renderHeight = maxOriginY - minOriginY;
if (renderHeight < 0) {
renderHeight = cellHeight;
}
int trackCount = floorf(renderHeight/cellHeight);
int trackIndex = arc4random_uniform(trackCount);//用户改变行高(比如弹幕文字大小不会引起显示bug, 因为虽然是同一个类, 但是trackCount变小了, 所以不会出现trackIndex*cellHeight超出屏幕边界的情况)
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), trackIndex)];
if (trackInfo && trackInfo.nextAvailableTime > CACurrentMediaTime()) {//当前行暂不可用
NSMutableArray *availableTrackInfos = [NSMutableArray array];
for (OCBarrageTrackInfo *info in _trackNextAvailableTime.allValues) {
if (CACurrentMediaTime() > info.nextAvailableTime && [info.trackIdentifier containsString:NSStringFromClass([barrageCell class])]) {//只在同类弹幕中判断是否有可用的轨道
[availableTrackInfos addObject:info];
}
}
if (availableTrackInfos.count > 0) {
OCBarrageTrackInfo *randomInfo = [availableTrackInfos objectAtIndex:arc4random_uniform((int)availableTrackInfos.count)];
trackIndex = randomInfo.trackIndex;
} else {
if (_trackNextAvailableTime.count < trackCount) {//刚开始不是每一条轨道都跑过弹幕, 还有空轨道
NSMutableArray *numberArray = [NSMutableArray array];
for (int index = 0; index < trackCount; index++) {
OCBarrageTrackInfo *emptyTrackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), index)];
if (!emptyTrackInfo) {
[numberArray addObject:[NSNumber numberWithInt:index]];
}
}
if (numberArray.count > 0) {
trackIndex = [[numberArray objectAtIndex:arc4random_uniform((int)numberArray.count)] intValue];
}
}
//真的是没有可用的轨道了
}
}
dispatch_semaphore_signal(_trackInfoLock);
barrageCell.trackIndex = trackIndex;
cellFrame.origin.y = trackIndex*cellHeight+minOriginY;
} else {
switch (self.renderPositionStyle) {
case OCBarrageRenderPositionRandom: {
CGFloat maxY = CGRectGetHeight(self.bounds) - CGRectGetHeight(cellFrame);
int originY = floorl(maxY);
cellFrame.origin.y = arc4random_uniform(originY);
}
break;
case OCBarrageRenderPositionIncrease: {
if (_lastestCell) {
CGRect lastestFrame = _lastestCell.frame;
cellFrame.origin.y = CGRectGetMaxY(lastestFrame);
} else {
cellFrame.origin.y = 0.0;
}
}
break;
default: {
CGFloat renderViewHeight = CGRectGetHeight(self.bounds);
CGFloat cellHeight = CGRectGetHeight(barrageCell.bounds);
int trackCount = floorf(renderViewHeight/cellHeight);
int trackIndex = arc4random_uniform(trackCount);//用户改变行高(比如弹幕文字大小不会引起显示bug, 因为虽然是同一个类, 但是trackCount变小了, 所以不会出现trackIndex*cellHeight超出屏幕边界的情况)
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), trackIndex)];
if (trackInfo && trackInfo.nextAvailableTime > CACurrentMediaTime()) {//当前行暂不可用
NSMutableArray *availableTrackInfos = [NSMutableArray array];
for (OCBarrageTrackInfo *info in _trackNextAvailableTime.allValues) {
if (CACurrentMediaTime() > info.nextAvailableTime && [info.trackIdentifier containsString:NSStringFromClass([barrageCell class])]) {//只在同类弹幕中判断是否有可用的轨道
[availableTrackInfos addObject:info];
}
}
if (availableTrackInfos.count > 0) {
OCBarrageTrackInfo *randomInfo = [availableTrackInfos objectAtIndex:arc4random_uniform((int)availableTrackInfos.count)];
trackIndex = randomInfo.trackIndex;
} else {
if (_trackNextAvailableTime.count < trackCount) {//刚开始不是每一条轨道都跑过弹幕, 还有空轨道
NSMutableArray *numberArray = [NSMutableArray array];
for (int index = 0; index < trackCount; index++) {
OCBarrageTrackInfo *emptyTrackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), index)];
if (!emptyTrackInfo) {
[numberArray addObject:[NSNumber numberWithInt:index]];
}
}
if (numberArray.count > 0) {
trackIndex = [[numberArray objectAtIndex:arc4random_uniform((int)numberArray.count)] intValue];
}
}
//真的是没有可用的轨道了
}
}
dispatch_semaphore_signal(_trackInfoLock);
barrageCell.trackIndex = trackIndex;
cellFrame.origin.y = trackIndex*cellHeight;
}
break;
}
}
if (CGRectGetMaxY(cellFrame) > CGRectGetHeight(self.bounds)) {
cellFrame.origin.y = 0.0; //超过底部, 回到顶部
} else if (cellFrame.origin.y < 0) {
cellFrame.origin.y = 0.0;
}
return cellFrame;
}
- (void)clearIdleCells {
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970];
NSEnumerator *enumerator = [self.idleCells reverseObjectEnumerator];
OCBarrageCell *cell;
while (cell = [enumerator nextObject]){
CGFloat time = timeInterval - cell.idleTime;
if (time > 5.0 && cell.idleTime > 0) {
[self.idleCells removeObject:cell];
}
}
if (self.idleCells.count == 0) {
_autoClear = NO;
} else {
[self performSelector:@selector(clearIdleCells) withObject:nil afterDelay:5.0];
}
dispatch_semaphore_signal(_idleCellsLock);
}
- (void)recordTrackInfoWithBarrageCell:(OCBarrageCell *)barrageCell {
NSString *nextAvalibleTimeKey = kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), barrageCell.trackIndex);
CFTimeInterval duration = barrageCell.barrageAnimation.duration;
NSValue *fromValue = nil;
NSValue *toValue = nil;
if ([barrageCell.barrageAnimation isKindOfClass:[CABasicAnimation class]]) {
fromValue = [(CABasicAnimation *)barrageCell.barrageAnimation fromValue];
toValue = [(CABasicAnimation *)barrageCell.barrageAnimation toValue];
} else if ([barrageCell.barrageAnimation isKindOfClass:[CAKeyframeAnimation class]]) {
fromValue = [[(CAKeyframeAnimation *)barrageCell.barrageAnimation values] firstObject];
toValue = [[(CAKeyframeAnimation *)barrageCell.barrageAnimation values] lastObject];
} else {
}
const char *fromeValueType = [fromValue objCType];
const char *toValueType = [toValue objCType];
if (!fromeValueType || !toValueType) {
return;
}
NSString *fromeValueTypeString = [NSString stringWithCString:fromeValueType encoding:NSUTF8StringEncoding];
NSString *toValueTypeString = [NSString stringWithCString:toValueType encoding:NSUTF8StringEncoding];
if (![fromeValueTypeString isEqualToString:toValueTypeString]) {
return;
}
if ([fromeValueTypeString containsString:@"CGPoint"]) {
CGPoint fromPoint = [fromValue CGPointValue];
CGPoint toPoint = [toValue CGPointValue];
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:nextAvalibleTimeKey];
if (!trackInfo) {
trackInfo = [[OCBarrageTrackInfo alloc] init];
trackInfo.trackIdentifier = nextAvalibleTimeKey;
trackInfo.trackIndex = barrageCell.trackIndex;
}
trackInfo.barrageCount++;
trackInfo.nextAvailableTime = CGRectGetWidth(barrageCell.bounds);
CGFloat distanceX = fabs(toPoint.x - fromPoint.x);
CGFloat distanceY = fabs(toPoint.y - fromPoint.y);
CGFloat distance = MAX(distanceX, distanceY);
CGFloat speed = distance/duration;
if (distanceX == distance) {
CFTimeInterval time = CGRectGetWidth(barrageCell.bounds)/speed;
trackInfo.nextAvailableTime = CACurrentMediaTime() + time + 0.1;//多加一点时间
[_trackNextAvailableTime setValue:trackInfo forKey:nextAvalibleTimeKey];
} else if (distanceY == distance) {
// CFTimeInterval time = CGRectGetHeight(barrageCell.bounds)/speed;
} else {
}
dispatch_semaphore_signal(_trackInfoLock);
return;
} else if ([fromeValueTypeString containsString:@"CGVector"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGSize"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGRect"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGAffineTransform"]) {
return;
} else if ([fromeValueTypeString containsString:@"UIEdgeInsets"]) {
return;
} else if ([fromeValueTypeString containsString:@"UIOffset"]) {
return;
}
}
#pragma mark ----- CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
!self.animationStopBlock ?: self.animationStopBlock();
if (!flag) {
return;
}
if (self.renderStatus == OCBarrageRenderStoped) {
return;
}
OCBarrageCell *animationedCell = nil;
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
for (OCBarrageCell *cell in self.animatingCells) {
CAAnimation *barrageAnimation = [cell barrageAnimation];
if (barrageAnimation == anim) {
animationedCell = cell;
[self.animatingCells removeObject:cell];
break;
}
}
dispatch_semaphore_signal(_animatingCellsLock);
if (!animationedCell) {
return;
}
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([animationedCell class]), animationedCell.trackIndex)];
if (trackInfo) {
trackInfo.barrageCount--;
}
dispatch_semaphore_signal(_trackInfoLock);
[animationedCell removeFromSuperview];
[animationedCell prepareForReuse];
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
animationedCell.idleTime = [[NSDate date] timeIntervalSince1970];
[self.idleCells addObject:animationedCell];
dispatch_semaphore_signal(_idleCellsLock);
if (!_autoClear) {
[self performSelector:@selector(clearIdleCells) withObject:nil afterDelay:5.0];
_autoClear = YES;
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
UITouch *touch = [touches.allObjects firstObject];
CGPoint touchPoint = [touch locationInView:self];
[self trigerActionWithPoint:touchPoint];
}
}
- (BOOL)trigerActionWithPoint:(CGPoint)touchPoint
{
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
BOOL anyTriger = NO;
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
if ([cell.layer.presentationLayer hitTest:touchPoint]) {
if (cell.barrageDescriptor.touchAction) {
cell.barrageDescriptor.touchAction(cell.barrageDescriptor);
anyTriger = YES;
}
if (cell.barrageDescriptor.cellTouchedAction) {
cell.barrageDescriptor.cellTouchedAction(cell.barrageDescriptor, cell);
anyTriger = YES;
}
break;
}
}
dispatch_semaphore_signal(_animatingCellsLock);
return anyTriger;
}
#pragma mark ----- getter
- (NSMutableArray<OCBarrageCell *> *)animatingCells {
if (!_animatingCells) {
_animatingCells = [[NSMutableArray alloc] init];
}
return _animatingCells;
}
- (NSMutableArray<OCBarrageCell *> *)idleCells {
if (!_idleCells) {
_idleCells = [[NSMutableArray alloc] init];
}
return _idleCells;
}
- (OCBarrageRenderStatus)renderStatus {
return _renderStatus;
}
@end