├── .DS_Store ├── DanmakuLib ├── CommentTextLayer.h ├── CommentTextLayer.m ├── RiverRunCommentManager.h ├── RiverRunCommentManager.m ├── RiverRunCommentUtil.h └── RiverRunCommentUtil.m ├── DanmakuMaster.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── DanmakuMaster.xcscmblueprint │ └── xcuserdata │ │ └── PeterKong.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── PeterKong.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── DanmakuMaster.xcscheme │ └── xcschememanagement.plist ├── DanmakuMaster ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── DanmakuLib │ ├── CommentTextLayer.h │ ├── CommentTextLayer.m │ ├── RiverRunCommentManager.h │ ├── RiverRunCommentManager.m │ ├── RiverRunCommentUtil.h │ └── RiverRunCommentUtil.m ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyPeter/DanmukuMaster-iOS/9af88fb4e376ff6da5167346f4f3176ad2507655/.DS_Store -------------------------------------------------------------------------------- /DanmakuLib/CommentTextLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CommentTextLayer.h 3 | // SmilePlayer2 4 | // 5 | // Created by pontago on 2014/04/05. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | extern NSString* const COMMENT_FONT_NAME; 12 | 13 | typedef enum _COMMENT_POSITION { 14 | COMMENT_POSITION_NORMAL, 15 | COMMENT_POSITION_TOP, 16 | COMMENT_POSITION_BOTTOM 17 | } COMMENT_POSITION; 18 | 19 | typedef enum _COMMENT_SIZE { 20 | COMMENT_SIZE_NORMAL, 21 | COMMENT_SIZE_SMALL, 22 | COMMENT_SIZE_BIG 23 | } COMMENT_SIZE; 24 | 25 | @interface CommentTextLayer : CALayer 26 | + (id)layerWithCommentInfo:(NSDictionary*)commentInfo screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape; 27 | +(UIColor *)changeColor:(NSString *)str; 28 | - (void)updateFrame:(CGSize)screenSize; 29 | - (CGFloat)commentFontSize; 30 | - (CGFloat)adjustsFontSizeToFitWidth:(CGSize)screenSize; 31 | - (void)pauseAnimation:(BOOL)aPause; 32 | 33 | @property (nonatomic) NSInteger vpos; 34 | @property (nonatomic) NSInteger commentPosition; 35 | @property (nonatomic) NSInteger commentSize; 36 | @property (nonatomic) BOOL isLandscape; 37 | @property (nonatomic, strong) NSString *string; 38 | @property (nonatomic, strong) UIColor *textColor; 39 | @property (nonatomic, strong) UIColor *strokeColor; 40 | @property (nonatomic) CGFloat fontSize; 41 | @end 42 | -------------------------------------------------------------------------------- /DanmakuLib/CommentTextLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CommentTextLayer.m 3 | // SmilePlayer2 4 | // 5 | // Created by pontago on 2014/04/05. 6 | // 7 | // 8 | 9 | #import "CommentTextLayer.h" 10 | #import 11 | #import 12 | 13 | NSString* const COMMENT_FONT_NAME = @"STHeitiJ-Light"; 14 | 15 | #define HEXCOLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 \ 16 | green:((c>>8)&0xFF)/255.0 \ 17 | blue:(c&0xFF)/255.0 \ 18 | alpha:1.0]; 19 | 20 | @interface CommentTextLayer () { 21 | } 22 | - (UIFont*)_createFont:(CGFloat)fontSize; 23 | - (CTFontRef)_createFontRef; 24 | - (CTFrameRef)_createFrameRef; 25 | - (CGImageRef)_createStrokeImageRef:(CTFrameRef)frameRef; 26 | @end 27 | 28 | @implementation CommentTextLayer 29 | 30 | -(void)dealloc 31 | { 32 | // NSLog(@"layer - dealloc"); 33 | } 34 | 35 | + (id)layerWithCommentInfo:(NSDictionary*)commentInfo screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape { 36 | CommentTextLayer *commentTextLayer = [CommentTextLayer layer]; 37 | if (commentTextLayer) { 38 | commentTextLayer.anchorPoint = CGPointMake(0.f, 0.f); 39 | commentTextLayer.drawsAsynchronously = YES; 40 | commentTextLayer.contentsScale = [[UIScreen mainScreen] scale]; 41 | commentTextLayer.string = commentInfo[@"body"]; 42 | 43 | UIColor *color; 44 | if(![commentInfo[@"color"] isEqualToString:@"-1"]){ 45 | color = [CommentTextLayer changeColor:commentInfo[@"color"]]; 46 | } 47 | else{ 48 | color = [CommentTextLayer changeColor:@"#ffffff"]; 49 | } 50 | commentTextLayer.textColor = color; 51 | 52 | if ([commentInfo[@"color"] intValue] == 0x000000) { 53 | commentTextLayer.strokeColor = [UIColor colorWithWhite:1.f alpha:0.6f]; 54 | } 55 | else { 56 | commentTextLayer.strokeColor = [UIColor colorWithWhite:0.f alpha:0.3f]; 57 | } 58 | 59 | commentTextLayer.vpos = [commentInfo[@"vpos"] intValue]; 60 | commentTextLayer.commentPosition = [commentInfo[@"position"] intValue]; 61 | commentTextLayer.isLandscape = isLandscape; 62 | 63 | NSNumber *fontSizeNum = commentInfo[@"fontSize"]; 64 | commentTextLayer.commentSize = [fontSizeNum intValue]; 65 | 66 | [commentTextLayer updateFrame:screenSize]; 67 | } 68 | return commentTextLayer; 69 | } 70 | 71 | - (UIFont*)_createFont:(CGFloat)fontSize { 72 | #if 0 73 | NSDictionary *fontAttributes = @{ 74 | UIFontDescriptorNameAttribute: COMMENT_FONT_NAME, 75 | UIFontDescriptorCascadeListAttribute: @[ 76 | [UIFontDescriptor fontDescriptorWithName:@"AppleColorEmoji" size:fontSize] 77 | ] 78 | }; 79 | UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:fontAttributes]; 80 | return [UIFont fontWithDescriptor:fontDescriptor size:fontSize]; 81 | #endif 82 | return [UIFont fontWithName:COMMENT_FONT_NAME size:fontSize]; 83 | } 84 | 85 | - (CTFontRef)_createFontRef { 86 | CTFontDescriptorRef emojiFontDescriptorRef = CTFontDescriptorCreateWithNameAndSize(CFSTR("AppleColorEmoji"), self.fontSize); 87 | 88 | NSDictionary *fontAttributes = @{ 89 | (id)kCTFontNameAttribute: COMMENT_FONT_NAME, 90 | (id)kCTFontCascadeListAttribute: @[ 91 | (__bridge id)emojiFontDescriptorRef, 92 | ] 93 | }; 94 | 95 | CTFontDescriptorRef fontDescriptorRef = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)fontAttributes); 96 | CTFontRef fontRef = CTFontCreateWithFontDescriptor(fontDescriptorRef, self.fontSize, NULL); 97 | 98 | CFRelease(fontDescriptorRef); 99 | CFRelease(emojiFontDescriptorRef); 100 | 101 | return fontRef; 102 | } 103 | 104 | - (CTFrameRef)_createFrameRef { 105 | NSString *str = self.string; 106 | CTFontRef fontRef = [self _createFontRef]; 107 | 108 | CTTextAlignment alignment = kCTTextAlignmentCenter; 109 | CTParagraphStyleSetting paragraphStyleSettings[] = { 110 | { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment }, 111 | }; 112 | CTParagraphStyleRef paragraphStyleRef = CTParagraphStyleCreate(paragraphStyleSettings, 1); 113 | 114 | NSAttributedString *attrStr = [[NSAttributedString alloc] 115 | initWithString:str 116 | attributes:@{ 117 | (NSString*)kCTFontAttributeName: (__bridge id)fontRef, 118 | (NSString*)kCTForegroundColorAttributeName: self.textColor, 119 | (NSString*)kCTParagraphStyleAttributeName: (__bridge id)paragraphStyleRef 120 | }]; 121 | 122 | 123 | CGMutablePathRef pathRef = CGPathCreateMutable(); 124 | CGPathAddRect(pathRef, NULL, self.bounds); 125 | CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFMutableAttributedStringRef)attrStr); 126 | CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, str.length), pathRef, NULL); 127 | 128 | CFRelease(fontRef); 129 | CGPathRelease(pathRef); 130 | CFRelease(framesetterRef); 131 | CFRelease(paragraphStyleRef); 132 | 133 | return frameRef; 134 | } 135 | 136 | - (CGImageRef)_createStrokeImageRef:(CTFrameRef)frameRef { 137 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0); 138 | CGContextRef context = UIGraphicsGetCurrentContext(); 139 | 140 | CGContextSetTextDrawingMode(context, kCGTextStroke); 141 | CGContextSetLineWidth(context, 2.f); 142 | CGContextSetLineJoin(context, kCGLineJoinRound); 143 | CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); 144 | CTFrameDraw(frameRef, context); 145 | 146 | CGImageRef clippingMask = CGBitmapContextCreateImage(context); 147 | 148 | CGContextClearRect(context, CGRectMake(0, 0, self.bounds.size.width + 2.f, self.bounds.size.height + 2.f)); 149 | CGContextClipToMask(context, self.bounds, clippingMask); 150 | 151 | CGContextTranslateCTM(context, 0.0, CGRectGetHeight(self.bounds)); 152 | CGContextScaleCTM(context, 1.0, -1.0); 153 | CGContextSetFillColorWithColor(context, self.strokeColor.CGColor); 154 | CGContextFillRect(context, self.bounds); 155 | 156 | CGImageRef imageRef = CGBitmapContextCreateImage(context); 157 | UIGraphicsEndImageContext(); 158 | 159 | CGImageRelease(clippingMask); 160 | return imageRef; 161 | } 162 | 163 | - (void)drawInContext:(CGContextRef)ctx { 164 | CGContextTranslateCTM(ctx, 0.0, CGRectGetHeight(self.bounds)); 165 | CGContextScaleCTM(ctx, 1.0, -1.0); 166 | 167 | CTFrameRef frameRef = [self _createFrameRef]; 168 | CGImageRef strokeImageRef = [self _createStrokeImageRef:frameRef]; 169 | 170 | UIGraphicsPushContext(ctx); 171 | 172 | CGContextDrawImage(ctx, self.bounds, strokeImageRef); 173 | CTFrameDraw(frameRef, ctx); 174 | 175 | UIGraphicsPopContext(); 176 | 177 | CGImageRelease(strokeImageRef); 178 | CFRelease(frameRef); 179 | } 180 | 181 | ////////////////////////////////////////////////////////////////////////////////////////////主要是这个方法没有回收 182 | - (void)updateFrame:(CGSize)screenSize { 183 | if (self.commentPosition == COMMENT_POSITION_NORMAL) { 184 | self.fontSize = [self commentFontSize]; 185 | UIFont *font = [self _createFont:self.fontSize]; 186 | CGSize textSize = [self.string 187 | boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, screenSize.height) 188 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 189 | attributes:@{ NSFontAttributeName:font } 190 | context:nil].size; 191 | CTFontRef fontRef = [self _createFontRef]; 192 | self.frame = CGRectMake(0, 0, textSize.width, textSize.height + CTFontGetDescent(fontRef)); 193 | CFRelease(fontRef); 194 | } 195 | else { 196 | self.fontSize = [self adjustsFontSizeToFitWidth:screenSize]; 197 | UIFont *font = [self _createFont:self.fontSize]; 198 | CGSize textSize = [self.string 199 | boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, screenSize.height) 200 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 201 | attributes:@{ NSFontAttributeName:font } 202 | context:nil].size; 203 | self.frame = CGRectMake(0, 0, screenSize.width, textSize.height); 204 | } 205 | [self setNeedsDisplay]; 206 | } 207 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 208 | - (CGFloat)commentFontSize { 209 | if (self.commentSize == COMMENT_SIZE_BIG) { 210 | return _isLandscape ? 21.f : 17.f; 211 | } 212 | else if (self.commentSize == COMMENT_SIZE_SMALL) { 213 | return _isLandscape ? 16.f : 12.f; 214 | } 215 | return _isLandscape ? 19.f : 14.f; 216 | } 217 | 218 | - (CGFloat)adjustsFontSizeToFitWidth:(CGSize)screenSize { 219 | UIFont *font; 220 | CGFloat minFontSize = 8.f, currentFontSize = [self commentFontSize]; 221 | CGSize textSize; 222 | 223 | for (; currentFontSize >= minFontSize; --currentFontSize) { 224 | font = [self _createFont:currentFontSize]; 225 | 226 | textSize = [self.string 227 | boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, screenSize.height) 228 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 229 | attributes:@{ NSFontAttributeName:font } 230 | context:nil].size; 231 | 232 | if (textSize.width < screenSize.width) { 233 | break; 234 | } 235 | } 236 | 237 | return currentFontSize; 238 | } 239 | 240 | - (void)pauseAnimation:(BOOL)aPause { 241 | if (aPause) { 242 | CFTimeInterval pausedTime = [self convertTime:CACurrentMediaTime() fromLayer:nil]; 243 | self.speed = 0.f; 244 | self.timeOffset = pausedTime; 245 | } 246 | else { 247 | CFTimeInterval pausedTime = self.timeOffset; 248 | self.speed = 1.f; 249 | self.timeOffset = 0.f; 250 | self.beginTime = 0.f; 251 | CFTimeInterval timeSincePause = [self convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; 252 | self.beginTime = timeSincePause; 253 | } 254 | } 255 | 256 | +(UIColor *)changeColor:(NSString *)str{ 257 | unsigned int red,green,blue; 258 | NSString * str1 = [str substringWithRange:NSMakeRange(1, 2)]; 259 | NSString * str2 = [str substringWithRange:NSMakeRange(3, 2)]; 260 | NSString * str3 = [str substringWithRange:NSMakeRange(5, 2)]; 261 | 262 | NSScanner * canner = [NSScanner scannerWithString:str1]; 263 | [canner scanHexInt:&red]; 264 | 265 | canner = [NSScanner scannerWithString:str2]; 266 | [canner scanHexInt:&green]; 267 | 268 | canner = [NSScanner scannerWithString:str3]; 269 | [canner scanHexInt:&blue]; 270 | UIColor * color = [UIColor colorWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:1.0]; 271 | return color; 272 | } 273 | 274 | @end 275 | -------------------------------------------------------------------------------- /DanmakuLib/RiverRunCommentManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentManager.h 3 | // 4 | // 5 | // Created by CrazyPeter on 2014/04/21. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | extern CGFloat const COMMENT_DURATION; 12 | extern CGFloat const COMMENT_TOP_OR_BOTTOM_DURATION; 13 | 14 | @protocol RiverRunCommentManagerDelegate 15 | - (CGFloat)willShowComments:(BOOL)seek; 16 | @end 17 | 18 | @interface RiverRunCommentManager : NSObject 19 | @property (nonatomic, strong) NSArray *comments; 20 | @property (nonatomic, weak) id delegate; 21 | 22 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate andPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape; 23 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate; 24 | + (id)nicoCommentManagerWithComments:(NSArray*)comments delegate:(id)delegate; 25 | 26 | - (void)setupPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape; 27 | - (void)start; 28 | - (void)stop; 29 | 30 | - (void)deleteAllCommentLayer; 31 | - (void)startCommentLayer; 32 | - (void)stopCommentLayer; 33 | - (void)seekCommentLayer:(BOOL)pause; 34 | @end 35 | -------------------------------------------------------------------------------- /DanmakuLib/RiverRunCommentManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentManager.m 3 | // 4 | // 5 | // Created by CrazyPeter on 2014/04/21. 6 | // 7 | // 8 | 9 | #import "RiverRunCommentManager.h" 10 | #import "CommentTextLayer.h" 11 | #include 12 | 13 | CGFloat const COMMENT_DURATION = 5.f; 14 | CGFloat const COMMENT_TOP_OR_BOTTOM_DURATION = 3.f; 15 | 16 | @interface RiverRunCommentManager () { 17 | UIView *_view; 18 | CGSize _screenSize, _videoSize; 19 | NSTimer *_commentTimer; 20 | NSInteger _currentIndex; 21 | CGFloat _moviePosition; 22 | BOOL _isLandscape; 23 | NSMutableArray *_showCommentItems; 24 | CGFloat _totalsize; 25 | } 26 | - (CGSize)_sizeInOrientation; 27 | 28 | - (void)_commentTimerFired:(NSTimer*)timer; 29 | - (BOOL)_createCommentLayer:(NSDictionary*)comment setPosition:(BOOL)setPosition pause:(BOOL)pause andDuration:(NSTimeInterval)duration_form_data; 30 | - (void)_addShowCommentItem:(NSDictionary*)commentItem; 31 | - (BOOL)_checkCollisionHeight:(CGRect)rect targetRect:(CGRect)targetRect; 32 | - (BOOL)_checkCollisionWidth:(CGRect)rect targetRect:(CGRect)targetRect; 33 | - (void)_deleteCommentLayer; 34 | @end 35 | 36 | @implementation RiverRunCommentManager 37 | - (id)init { 38 | self = [super init]; 39 | if (self) { 40 | _showCommentItems = [NSMutableArray array]; 41 | _currentIndex = 0; 42 | _isLandscape = NO; 43 | } 44 | return self; 45 | } 46 | 47 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate andPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape{ 48 | { 49 | self = [self init]; 50 | if (self) { 51 | _comments = comments; 52 | _delegate = delegate; 53 | } 54 | [self setupPresentView:view videoSize:videoSize screenSize:screenSize isLandscape:isLandscape]; 55 | return self; 56 | } 57 | } 58 | 59 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate { 60 | self = [self init]; 61 | if (self) { 62 | _comments = comments; 63 | _delegate = delegate; 64 | } 65 | 66 | return self; 67 | } 68 | 69 | + (id)nicoCommentManagerWithComments:(NSArray*)comments delegate:(id)delegate { 70 | return [[RiverRunCommentManager alloc] initWithComments:comments delegate:delegate]; 71 | } 72 | 73 | - (void)dealloc { 74 | NSLog(@"NicoCommentManager dealloc"); 75 | } 76 | 77 | - (CGSize)_sizeInOrientation { 78 | UIApplication *application = [UIApplication sharedApplication]; 79 | UIInterfaceOrientation orientation = application.statusBarOrientation; 80 | CGSize size = [UIScreen mainScreen].bounds.size; 81 | 82 | if (UIInterfaceOrientationIsLandscape(orientation)) { 83 | size = CGSizeMake(size.height, size.width); 84 | } 85 | 86 | return size; 87 | } 88 | 89 | - (void)setupPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape { 90 | _view = view; 91 | _videoSize = videoSize; 92 | _screenSize = screenSize; 93 | _isLandscape = isLandscape; 94 | 95 | if (isnan(_screenSize.width) || isnan(_screenSize.height)) { 96 | _screenSize = [self _sizeInOrientation]; 97 | } 98 | } 99 | 100 | - (void)start { 101 | if (!_commentTimer) { 102 | _commentTimer = [NSTimer timerWithTimeInterval:0.5f 103 | target:self selector:@selector(_commentTimerFired:) 104 | userInfo:nil repeats:YES]; 105 | [[NSRunLoop currentRunLoop] addTimer:_commentTimer forMode:NSRunLoopCommonModes]; 106 | } 107 | } 108 | 109 | - (void)stop { 110 | [_commentTimer invalidate]; 111 | _commentTimer = nil; 112 | 113 | [self _deleteCommentLayer]; 114 | } 115 | 116 | - (void)_commentTimerFired:(NSTimer*)timer { 117 | if (_delegate) { 118 | _moviePosition = [_delegate willShowComments:NO]; 119 | } 120 | if (_moviePosition == -1) return; 121 | 122 | [CATransaction begin]; 123 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 124 | 125 | [self _deleteCommentLayer]; 126 | 127 | CGFloat currentTime = floor(_moviePosition * 1000.f); 128 | NSInteger max = [_comments count]; 129 | 130 | for (NSInteger i = _currentIndex; i < max; i++) { 131 | NSDictionary *comment = _comments[i]; 132 | 133 | if (currentTime >= [comment[@"vpos"] floatValue]) { 134 | [self _createCommentLayer:comment setPosition:NO pause:NO andDuration: [comment[@"duration"] floatValue]]; 135 | 136 | if ((i + 1) == max) { 137 | _currentIndex = i + 1; 138 | } 139 | } 140 | else { 141 | _currentIndex = i; 142 | break; 143 | } 144 | } 145 | 146 | [CATransaction commit]; 147 | } 148 | 149 | - (BOOL)_createCommentLayer:(NSDictionary*)comment setPosition:(BOOL)setPosition pause:(BOOL)pause andDuration:(NSTimeInterval)duration_form_data{ 150 | CGPoint commentOffset; 151 | 152 | CGFloat px = _screenSize.width; 153 | CGFloat py = 0, ty = 0; 154 | CGFloat videoTop = floor((_view.bounds.size.height / 2) - (_screenSize.height / 2)); 155 | CGFloat videoBottom = floor(videoTop + _screenSize.height); 156 | BOOL isDanmaku = NO; 157 | NSTimeInterval duration; 158 | 159 | py = videoTop; 160 | 161 | CommentTextLayer *commentTextLayer = [CommentTextLayer 162 | layerWithCommentInfo:comment 163 | screenSize:_screenSize 164 | isLandscape:_isLandscape]; 165 | 166 | 167 | NSInteger commentPosition = [comment[@"position"] intValue]; 168 | if (commentPosition == COMMENT_POSITION_NORMAL) { 169 | if (duration_form_data==0) { 170 | duration = COMMENT_DURATION; 171 | } 172 | else{ 173 | duration = duration_form_data; 174 | } 175 | } 176 | else { 177 | if (duration_form_data==0) { 178 | duration = COMMENT_DURATION; 179 | } 180 | else{ 181 | duration = duration_form_data; 182 | } 183 | px = 0.f; 184 | if (commentPosition == COMMENT_POSITION_BOTTOM) { 185 | py = videoBottom - commentTextLayer.frame.size.height; 186 | } 187 | } 188 | commentOffset = CGPointMake(px, py); 189 | 190 | 191 | @synchronized(_showCommentItems) { 192 | CGRect rect, targetRect; 193 | rect.size = commentTextLayer.frame.size; 194 | 195 | for (NSDictionary *item in _showCommentItems) { 196 | rect.origin = commentOffset; 197 | 198 | CommentTextLayer *showCommentTextLayer = item[@"commentTextLayer"]; 199 | CALayer *layer = showCommentTextLayer.presentationLayer; 200 | if (layer) { 201 | targetRect.origin = layer.position; 202 | } 203 | else { 204 | targetRect.origin = showCommentTextLayer.position; 205 | } 206 | targetRect.size = showCommentTextLayer.frame.size; 207 | 208 | if (commentPosition == COMMENT_POSITION_TOP) { 209 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_TOP) continue; 210 | 211 | if ([self _checkCollisionHeight:rect targetRect:targetRect]) { 212 | ty = CGRectGetHeight(targetRect); 213 | commentOffset = CGPointMake(px, targetRect.origin.y + ty); 214 | } 215 | 216 | if ((commentOffset.y + ty) > videoBottom) { 217 | isDanmaku = YES; 218 | break; 219 | } 220 | } 221 | else if (commentPosition == COMMENT_POSITION_BOTTOM) { 222 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_BOTTOM) continue; 223 | 224 | if ([self _checkCollisionHeight:rect targetRect:targetRect]) { 225 | ty = CGRectGetHeight(targetRect); 226 | commentOffset = CGPointMake(px, targetRect.origin.y - ty); 227 | } 228 | 229 | if ((commentOffset.y - ty) < videoTop) { 230 | isDanmaku = YES; 231 | break; 232 | } 233 | } 234 | else if (commentPosition == COMMENT_POSITION_NORMAL) { 235 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_NORMAL) continue; 236 | 237 | if ([self _checkCollisionHeight:rect targetRect:targetRect]) { 238 | if ([self _checkCollisionWidth:rect targetRect:targetRect]) { 239 | ty = CGRectGetHeight(targetRect); 240 | commentOffset = CGPointMake(px, targetRect.origin.y + ty); 241 | } 242 | if ((commentOffset.y + ty) > videoBottom) { 243 | isDanmaku = YES; 244 | break; 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | if (isDanmaku) { 252 | NSInteger y = arc4random_uniform(_screenSize.height - CGRectGetHeight(commentTextLayer.frame)); 253 | commentOffset = CGPointMake(px, py + y); 254 | } 255 | 256 | commentTextLayer.position = commentOffset; 257 | [self _addShowCommentItem:@{ 258 | @"commentTextLayer": commentTextLayer, 259 | @"duration": @(duration), 260 | }]; 261 | 262 | [_view.layer addSublayer:commentTextLayer]; 263 | 264 | if (commentPosition == COMMENT_POSITION_NORMAL) { 265 | 266 | CGFloat animateDuration; 267 | 268 | if (duration_form_data == 0) { 269 | animateDuration = COMMENT_DURATION; 270 | } 271 | else{ 272 | animateDuration = duration_form_data; 273 | } 274 | 275 | //正常弹幕 276 | if (setPosition) { 277 | CGFloat currentTime = _moviePosition * 1000.f; 278 | CGFloat diff = (currentTime - commentTextLayer.vpos) / 1000.f; 279 | CGFloat perWidth = (_screenSize.width + commentTextLayer.frame.size.width) / animateDuration; 280 | commentOffset.x = (_screenSize.width) - (perWidth * diff); 281 | animateDuration = animateDuration - diff; 282 | } 283 | 284 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; 285 | animation.repeatCount = 0; 286 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 287 | animation.duration = animateDuration; 288 | animation.removedOnCompletion = NO; 289 | animation.fillMode = kCAFillModeForwards; 290 | 291 | animation.fromValue = [NSValue valueWithCGPoint:commentOffset]; 292 | commentOffset.x = -commentTextLayer.frame.size.width; 293 | animation.toValue = [NSValue valueWithCGPoint:commentOffset]; 294 | 295 | [commentTextLayer addAnimation:animation forKey:@"Comment"]; 296 | 297 | if (pause) { 298 | [self stopCommentLayer]; 299 | } 300 | } 301 | 302 | return YES; 303 | } 304 | 305 | - (void)_addShowCommentItem:(NSDictionary*)commentItem { 306 | CommentTextLayer *commentTextLayer = commentItem[@"commentTextLayer"]; 307 | 308 | @synchronized(_showCommentItems) { 309 | if (commentTextLayer.commentPosition == COMMENT_POSITION_NORMAL) { 310 | NSInteger max = [_showCommentItems count]; 311 | 312 | for (NSInteger i = (max - 1); i >= 0; i--) { 313 | CommentTextLayer *showCommentTextLayer = _showCommentItems[i][@"commentTextLayer"]; 314 | 315 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_NORMAL) continue; 316 | 317 | if (commentTextLayer.position.y >= showCommentTextLayer.position.y) { 318 | [_showCommentItems insertObject:commentItem atIndex:(i + 1)]; 319 | return; 320 | } 321 | } 322 | } 323 | 324 | [_showCommentItems addObject:commentItem]; 325 | } 326 | } 327 | 328 | - (BOOL)_checkCollisionHeight:(CGRect)rect targetRect:(CGRect)targetRect { 329 | CGFloat commentTop1 = rect.origin.y; 330 | CGFloat commentBottom1 = rect.origin.y + rect.size.height; 331 | CGFloat commentTop2 = targetRect.origin.y; 332 | CGFloat commentBottom2 = targetRect.origin.y + targetRect.size.height; 333 | 334 | BOOL b1 = (commentTop1 >= commentTop2 && commentTop1 <= commentBottom2) || 335 | (commentBottom1 >= commentTop2 && commentBottom1 <= commentBottom2); 336 | BOOL b2 = (commentTop2 >= commentTop1 && commentTop2 <= commentBottom1) || 337 | (commentBottom2 >= commentTop1 && commentBottom2 <= commentBottom1); 338 | 339 | return (b1 || b2); 340 | } 341 | 342 | - (BOOL)_checkCollisionWidth:(CGRect)rect targetRect:(CGRect)targetRect { 343 | CGSize screenSize = _screenSize; 344 | CGFloat perWidth = (screenSize.width + rect.size.width) / COMMENT_DURATION; 345 | CGFloat perWidth2 = (screenSize.width + targetRect.size.width) / COMMENT_DURATION; 346 | 347 | CGFloat targetFinishPosition, finishPosition; 348 | CGFloat add = COMMENT_DURATION / 3; 349 | CGFloat max = add * 3; 350 | 351 | for (CGFloat i = 0; i <= max; i += add) { 352 | targetFinishPosition = targetRect.origin.x - (perWidth2 * i) + targetRect.size.width; 353 | 354 | if (targetFinishPosition <= -targetRect.size.width) break; 355 | 356 | finishPosition = screenSize.width - (perWidth * i); 357 | 358 | if (targetFinishPosition > finishPosition) { 359 | return YES; 360 | } 361 | } 362 | 363 | return NO; 364 | } 365 | 366 | - (void)_deleteCommentLayer { 367 | CGFloat currentTime = floor(_moviePosition * 1000.f); 368 | NSMutableArray *deleteItems = [NSMutableArray array]; 369 | 370 | @synchronized(_showCommentItems) { 371 | for (NSDictionary *item in _showCommentItems) { 372 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 373 | CALayer *layer = commentTextLayer.presentationLayer; 374 | if (commentTextLayer.commentPosition == COMMENT_POSITION_NORMAL) { 375 | if (layer.frame.origin.x <= -commentTextLayer.frame.size.width) { 376 | [commentTextLayer removeFromSuperlayer]; 377 | [deleteItems addObject:item]; 378 | } 379 | } 380 | else { 381 | if (currentTime > (CGFloat)(commentTextLayer.vpos + [item[@"duration"] doubleValue] * 1000.0)) { 382 | NSLog(@"commentTextLayer.vpos - %ld,duration - %lf",(long)commentTextLayer.vpos,[item[@"duration"] doubleValue] * 1000.0); 383 | [commentTextLayer removeFromSuperlayer]; 384 | [deleteItems addObject:item]; 385 | } 386 | } 387 | } 388 | [_showCommentItems removeObjectsInArray:deleteItems]; 389 | } 390 | deleteItems = nil; 391 | } 392 | 393 | - (void)deleteAllCommentLayer { 394 | @synchronized(_showCommentItems) { 395 | for (NSDictionary *item in _showCommentItems) { 396 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 397 | [commentTextLayer removeFromSuperlayer]; 398 | } 399 | [_showCommentItems removeAllObjects]; 400 | } 401 | } 402 | 403 | - (void)startCommentLayer { 404 | [CATransaction begin]; 405 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 406 | 407 | @synchronized(_showCommentItems) { 408 | for (NSDictionary *item in _showCommentItems) { 409 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 410 | [commentTextLayer pauseAnimation:NO]; 411 | } 412 | } 413 | 414 | [CATransaction commit]; 415 | } 416 | 417 | - (void)stopCommentLayer { 418 | [CATransaction begin]; 419 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 420 | 421 | @synchronized(_showCommentItems) { 422 | for (NSDictionary *item in _showCommentItems) { 423 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 424 | [commentTextLayer pauseAnimation:YES]; 425 | } 426 | } 427 | 428 | [CATransaction commit]; 429 | } 430 | 431 | - (void)seekCommentLayer:(BOOL)pause { 432 | if (_delegate) { 433 | _moviePosition = [_delegate willShowComments:YES]; 434 | } 435 | if (_moviePosition == -1) return; 436 | [self deleteAllCommentLayer]; 437 | CGFloat currentTime = floor(_moviePosition * 1000.f); 438 | CGFloat normalCommentTime = currentTime - (COMMENT_DURATION * 1000.f); 439 | CGFloat topBottomCommentTime = currentTime - (COMMENT_TOP_OR_BOTTOM_DURATION * 1000.f); 440 | NSInteger max = [_comments count]; 441 | NSInteger i; 442 | 443 | [CATransaction begin]; 444 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 445 | 446 | _currentIndex = 0; 447 | 448 | for (i = 0; i < max; i++) { 449 | NSDictionary *comment = _comments[i]; 450 | 451 | if (currentTime < [comment[@"vpos"] floatValue]) { 452 | _currentIndex = i; 453 | break; 454 | } 455 | 456 | if (normalCommentTime < [comment[@"vpos"] floatValue] && [comment[@"position"] intValue] == COMMENT_POSITION_NORMAL) { 457 | [self _createCommentLayer:comment setPosition:YES pause:pause andDuration:[comment[@"duration"] floatValue] ]; 458 | } 459 | else if (topBottomCommentTime < [comment[@"vpos"] floatValue] && [comment[@"position"] intValue] != COMMENT_POSITION_NORMAL) { 460 | [self _createCommentLayer:comment setPosition:NO pause:pause andDuration:[comment[@"duration"] floatValue]]; 461 | } 462 | } 463 | 464 | _currentIndex = i; 465 | 466 | [CATransaction commit]; 467 | } 468 | @end 469 | -------------------------------------------------------------------------------- /DanmakuLib/RiverRunCommentUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentUtil.h 3 | // SmilePlayer2 4 | // 5 | // Created by CrazyPeter on 2014/04/05. 6 | // 7 | // 8 | 9 | #import 10 | #import "CommentTextLayer.h" 11 | 12 | @interface RiverRunCommentUtil : NSObject 13 | + (NSInteger)commentPosition:(NSString*)position; 14 | + (NSInteger)commentSize:(NSString*)fontSize; 15 | + (NSString*)getFontSize; 16 | + (NSString*)getPosition; 17 | @end 18 | -------------------------------------------------------------------------------- /DanmakuLib/RiverRunCommentUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentUtil.m 3 | // 4 | // 5 | // Created by CrazyPeter on 2014/04/05. 6 | // 7 | // 8 | 9 | #import "RiverRunCommentUtil.h" 10 | 11 | @implementation RiverRunCommentUtil 12 | + (NSInteger)commentPosition:(NSString*)position{ 13 | if ([position isEqualToString:@"top"]) { 14 | return COMMENT_POSITION_TOP; 15 | } 16 | else if ([position isEqualToString:@"bottom"]) { 17 | return COMMENT_POSITION_BOTTOM; 18 | } 19 | return COMMENT_POSITION_NORMAL; 20 | } 21 | 22 | + (NSInteger)commentSize:(NSString*)fontSize{ 23 | if ([fontSize isEqualToString:@"big"]) { 24 | return COMMENT_SIZE_BIG; 25 | } 26 | else if ([fontSize isEqualToString:@"small"]) { 27 | return COMMENT_SIZE_SMALL; 28 | } 29 | return COMMENT_SIZE_NORMAL; 30 | } 31 | 32 | /* 33 | 颜色数值转换:#ababab 34 | */ 35 | +(UIColor *)changeColor:(NSString *)str{ 36 | unsigned int red,green,blue; 37 | NSString * str1 = [str substringWithRange:NSMakeRange(1, 2)]; 38 | NSString * str2 = [str substringWithRange:NSMakeRange(3, 2)]; 39 | NSString * str3 = [str substringWithRange:NSMakeRange(5, 2)]; 40 | 41 | NSScanner * canner = [NSScanner scannerWithString:str1]; 42 | [canner scanHexInt:&red]; 43 | 44 | canner = [NSScanner scannerWithString:str2]; 45 | [canner scanHexInt:&green]; 46 | 47 | canner = [NSScanner scannerWithString:str3]; 48 | [canner scanHexInt:&blue]; 49 | UIColor * color = [UIColor colorWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:1.0]; 50 | return color; 51 | } 52 | 53 | + (NSString*)getPosition 54 | { 55 | int i = arc4random_uniform(3); 56 | if (i == 1) { 57 | return @"top"; 58 | } 59 | else if(i == 2){ 60 | return @"bottom"; 61 | } 62 | else 63 | return @""; 64 | } 65 | 66 | + (NSString*)getFontSize 67 | { 68 | int i = arc4random_uniform(3); 69 | if (i == 1) { 70 | return @"big"; 71 | } 72 | else if(i == 2){ 73 | return @"small"; 74 | } 75 | else 76 | return @""; 77 | } 78 | @end 79 | -------------------------------------------------------------------------------- /DanmakuMaster.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 29F98BB11AD5130200831923 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29F98BB01AD5130200831923 /* main.m */; }; 11 | 29F98BB41AD5130200831923 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 29F98BB31AD5130200831923 /* AppDelegate.m */; }; 12 | 29F98BB71AD5130200831923 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 29F98BB61AD5130200831923 /* ViewController.m */; }; 13 | 29F98BBA1AD5130200831923 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29F98BB81AD5130200831923 /* Main.storyboard */; }; 14 | 29F98BBC1AD5130200831923 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29F98BBB1AD5130200831923 /* Images.xcassets */; }; 15 | 29F98BBF1AD5130200831923 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29F98BBD1AD5130200831923 /* LaunchScreen.xib */; }; 16 | 29F98BDB1AD5133A00831923 /* CommentTextLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 29F98BD61AD5133A00831923 /* CommentTextLayer.m */; }; 17 | 29F98BDC1AD5133A00831923 /* RiverRunCommentManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 29F98BD81AD5133A00831923 /* RiverRunCommentManager.m */; }; 18 | 29F98BDD1AD5133A00831923 /* RiverRunCommentUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 29F98BDA1AD5133A00831923 /* RiverRunCommentUtil.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 29F98BAB1AD5130200831923 /* DanmakuMaster.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DanmakuMaster.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 29F98BAF1AD5130200831923 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | 29F98BB01AD5130200831923 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 25 | 29F98BB21AD5130200831923 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 26 | 29F98BB31AD5130200831923 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 27 | 29F98BB51AD5130200831923 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 28 | 29F98BB61AD5130200831923 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 29 | 29F98BB91AD5130200831923 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | 29F98BBB1AD5130200831923 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 31 | 29F98BBE1AD5130200831923 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 32 | 29F98BD51AD5133A00831923 /* CommentTextLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentTextLayer.h; sourceTree = ""; }; 33 | 29F98BD61AD5133A00831923 /* CommentTextLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentTextLayer.m; sourceTree = ""; }; 34 | 29F98BD71AD5133A00831923 /* RiverRunCommentManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RiverRunCommentManager.h; sourceTree = ""; }; 35 | 29F98BD81AD5133A00831923 /* RiverRunCommentManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RiverRunCommentManager.m; sourceTree = ""; }; 36 | 29F98BD91AD5133A00831923 /* RiverRunCommentUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RiverRunCommentUtil.h; sourceTree = ""; }; 37 | 29F98BDA1AD5133A00831923 /* RiverRunCommentUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RiverRunCommentUtil.m; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | 29F98BA81AD5130200831923 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | 29F98BA21AD5130200831923 = { 52 | isa = PBXGroup; 53 | children = ( 54 | 29F98BAD1AD5130200831923 /* DanmakuMaster */, 55 | 29F98BAC1AD5130200831923 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | 29F98BAC1AD5130200831923 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 29F98BAB1AD5130200831923 /* DanmakuMaster.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | 29F98BAD1AD5130200831923 /* DanmakuMaster */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 29F98BD41AD5133A00831923 /* DanmakuLib */, 71 | 29F98BB21AD5130200831923 /* AppDelegate.h */, 72 | 29F98BB31AD5130200831923 /* AppDelegate.m */, 73 | 29F98BB51AD5130200831923 /* ViewController.h */, 74 | 29F98BB61AD5130200831923 /* ViewController.m */, 75 | 29F98BB81AD5130200831923 /* Main.storyboard */, 76 | 29F98BBB1AD5130200831923 /* Images.xcassets */, 77 | 29F98BBD1AD5130200831923 /* LaunchScreen.xib */, 78 | 29F98BAE1AD5130200831923 /* Supporting Files */, 79 | ); 80 | path = DanmakuMaster; 81 | sourceTree = ""; 82 | }; 83 | 29F98BAE1AD5130200831923 /* Supporting Files */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 29F98BAF1AD5130200831923 /* Info.plist */, 87 | 29F98BB01AD5130200831923 /* main.m */, 88 | ); 89 | name = "Supporting Files"; 90 | sourceTree = ""; 91 | }; 92 | 29F98BD41AD5133A00831923 /* DanmakuLib */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 29F98BD51AD5133A00831923 /* CommentTextLayer.h */, 96 | 29F98BD61AD5133A00831923 /* CommentTextLayer.m */, 97 | 29F98BD71AD5133A00831923 /* RiverRunCommentManager.h */, 98 | 29F98BD81AD5133A00831923 /* RiverRunCommentManager.m */, 99 | 29F98BD91AD5133A00831923 /* RiverRunCommentUtil.h */, 100 | 29F98BDA1AD5133A00831923 /* RiverRunCommentUtil.m */, 101 | ); 102 | path = DanmakuLib; 103 | sourceTree = ""; 104 | }; 105 | /* End PBXGroup section */ 106 | 107 | /* Begin PBXNativeTarget section */ 108 | 29F98BAA1AD5130200831923 /* DanmakuMaster */ = { 109 | isa = PBXNativeTarget; 110 | buildConfigurationList = 29F98BCE1AD5130200831923 /* Build configuration list for PBXNativeTarget "DanmakuMaster" */; 111 | buildPhases = ( 112 | 29F98BA71AD5130200831923 /* Sources */, 113 | 29F98BA81AD5130200831923 /* Frameworks */, 114 | 29F98BA91AD5130200831923 /* Resources */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = DanmakuMaster; 121 | productName = DanmakuMaster; 122 | productReference = 29F98BAB1AD5130200831923 /* DanmakuMaster.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | 29F98BA31AD5130200831923 /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | LastUpgradeCheck = 0730; 132 | ORGANIZATIONNAME = CrazyPeter; 133 | TargetAttributes = { 134 | 29F98BAA1AD5130200831923 = { 135 | CreatedOnToolsVersion = 6.1.1; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 29F98BA61AD5130200831923 /* Build configuration list for PBXProject "DanmakuMaster" */; 140 | compatibilityVersion = "Xcode 3.2"; 141 | developmentRegion = English; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 29F98BA21AD5130200831923; 148 | productRefGroup = 29F98BAC1AD5130200831923 /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 29F98BAA1AD5130200831923 /* DanmakuMaster */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 29F98BA91AD5130200831923 /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 29F98BBA1AD5130200831923 /* Main.storyboard in Resources */, 163 | 29F98BBF1AD5130200831923 /* LaunchScreen.xib in Resources */, 164 | 29F98BBC1AD5130200831923 /* Images.xcassets in Resources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXResourcesBuildPhase section */ 169 | 170 | /* Begin PBXSourcesBuildPhase section */ 171 | 29F98BA71AD5130200831923 /* Sources */ = { 172 | isa = PBXSourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | 29F98BB71AD5130200831923 /* ViewController.m in Sources */, 176 | 29F98BDC1AD5133A00831923 /* RiverRunCommentManager.m in Sources */, 177 | 29F98BDD1AD5133A00831923 /* RiverRunCommentUtil.m in Sources */, 178 | 29F98BB41AD5130200831923 /* AppDelegate.m in Sources */, 179 | 29F98BDB1AD5133A00831923 /* CommentTextLayer.m in Sources */, 180 | 29F98BB11AD5130200831923 /* main.m in Sources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXSourcesBuildPhase section */ 185 | 186 | /* Begin PBXVariantGroup section */ 187 | 29F98BB81AD5130200831923 /* Main.storyboard */ = { 188 | isa = PBXVariantGroup; 189 | children = ( 190 | 29F98BB91AD5130200831923 /* Base */, 191 | ); 192 | name = Main.storyboard; 193 | sourceTree = ""; 194 | }; 195 | 29F98BBD1AD5130200831923 /* LaunchScreen.xib */ = { 196 | isa = PBXVariantGroup; 197 | children = ( 198 | 29F98BBE1AD5130200831923 /* Base */, 199 | ); 200 | name = LaunchScreen.xib; 201 | sourceTree = ""; 202 | }; 203 | /* End PBXVariantGroup section */ 204 | 205 | /* Begin XCBuildConfiguration section */ 206 | 29F98BCC1AD5130200831923 /* Debug */ = { 207 | isa = XCBuildConfiguration; 208 | buildSettings = { 209 | ALWAYS_SEARCH_USER_PATHS = NO; 210 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 211 | CLANG_CXX_LIBRARY = "libc++"; 212 | CLANG_ENABLE_MODULES = YES; 213 | CLANG_ENABLE_OBJC_ARC = YES; 214 | CLANG_WARN_BOOL_CONVERSION = YES; 215 | CLANG_WARN_CONSTANT_CONVERSION = YES; 216 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 217 | CLANG_WARN_EMPTY_BODY = YES; 218 | CLANG_WARN_ENUM_CONVERSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 224 | COPY_PHASE_STRIP = NO; 225 | ENABLE_STRICT_OBJC_MSGSEND = YES; 226 | ENABLE_TESTABILITY = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 228 | GCC_DYNAMIC_NO_PIC = NO; 229 | GCC_OPTIMIZATION_LEVEL = 0; 230 | GCC_PREPROCESSOR_DEFINITIONS = ( 231 | "DEBUG=1", 232 | "$(inherited)", 233 | ); 234 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 235 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 236 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 237 | GCC_WARN_UNDECLARED_SELECTOR = YES; 238 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 239 | GCC_WARN_UNUSED_FUNCTION = YES; 240 | GCC_WARN_UNUSED_VARIABLE = YES; 241 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 242 | MTL_ENABLE_DEBUG_INFO = YES; 243 | ONLY_ACTIVE_ARCH = YES; 244 | SDKROOT = iphoneos; 245 | }; 246 | name = Debug; 247 | }; 248 | 29F98BCD1AD5130200831923 /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_BOOL_CONVERSION = YES; 257 | CLANG_WARN_CONSTANT_CONVERSION = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = YES; 267 | ENABLE_NS_ASSERTIONS = NO; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu99; 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 277 | MTL_ENABLE_DEBUG_INFO = NO; 278 | SDKROOT = iphoneos; 279 | VALIDATE_PRODUCT = YES; 280 | }; 281 | name = Release; 282 | }; 283 | 29F98BCF1AD5130200831923 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 287 | INFOPLIST_FILE = DanmakuMaster/Info.plist; 288 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 289 | PRODUCT_BUNDLE_IDENTIFIER = "com.peter.$(PRODUCT_NAME:rfc1034identifier)"; 290 | PRODUCT_NAME = "$(TARGET_NAME)"; 291 | }; 292 | name = Debug; 293 | }; 294 | 29F98BD01AD5130200831923 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 298 | INFOPLIST_FILE = DanmakuMaster/Info.plist; 299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 300 | PRODUCT_BUNDLE_IDENTIFIER = "com.peter.$(PRODUCT_NAME:rfc1034identifier)"; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | }; 303 | name = Release; 304 | }; 305 | /* End XCBuildConfiguration section */ 306 | 307 | /* Begin XCConfigurationList section */ 308 | 29F98BA61AD5130200831923 /* Build configuration list for PBXProject "DanmakuMaster" */ = { 309 | isa = XCConfigurationList; 310 | buildConfigurations = ( 311 | 29F98BCC1AD5130200831923 /* Debug */, 312 | 29F98BCD1AD5130200831923 /* Release */, 313 | ); 314 | defaultConfigurationIsVisible = 0; 315 | defaultConfigurationName = Release; 316 | }; 317 | 29F98BCE1AD5130200831923 /* Build configuration list for PBXNativeTarget "DanmakuMaster" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | 29F98BCF1AD5130200831923 /* Debug */, 321 | 29F98BD01AD5130200831923 /* Release */, 322 | ); 323 | defaultConfigurationIsVisible = 0; 324 | defaultConfigurationName = Release; 325 | }; 326 | /* End XCConfigurationList section */ 327 | }; 328 | rootObject = 29F98BA31AD5130200831923 /* Project object */; 329 | } 330 | -------------------------------------------------------------------------------- /DanmakuMaster.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DanmakuMaster.xcodeproj/project.xcworkspace/xcshareddata/DanmakuMaster.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "E899153208F22DEC947D705AD2D5BF2D370293EF", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "EE65A933767A1D9A4E717C837796DBB99727F334" : 0, 8 | "E899153208F22DEC947D705AD2D5BF2D370293EF" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "AC687F69-529F-4D1E-9DCB-063F64D1DAD3", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "EE65A933767A1D9A4E717C837796DBB99727F334" : "", 13 | "E899153208F22DEC947D705AD2D5BF2D370293EF" : "DanmukuMaster-iOS\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "DanmakuMaster", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "DanmakuMaster.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CrazyPeter\/DanmukuMaster-iOS.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "E899153208F22DEC947D705AD2D5BF2D370293EF" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CrazyPeter\/CrazyPeter.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "EE65A933767A1D9A4E717C837796DBB99727F334" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /DanmakuMaster.xcodeproj/project.xcworkspace/xcuserdata/PeterKong.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyPeter/DanmukuMaster-iOS/9af88fb4e376ff6da5167346f4f3176ad2507655/DanmakuMaster.xcodeproj/project.xcworkspace/xcuserdata/PeterKong.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /DanmakuMaster.xcodeproj/xcuserdata/PeterKong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /DanmakuMaster.xcodeproj/xcuserdata/PeterKong.xcuserdatad/xcschemes/DanmakuMaster.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /DanmakuMaster.xcodeproj/xcuserdata/PeterKong.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DanmakuMaster.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 29F98BAA1AD5130200831923 16 | 17 | primary 18 | 19 | 20 | 29F98BC31AD5130200831923 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /DanmakuMaster/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // DanmakuMaster 4 | // 5 | // Created by Peter Kong on 15-4-8. 6 | // Copyright (c) 2015年 CrazyPeter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /DanmakuMaster/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // DanmakuMaster 4 | // 5 | // Created by Peter Kong on 15-4-8. 6 | // Copyright (c) 2015年 CrazyPeter. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /DanmakuMaster/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DanmakuMaster/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 36 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /DanmakuMaster/DanmakuLib/CommentTextLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CommentTextLayer.h 3 | // SmilePlayer2 4 | // 5 | // Created by pontago on 2014/04/05. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | extern NSString* const COMMENT_FONT_NAME; 12 | 13 | typedef enum _COMMENT_POSITION { 14 | COMMENT_POSITION_NORMAL, 15 | COMMENT_POSITION_TOP, 16 | COMMENT_POSITION_BOTTOM 17 | } COMMENT_POSITION; 18 | 19 | typedef enum _COMMENT_SIZE { 20 | COMMENT_SIZE_NORMAL, 21 | COMMENT_SIZE_SMALL, 22 | COMMENT_SIZE_BIG 23 | } COMMENT_SIZE; 24 | 25 | @interface CommentTextLayer : CALayer 26 | + (id)layerWithCommentInfo:(NSDictionary*)commentInfo screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape; 27 | +(UIColor *)changeColor:(NSString *)str; 28 | - (void)updateFrame:(CGSize)screenSize; 29 | - (CGFloat)commentFontSize; 30 | - (CGFloat)adjustsFontSizeToFitWidth:(CGSize)screenSize; 31 | - (void)pauseAnimation:(BOOL)aPause; 32 | 33 | @property (nonatomic) NSInteger vpos; 34 | @property (nonatomic) NSInteger commentPosition; 35 | @property (nonatomic) NSInteger commentSize; 36 | @property (nonatomic) BOOL isLandscape; 37 | @property (nonatomic, strong) NSString *string; 38 | @property (nonatomic, strong) UIColor *textColor; 39 | @property (nonatomic, strong) UIColor *strokeColor; 40 | @property (nonatomic) CGFloat fontSize; 41 | @end 42 | -------------------------------------------------------------------------------- /DanmakuMaster/DanmakuLib/CommentTextLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CommentTextLayer.m 3 | // SmilePlayer2 4 | // 5 | // Created by pontago on 2014/04/05. 6 | // 7 | // 8 | 9 | #import "CommentTextLayer.h" 10 | #import 11 | #import 12 | 13 | NSString* const COMMENT_FONT_NAME = @"Heiti SC"; 14 | 15 | #define HEXCOLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 \ 16 | green:((c>>8)&0xFF)/255.0 \ 17 | blue:(c&0xFF)/255.0 \ 18 | alpha:1.0]; 19 | 20 | @interface CommentTextLayer () { 21 | } 22 | - (UIFont*)_createFont:(CGFloat)fontSize; 23 | - (CTFontRef)_createFontRef; 24 | - (CTFrameRef)_createFrameRef; 25 | - (CGImageRef)_createStrokeImageRef:(CTFrameRef)frameRef; 26 | @end 27 | 28 | @implementation CommentTextLayer 29 | 30 | -(void)dealloc 31 | { 32 | // NSLog(@"layer - dealloc"); 33 | } 34 | 35 | + (id)layerWithCommentInfo:(NSDictionary*)commentInfo screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape { 36 | CommentTextLayer *commentTextLayer = [CommentTextLayer layer]; 37 | if (commentTextLayer) { 38 | commentTextLayer.anchorPoint = CGPointMake(0.f, 0.f); 39 | commentTextLayer.drawsAsynchronously = YES; 40 | commentTextLayer.contentsScale = [[UIScreen mainScreen] scale]; 41 | commentTextLayer.string = commentInfo[@"body"]; 42 | 43 | UIColor *color; 44 | if(![commentInfo[@"color"] isEqualToString:@"-1"]){ 45 | color = [CommentTextLayer changeColor:commentInfo[@"color"]]; 46 | } 47 | else{ 48 | color = [CommentTextLayer changeColor:@"#ffffff"]; 49 | } 50 | commentTextLayer.textColor = color; 51 | 52 | if ([commentInfo[@"color"] intValue] == 0x000000) { 53 | commentTextLayer.strokeColor = [UIColor colorWithWhite:1.f alpha:0.6f]; 54 | } 55 | else { 56 | commentTextLayer.strokeColor = [UIColor colorWithWhite:0.f alpha:0.3f]; 57 | } 58 | 59 | commentTextLayer.vpos = [commentInfo[@"vpos"] intValue]; 60 | commentTextLayer.commentPosition = [commentInfo[@"position"] intValue]; 61 | commentTextLayer.isLandscape = isLandscape; 62 | 63 | NSNumber *fontSizeNum = commentInfo[@"fontSize"]; 64 | commentTextLayer.commentSize = [fontSizeNum intValue]; 65 | 66 | [commentTextLayer updateFrame:screenSize]; 67 | } 68 | return commentTextLayer; 69 | } 70 | 71 | - (UIFont*)_createFont:(CGFloat)fontSize { 72 | #if 0 73 | NSDictionary *fontAttributes = @{ 74 | UIFontDescriptorNameAttribute: COMMENT_FONT_NAME, 75 | UIFontDescriptorCascadeListAttribute: @[ 76 | [UIFontDescriptor fontDescriptorWithName:@"AppleColorEmoji" size:fontSize] 77 | ] 78 | }; 79 | UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:fontAttributes]; 80 | return [UIFont fontWithDescriptor:fontDescriptor size:fontSize]; 81 | #endif 82 | return [UIFont fontWithName:COMMENT_FONT_NAME size:fontSize]; 83 | } 84 | 85 | - (CTFontRef)_createFontRef { 86 | CTFontDescriptorRef emojiFontDescriptorRef = CTFontDescriptorCreateWithNameAndSize(CFSTR("AppleColorEmoji"), self.fontSize); 87 | 88 | NSDictionary *fontAttributes = @{ 89 | (id)kCTFontNameAttribute: COMMENT_FONT_NAME, 90 | (id)kCTFontCascadeListAttribute: @[ 91 | (__bridge id)emojiFontDescriptorRef, 92 | ] 93 | }; 94 | 95 | CTFontDescriptorRef fontDescriptorRef = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)fontAttributes); 96 | CTFontRef fontRef = CTFontCreateWithFontDescriptor(fontDescriptorRef, self.fontSize, NULL); 97 | 98 | CFRelease(fontDescriptorRef); 99 | CFRelease(emojiFontDescriptorRef); 100 | 101 | return fontRef; 102 | } 103 | 104 | - (CTFrameRef)_createFrameRef { 105 | NSString *str = self.string; 106 | CTFontRef fontRef = [self _createFontRef]; 107 | 108 | CTTextAlignment alignment = kCTTextAlignmentCenter; 109 | CTParagraphStyleSetting paragraphStyleSettings[] = { 110 | { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment }, 111 | }; 112 | CTParagraphStyleRef paragraphStyleRef = CTParagraphStyleCreate(paragraphStyleSettings, 1); 113 | 114 | NSAttributedString *attrStr = [[NSAttributedString alloc] 115 | initWithString:str 116 | attributes:@{ 117 | (NSString*)kCTFontAttributeName: (__bridge id)fontRef, 118 | (NSString*)kCTForegroundColorAttributeName: self.textColor, 119 | (NSString*)kCTParagraphStyleAttributeName: (__bridge id)paragraphStyleRef 120 | }]; 121 | 122 | 123 | CGMutablePathRef pathRef = CGPathCreateMutable(); 124 | CGPathAddRect(pathRef, NULL, self.bounds); 125 | CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFMutableAttributedStringRef)attrStr); 126 | CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, str.length), pathRef, NULL); 127 | 128 | CFRelease(fontRef); 129 | CGPathRelease(pathRef); 130 | CFRelease(framesetterRef); 131 | CFRelease(paragraphStyleRef); 132 | 133 | return frameRef; 134 | } 135 | 136 | - (CGImageRef)_createStrokeImageRef:(CTFrameRef)frameRef { 137 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0); 138 | CGContextRef context = UIGraphicsGetCurrentContext(); 139 | 140 | CGContextSetTextDrawingMode(context, kCGTextStroke); 141 | CGContextSetLineWidth(context, 2.f); 142 | CGContextSetLineJoin(context, kCGLineJoinRound); 143 | CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); 144 | CTFrameDraw(frameRef, context); 145 | 146 | CGImageRef clippingMask = CGBitmapContextCreateImage(context); 147 | 148 | CGContextClearRect(context, CGRectMake(0, 0, self.bounds.size.width + 2.f, self.bounds.size.height + 2.f)); 149 | CGContextClipToMask(context, self.bounds, clippingMask); 150 | 151 | CGContextTranslateCTM(context, 0.0, CGRectGetHeight(self.bounds)); 152 | CGContextScaleCTM(context, 1.0, -1.0); 153 | CGContextSetFillColorWithColor(context, self.strokeColor.CGColor); 154 | CGContextFillRect(context, self.bounds); 155 | 156 | CGImageRef imageRef = CGBitmapContextCreateImage(context); 157 | UIGraphicsEndImageContext(); 158 | 159 | CGImageRelease(clippingMask); 160 | return imageRef; 161 | } 162 | 163 | - (void)drawInContext:(CGContextRef)ctx { 164 | CGContextTranslateCTM(ctx, 0.0, CGRectGetHeight(self.bounds)); 165 | CGContextScaleCTM(ctx, 1.0, -1.0); 166 | 167 | CTFrameRef frameRef = [self _createFrameRef]; 168 | CGImageRef strokeImageRef = [self _createStrokeImageRef:frameRef]; 169 | 170 | UIGraphicsPushContext(ctx); 171 | 172 | CGContextDrawImage(ctx, self.bounds, strokeImageRef); 173 | CTFrameDraw(frameRef, ctx); 174 | 175 | UIGraphicsPopContext(); 176 | 177 | CGImageRelease(strokeImageRef); 178 | CFRelease(frameRef); 179 | } 180 | 181 | ////////////////////////////////////////////////////////////////////////////////////////////主要是这个方法没有回收 182 | - (void)updateFrame:(CGSize)screenSize { 183 | if (self.commentPosition == COMMENT_POSITION_NORMAL) { 184 | self.fontSize = [self commentFontSize]; 185 | UIFont *font = [self _createFont:self.fontSize]; 186 | CGSize textSize = [self.string 187 | boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, screenSize.height) 188 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 189 | attributes:@{ NSFontAttributeName:font } 190 | context:nil].size; 191 | CTFontRef fontRef = [self _createFontRef]; 192 | self.frame = CGRectMake(0, 0, textSize.width, textSize.height + CTFontGetDescent(fontRef)); 193 | CFRelease(fontRef); 194 | } 195 | else { 196 | self.fontSize = [self adjustsFontSizeToFitWidth:screenSize]; 197 | UIFont *font = [self _createFont:self.fontSize]; 198 | CGSize textSize = [self.string 199 | boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, screenSize.height) 200 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 201 | attributes:@{ NSFontAttributeName:font } 202 | context:nil].size; 203 | self.frame = CGRectMake(0, 0, screenSize.width, textSize.height); 204 | } 205 | [self setNeedsDisplay]; 206 | } 207 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 208 | - (CGFloat)commentFontSize { 209 | if (self.commentSize == COMMENT_SIZE_BIG) { 210 | return _isLandscape ? 21.f : 17.f; 211 | } 212 | else if (self.commentSize == COMMENT_SIZE_SMALL) { 213 | return _isLandscape ? 16.f : 12.f; 214 | } 215 | return _isLandscape ? 19.f : 14.f; 216 | } 217 | 218 | - (CGFloat)adjustsFontSizeToFitWidth:(CGSize)screenSize { 219 | UIFont *font; 220 | CGFloat minFontSize = 8.f, currentFontSize = [self commentFontSize]; 221 | CGSize textSize; 222 | 223 | for (; currentFontSize >= minFontSize; --currentFontSize) { 224 | font = [self _createFont:currentFontSize]; 225 | 226 | textSize = [self.string 227 | boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, screenSize.height) 228 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 229 | attributes:@{ NSFontAttributeName:font } 230 | context:nil].size; 231 | 232 | if (textSize.width < screenSize.width) { 233 | break; 234 | } 235 | } 236 | 237 | return currentFontSize; 238 | } 239 | 240 | - (void)pauseAnimation:(BOOL)aPause { 241 | if (aPause) { 242 | CFTimeInterval pausedTime = [self convertTime:CACurrentMediaTime() fromLayer:nil]; 243 | self.speed = 0.f; 244 | self.timeOffset = pausedTime; 245 | } 246 | else { 247 | CFTimeInterval pausedTime = self.timeOffset; 248 | self.speed = 1.f; 249 | self.timeOffset = 0.f; 250 | self.beginTime = 0.f; 251 | CFTimeInterval timeSincePause = [self convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; 252 | self.beginTime = timeSincePause; 253 | } 254 | } 255 | 256 | +(UIColor *)changeColor:(NSString *)str{ 257 | unsigned int red,green,blue; 258 | NSString * str1 = [str substringWithRange:NSMakeRange(1, 2)]; 259 | NSString * str2 = [str substringWithRange:NSMakeRange(3, 2)]; 260 | NSString * str3 = [str substringWithRange:NSMakeRange(5, 2)]; 261 | 262 | NSScanner * canner = [NSScanner scannerWithString:str1]; 263 | [canner scanHexInt:&red]; 264 | 265 | canner = [NSScanner scannerWithString:str2]; 266 | [canner scanHexInt:&green]; 267 | 268 | canner = [NSScanner scannerWithString:str3]; 269 | [canner scanHexInt:&blue]; 270 | UIColor * color = [UIColor colorWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:1.0]; 271 | return color; 272 | } 273 | 274 | @end 275 | -------------------------------------------------------------------------------- /DanmakuMaster/DanmakuLib/RiverRunCommentManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentManager.h 3 | // 4 | // 5 | // Created by CrazyPeter on 2014/04/21. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | extern CGFloat const COMMENT_DURATION; 12 | extern CGFloat const COMMENT_TOP_OR_BOTTOM_DURATION; 13 | 14 | @protocol RiverRunCommentManagerDelegate 15 | - (CGFloat)willShowComments:(BOOL)seek; 16 | @end 17 | 18 | @interface RiverRunCommentManager : NSObject 19 | @property (nonatomic, strong) NSArray *comments; 20 | @property (nonatomic, weak) id delegate; 21 | 22 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate andPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape; 23 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate; 24 | + (id)nicoCommentManagerWithComments:(NSArray*)comments delegate:(id)delegate; 25 | 26 | - (void)setupPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape; 27 | - (void)start; 28 | - (void)stop; 29 | 30 | - (void)deleteAllCommentLayer; 31 | - (void)startCommentLayer; 32 | - (void)stopCommentLayer; 33 | - (void)seekCommentLayer:(BOOL)pause; 34 | @end 35 | -------------------------------------------------------------------------------- /DanmakuMaster/DanmakuLib/RiverRunCommentManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentManager.m 3 | // 4 | // 5 | // Created by CrazyPeter on 2014/04/21. 6 | // 7 | // 8 | 9 | #import "RiverRunCommentManager.h" 10 | #import "CommentTextLayer.h" 11 | #include 12 | 13 | CGFloat const COMMENT_DURATION = 5.f; 14 | CGFloat const COMMENT_TOP_OR_BOTTOM_DURATION = 3.f; 15 | 16 | @interface RiverRunCommentManager () { 17 | UIView *_view; 18 | CGSize _screenSize, _videoSize; 19 | NSTimer *_commentTimer; 20 | NSInteger _currentIndex; 21 | CGFloat _moviePosition; 22 | BOOL _isLandscape; 23 | NSMutableArray *_showCommentItems; 24 | CGFloat _totalsize; 25 | } 26 | - (CGSize)_sizeInOrientation; 27 | 28 | - (void)_commentTimerFired:(NSTimer*)timer; 29 | - (BOOL)_createCommentLayer:(NSDictionary*)comment setPosition:(BOOL)setPosition pause:(BOOL)pause andDuration:(NSTimeInterval)duration_form_data; 30 | - (void)_addShowCommentItem:(NSDictionary*)commentItem; 31 | - (BOOL)_checkCollisionHeight:(CGRect)rect targetRect:(CGRect)targetRect; 32 | - (BOOL)_checkCollisionWidth:(CGRect)rect targetRect:(CGRect)targetRect; 33 | - (void)_deleteCommentLayer; 34 | @end 35 | 36 | @implementation RiverRunCommentManager 37 | - (id)init { 38 | self = [super init]; 39 | if (self) { 40 | _showCommentItems = [NSMutableArray array]; 41 | _currentIndex = 0; 42 | _isLandscape = NO; 43 | } 44 | return self; 45 | } 46 | 47 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate andPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape{ 48 | { 49 | self = [self init]; 50 | if (self) { 51 | _comments = comments; 52 | _delegate = delegate; 53 | } 54 | [self setupPresentView:view videoSize:videoSize screenSize:screenSize isLandscape:isLandscape]; 55 | return self; 56 | } 57 | } 58 | 59 | - (id)initWithComments:(NSArray*)comments delegate:(id)delegate { 60 | self = [self init]; 61 | if (self) { 62 | _comments = comments; 63 | _delegate = delegate; 64 | } 65 | 66 | return self; 67 | } 68 | 69 | + (id)nicoCommentManagerWithComments:(NSArray*)comments delegate:(id)delegate { 70 | return [[RiverRunCommentManager alloc] initWithComments:comments delegate:delegate]; 71 | } 72 | 73 | - (void)dealloc { 74 | NSLog(@"NicoCommentManager dealloc"); 75 | } 76 | 77 | - (CGSize)_sizeInOrientation { 78 | UIApplication *application = [UIApplication sharedApplication]; 79 | UIInterfaceOrientation orientation = application.statusBarOrientation; 80 | CGSize size = [UIScreen mainScreen].bounds.size; 81 | 82 | if (UIInterfaceOrientationIsLandscape(orientation)) { 83 | size = CGSizeMake(size.height, size.width); 84 | } 85 | 86 | return size; 87 | } 88 | 89 | - (void)setupPresentView:(UIView*)view videoSize:(CGSize)videoSize screenSize:(CGSize)screenSize isLandscape:(BOOL)isLandscape { 90 | _view = view; 91 | _videoSize = videoSize; 92 | _screenSize = screenSize; 93 | _isLandscape = isLandscape; 94 | 95 | if (isnan(_screenSize.width) || isnan(_screenSize.height)) { 96 | _screenSize = [self _sizeInOrientation]; 97 | } 98 | } 99 | 100 | - (void)start { 101 | if (!_commentTimer) { 102 | _commentTimer = [NSTimer timerWithTimeInterval:0.5f 103 | target:self selector:@selector(_commentTimerFired:) 104 | userInfo:nil repeats:YES]; 105 | [[NSRunLoop currentRunLoop] addTimer:_commentTimer forMode:NSRunLoopCommonModes]; 106 | } 107 | } 108 | 109 | - (void)stop { 110 | [_commentTimer invalidate]; 111 | _commentTimer = nil; 112 | 113 | [self _deleteCommentLayer]; 114 | } 115 | 116 | - (void)_commentTimerFired:(NSTimer*)timer { 117 | if (_delegate) { 118 | _moviePosition = [_delegate willShowComments:NO]; 119 | } 120 | if (_moviePosition == -1) return; 121 | 122 | [CATransaction begin]; 123 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 124 | 125 | [self _deleteCommentLayer]; 126 | 127 | CGFloat currentTime = floor(_moviePosition * 1000.f); 128 | NSInteger max = [_comments count]; 129 | 130 | for (NSInteger i = _currentIndex; i < max; i++) { 131 | NSDictionary *comment = _comments[i]; 132 | 133 | if (currentTime >= [comment[@"vpos"] floatValue]) { 134 | [self _createCommentLayer:comment setPosition:NO pause:NO andDuration: [comment[@"duration"] floatValue]]; 135 | 136 | if ((i + 1) == max) { 137 | _currentIndex = i + 1; 138 | } 139 | } 140 | else { 141 | _currentIndex = i; 142 | break; 143 | } 144 | } 145 | 146 | [CATransaction commit]; 147 | } 148 | 149 | - (BOOL)_createCommentLayer:(NSDictionary*)comment setPosition:(BOOL)setPosition pause:(BOOL)pause andDuration:(NSTimeInterval)duration_form_data{ 150 | CGPoint commentOffset; 151 | 152 | CGFloat px = _screenSize.width; 153 | CGFloat py = 0, ty = 0; 154 | CGFloat videoTop = floor((_view.bounds.size.height / 2) - (_screenSize.height / 2)); 155 | CGFloat videoBottom = floor(videoTop + _screenSize.height); 156 | BOOL isDanmaku = NO; 157 | NSTimeInterval duration; 158 | 159 | py = videoTop; 160 | 161 | CommentTextLayer *commentTextLayer = [CommentTextLayer 162 | layerWithCommentInfo:comment 163 | screenSize:_screenSize 164 | isLandscape:_isLandscape]; 165 | 166 | 167 | NSInteger commentPosition = [comment[@"position"] intValue]; 168 | if (commentPosition == COMMENT_POSITION_NORMAL) { 169 | if (duration_form_data==0) { 170 | duration = COMMENT_DURATION; 171 | } 172 | else{ 173 | duration = duration_form_data; 174 | } 175 | } 176 | else { 177 | if (duration_form_data==0) { 178 | duration = COMMENT_DURATION; 179 | } 180 | else{ 181 | duration = duration_form_data; 182 | } 183 | px = 0.f; 184 | if (commentPosition == COMMENT_POSITION_BOTTOM) { 185 | py = videoBottom - commentTextLayer.frame.size.height; 186 | } 187 | } 188 | commentOffset = CGPointMake(px, py); 189 | 190 | 191 | @synchronized(_showCommentItems) { 192 | CGRect rect, targetRect; 193 | rect.size = commentTextLayer.frame.size; 194 | 195 | for (NSDictionary *item in _showCommentItems) { 196 | rect.origin = commentOffset; 197 | 198 | CommentTextLayer *showCommentTextLayer = item[@"commentTextLayer"]; 199 | CALayer *layer = showCommentTextLayer.presentationLayer; 200 | if (layer) { 201 | targetRect.origin = layer.position; 202 | } 203 | else { 204 | targetRect.origin = showCommentTextLayer.position; 205 | } 206 | targetRect.size = showCommentTextLayer.frame.size; 207 | 208 | if (commentPosition == COMMENT_POSITION_TOP) { 209 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_TOP) continue; 210 | 211 | if ([self _checkCollisionHeight:rect targetRect:targetRect]) { 212 | ty = CGRectGetHeight(targetRect); 213 | commentOffset = CGPointMake(px, targetRect.origin.y + ty); 214 | } 215 | 216 | if ((commentOffset.y + ty) > videoBottom) { 217 | isDanmaku = YES; 218 | break; 219 | } 220 | } 221 | else if (commentPosition == COMMENT_POSITION_BOTTOM) { 222 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_BOTTOM) continue; 223 | 224 | if ([self _checkCollisionHeight:rect targetRect:targetRect]) { 225 | ty = CGRectGetHeight(targetRect); 226 | commentOffset = CGPointMake(px, targetRect.origin.y - ty); 227 | } 228 | 229 | if ((commentOffset.y - ty) < videoTop) { 230 | isDanmaku = YES; 231 | break; 232 | } 233 | } 234 | else if (commentPosition == COMMENT_POSITION_NORMAL) { 235 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_NORMAL) continue; 236 | 237 | if ([self _checkCollisionHeight:rect targetRect:targetRect]) { 238 | if ([self _checkCollisionWidth:rect targetRect:targetRect]) { 239 | ty = CGRectGetHeight(targetRect); 240 | commentOffset = CGPointMake(px, targetRect.origin.y + ty); 241 | } 242 | if ((commentOffset.y + ty) > videoBottom) { 243 | isDanmaku = YES; 244 | break; 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | if (isDanmaku) { 252 | NSInteger y = arc4random_uniform(_screenSize.height - CGRectGetHeight(commentTextLayer.frame)); 253 | commentOffset = CGPointMake(px, py + y); 254 | } 255 | 256 | commentTextLayer.position = commentOffset; 257 | [self _addShowCommentItem:@{ 258 | @"commentTextLayer": commentTextLayer, 259 | @"duration": @(duration), 260 | }]; 261 | 262 | [_view.layer addSublayer:commentTextLayer]; 263 | 264 | if (commentPosition == COMMENT_POSITION_NORMAL) { 265 | 266 | CGFloat animateDuration; 267 | 268 | if (duration_form_data == 0) { 269 | animateDuration = COMMENT_DURATION; 270 | } 271 | else{ 272 | animateDuration = duration_form_data; 273 | } 274 | 275 | //正常弹幕 276 | if (setPosition) { 277 | CGFloat currentTime = _moviePosition * 1000.f; 278 | CGFloat diff = (currentTime - commentTextLayer.vpos) / 1000.f; 279 | CGFloat perWidth = (_screenSize.width + commentTextLayer.frame.size.width) / animateDuration; 280 | commentOffset.x = (_screenSize.width) - (perWidth * diff); 281 | animateDuration = animateDuration - diff; 282 | } 283 | 284 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; 285 | animation.repeatCount = 0; 286 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 287 | animation.duration = animateDuration; 288 | animation.removedOnCompletion = NO; 289 | animation.fillMode = kCAFillModeForwards; 290 | 291 | animation.fromValue = [NSValue valueWithCGPoint:commentOffset]; 292 | commentOffset.x = -commentTextLayer.frame.size.width; 293 | animation.toValue = [NSValue valueWithCGPoint:commentOffset]; 294 | 295 | [commentTextLayer addAnimation:animation forKey:@"Comment"]; 296 | 297 | if (pause) { 298 | [self stopCommentLayer]; 299 | } 300 | } 301 | 302 | return YES; 303 | } 304 | 305 | - (void)_addShowCommentItem:(NSDictionary*)commentItem { 306 | CommentTextLayer *commentTextLayer = commentItem[@"commentTextLayer"]; 307 | 308 | @synchronized(_showCommentItems) { 309 | if (commentTextLayer.commentPosition == COMMENT_POSITION_NORMAL) { 310 | NSInteger max = [_showCommentItems count]; 311 | 312 | for (NSInteger i = (max - 1); i >= 0; i--) { 313 | CommentTextLayer *showCommentTextLayer = _showCommentItems[i][@"commentTextLayer"]; 314 | 315 | if (showCommentTextLayer.commentPosition != COMMENT_POSITION_NORMAL) continue; 316 | 317 | if (commentTextLayer.position.y >= showCommentTextLayer.position.y) { 318 | [_showCommentItems insertObject:commentItem atIndex:(i + 1)]; 319 | return; 320 | } 321 | } 322 | } 323 | 324 | [_showCommentItems addObject:commentItem]; 325 | } 326 | } 327 | 328 | - (BOOL)_checkCollisionHeight:(CGRect)rect targetRect:(CGRect)targetRect { 329 | CGFloat commentTop1 = rect.origin.y; 330 | CGFloat commentBottom1 = rect.origin.y + rect.size.height; 331 | CGFloat commentTop2 = targetRect.origin.y; 332 | CGFloat commentBottom2 = targetRect.origin.y + targetRect.size.height; 333 | 334 | BOOL b1 = (commentTop1 >= commentTop2 && commentTop1 <= commentBottom2) || 335 | (commentBottom1 >= commentTop2 && commentBottom1 <= commentBottom2); 336 | BOOL b2 = (commentTop2 >= commentTop1 && commentTop2 <= commentBottom1) || 337 | (commentBottom2 >= commentTop1 && commentBottom2 <= commentBottom1); 338 | 339 | return (b1 || b2); 340 | } 341 | 342 | - (BOOL)_checkCollisionWidth:(CGRect)rect targetRect:(CGRect)targetRect { 343 | CGSize screenSize = _screenSize; 344 | CGFloat perWidth = (screenSize.width + rect.size.width) / COMMENT_DURATION; 345 | CGFloat perWidth2 = (screenSize.width + targetRect.size.width) / COMMENT_DURATION; 346 | 347 | CGFloat targetFinishPosition, finishPosition; 348 | CGFloat add = COMMENT_DURATION / 3; 349 | CGFloat max = add * 3; 350 | 351 | for (CGFloat i = 0; i <= max; i += add) { 352 | targetFinishPosition = targetRect.origin.x - (perWidth2 * i) + targetRect.size.width; 353 | 354 | if (targetFinishPosition <= -targetRect.size.width) break; 355 | 356 | finishPosition = screenSize.width - (perWidth * i); 357 | 358 | if (targetFinishPosition > finishPosition) { 359 | return YES; 360 | } 361 | } 362 | 363 | return NO; 364 | } 365 | 366 | - (void)_deleteCommentLayer { 367 | CGFloat currentTime = floor(_moviePosition * 1000.f); 368 | NSMutableArray *deleteItems = [NSMutableArray array]; 369 | 370 | @synchronized(_showCommentItems) { 371 | for (NSDictionary *item in _showCommentItems) { 372 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 373 | CALayer *layer = commentTextLayer.presentationLayer; 374 | if (commentTextLayer.commentPosition == COMMENT_POSITION_NORMAL) { 375 | if (layer.frame.origin.x <= -commentTextLayer.frame.size.width) { 376 | [commentTextLayer removeFromSuperlayer]; 377 | [deleteItems addObject:item]; 378 | } 379 | } 380 | else { 381 | if (currentTime > (CGFloat)(commentTextLayer.vpos + [item[@"duration"] doubleValue] * 1000.0)) { 382 | NSLog(@"commentTextLayer.vpos - %ld,duration - %lf",(long)commentTextLayer.vpos,[item[@"duration"] doubleValue] * 1000.0); 383 | [commentTextLayer removeFromSuperlayer]; 384 | [deleteItems addObject:item]; 385 | } 386 | } 387 | } 388 | [_showCommentItems removeObjectsInArray:deleteItems]; 389 | } 390 | 391 | [deleteItems removeAllObjects]; 392 | deleteItems = nil; 393 | } 394 | 395 | - (void)deleteAllCommentLayer { 396 | @synchronized(_showCommentItems) { 397 | for (NSDictionary *item in _showCommentItems) { 398 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 399 | [commentTextLayer removeFromSuperlayer]; 400 | } 401 | [_showCommentItems removeAllObjects]; 402 | } 403 | } 404 | 405 | - (void)startCommentLayer { 406 | [CATransaction begin]; 407 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 408 | 409 | @synchronized(_showCommentItems) { 410 | for (NSDictionary *item in _showCommentItems) { 411 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 412 | [commentTextLayer pauseAnimation:NO]; 413 | } 414 | } 415 | 416 | [CATransaction commit]; 417 | } 418 | 419 | - (void)stopCommentLayer { 420 | [CATransaction begin]; 421 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 422 | 423 | @synchronized(_showCommentItems) { 424 | for (NSDictionary *item in _showCommentItems) { 425 | CommentTextLayer *commentTextLayer = item[@"commentTextLayer"]; 426 | [commentTextLayer pauseAnimation:YES]; 427 | } 428 | } 429 | 430 | [CATransaction commit]; 431 | } 432 | 433 | - (void)seekCommentLayer:(BOOL)pause { 434 | if (_delegate) { 435 | _moviePosition = [_delegate willShowComments:YES]; 436 | } 437 | if (_moviePosition == -1) return; 438 | [self deleteAllCommentLayer]; 439 | CGFloat currentTime = floor(_moviePosition * 1000.f); 440 | CGFloat normalCommentTime = currentTime - (COMMENT_DURATION * 1000.f); 441 | CGFloat topBottomCommentTime = currentTime - (COMMENT_TOP_OR_BOTTOM_DURATION * 1000.f); 442 | NSInteger max = [_comments count]; 443 | NSInteger i; 444 | 445 | [CATransaction begin]; 446 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 447 | 448 | _currentIndex = 0; 449 | 450 | for (i = 0; i < max; i++) { 451 | NSDictionary *comment = _comments[i]; 452 | 453 | if (currentTime < [comment[@"vpos"] floatValue]) { 454 | _currentIndex = i; 455 | break; 456 | } 457 | 458 | if (normalCommentTime < [comment[@"vpos"] floatValue] && [comment[@"position"] intValue] == COMMENT_POSITION_NORMAL) { 459 | [self _createCommentLayer:comment setPosition:YES pause:pause andDuration:[comment[@"duration"] floatValue] ]; 460 | } 461 | else if (topBottomCommentTime < [comment[@"vpos"] floatValue] && [comment[@"position"] intValue] != COMMENT_POSITION_NORMAL) { 462 | [self _createCommentLayer:comment setPosition:NO pause:pause andDuration:[comment[@"duration"] floatValue]]; 463 | } 464 | } 465 | 466 | _currentIndex = i; 467 | 468 | [CATransaction commit]; 469 | } 470 | @end 471 | -------------------------------------------------------------------------------- /DanmakuMaster/DanmakuLib/RiverRunCommentUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentUtil.h 3 | // SmilePlayer2 4 | // 5 | // Created by CrazyPeter on 2014/04/05. 6 | // 7 | // 8 | 9 | #import 10 | #import "CommentTextLayer.h" 11 | 12 | @interface RiverRunCommentUtil : NSObject 13 | + (NSInteger)commentPosition:(NSString*)position; 14 | + (NSInteger)commentSize:(NSString*)fontSize; 15 | + (NSString*)getRandomFontSize; 16 | + (NSString*)getRandomPosition; 17 | @end 18 | -------------------------------------------------------------------------------- /DanmakuMaster/DanmakuLib/RiverRunCommentUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // RiverRunCommentUtil.m 3 | // 4 | // 5 | // Created by CrazyPeter on 2014/04/05. 6 | // 7 | // 8 | 9 | #import "RiverRunCommentUtil.h" 10 | 11 | @implementation RiverRunCommentUtil 12 | + (NSInteger)commentPosition:(NSString*)position{ 13 | if ([position isEqualToString:@"top"]) { 14 | return COMMENT_POSITION_TOP; 15 | } 16 | else if ([position isEqualToString:@"bottom"]) { 17 | return COMMENT_POSITION_BOTTOM; 18 | } 19 | return COMMENT_POSITION_NORMAL; 20 | } 21 | 22 | + (NSInteger)commentSize:(NSString*)fontSize{ 23 | if ([fontSize isEqualToString:@"big"]) { 24 | return COMMENT_SIZE_BIG; 25 | } 26 | else if ([fontSize isEqualToString:@"small"]) { 27 | return COMMENT_SIZE_SMALL; 28 | } 29 | return COMMENT_SIZE_NORMAL; 30 | } 31 | 32 | /* 33 | 颜色数值转换:#ababab 34 | */ 35 | +(UIColor *)changeColor:(NSString *)str{ 36 | unsigned int red,green,blue; 37 | NSString * str1 = [str substringWithRange:NSMakeRange(1, 2)]; 38 | NSString * str2 = [str substringWithRange:NSMakeRange(3, 2)]; 39 | NSString * str3 = [str substringWithRange:NSMakeRange(5, 2)]; 40 | 41 | NSScanner * canner = [NSScanner scannerWithString:str1]; 42 | [canner scanHexInt:&red]; 43 | 44 | canner = [NSScanner scannerWithString:str2]; 45 | [canner scanHexInt:&green]; 46 | 47 | canner = [NSScanner scannerWithString:str3]; 48 | [canner scanHexInt:&blue]; 49 | UIColor * color = [UIColor colorWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:1.0]; 50 | return color; 51 | } 52 | 53 | + (NSString*)getRandomPosition 54 | { 55 | int i = arc4random_uniform(3); 56 | if (i == 1) { 57 | return @"top"; 58 | } 59 | else if(i == 2){ 60 | return @"bottom"; 61 | } 62 | else 63 | return @""; 64 | } 65 | 66 | + (NSString*)getRandomFontSize 67 | { 68 | int i = arc4random_uniform(3); 69 | if (i == 1) { 70 | return @"big"; 71 | } 72 | else if(i == 2){ 73 | return @"small"; 74 | } 75 | else 76 | return @""; 77 | } 78 | @end 79 | -------------------------------------------------------------------------------- /DanmakuMaster/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /DanmakuMaster/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /DanmakuMaster/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // DanmakuMaster 4 | // 5 | // Created by Peter Kong on 15-4-8. 6 | // Copyright (c) 2015年 CrazyPeter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /DanmakuMaster/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // DanmakuMaster 4 | // 5 | // Created by Peter Kong on 15-4-8. 6 | // Copyright (c) 2015年 CrazyPeter. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "RiverRunCommentUtil.h" 11 | #import "RiverRunCommentManager.h" 12 | 13 | @interface ViewController () 14 | @property (strong, nonatomic) NSMutableArray *commentArray; 15 | @property (strong, nonatomic) NSTimer *commentTimer; 16 | @property (strong, nonatomic) RiverRunCommentManager *manager; 17 | @property CGFloat commentNUM; 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | // Do any additional setup after loading the view, typically from a nib. 25 | _commentTimer = [NSTimer timerWithTimeInterval:0.1f 26 | target:self selector:@selector(commentTimerFired) 27 | userInfo:nil repeats:YES]; 28 | _commentNUM = 0; 29 | [[NSRunLoop currentRunLoop] addTimer:_commentTimer forMode:NSRunLoopCommonModes]; 30 | self.view.backgroundColor = [UIColor colorWithRed:0.23f green:0.35f blue:0.62f alpha:1.00f]; 31 | _commentArray = [NSMutableArray arrayWithArray:[self createVideoComment]]; 32 | _manager = [[RiverRunCommentManager alloc]initWithComments:_commentArray delegate:self andPresentView:self.view videoSize:self.view.bounds.size screenSize:self.view.bounds.size isLandscape:[[UIApplication sharedApplication] statusBarOrientation]]; 33 | } 34 | 35 | - (CGFloat)willShowComments:(BOOL)seek { 36 | return _commentNUM; 37 | } 38 | 39 | -(void)commentTimerFired 40 | { 41 | _commentNUM+=0.1; 42 | } 43 | 44 | - (IBAction)clickBegin:(id)sender { 45 | [_manager start]; 46 | } 47 | 48 | - (IBAction)clickStop:(id)sender { 49 | [_manager stop]; 50 | [_manager deleteAllCommentLayer]; 51 | [self.commentTimer invalidate]; 52 | } 53 | 54 | - (NSArray*)createVideoComment { 55 | NSInteger videoDuration = 30000; 56 | NSInteger commentNum = 500; 57 | NSMutableArray *videoComments = [NSMutableArray array]; 58 | 59 | for (NSInteger i = 0; i < commentNum; i++) { 60 | NSInteger vpos = arc4random_uniform((unsigned int)videoDuration); 61 | 62 | NSDictionary *commentInfo = @{ 63 | @"vpos": @(vpos), 64 | @"body": @"奔跑吧,弹幕!!!", 65 | @"position": @([RiverRunCommentUtil commentPosition:[RiverRunCommentUtil getRandomPosition]]), 66 | @"fontSize": @([RiverRunCommentUtil commentSize:[RiverRunCommentUtil getRandomFontSize]]), 67 | @"color": @"#ffffff", 68 | @"duration":@(3.f), 69 | }; 70 | [videoComments addObject:commentInfo]; 71 | } 72 | 73 | return [videoComments sortedArrayUsingComparator:^NSComparisonResult( 74 | NSDictionary *item1, NSDictionary *item2) { 75 | NSLog(@"item - %@",item1); 76 | 77 | NSInteger vpos1 = [item1[@"vpos"] intValue]; 78 | NSInteger vpos2 = [item2[@"vpos"] intValue]; 79 | return vpos1 > vpos2; 80 | }]; 81 | } 82 | 83 | 84 | - (void)didReceiveMemoryWarning { 85 | [super didReceiveMemoryWarning]; 86 | // Dispose of any resources that can be recreated. 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /DanmakuMaster/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DanmakuMaster 4 | // 5 | // Created by Peter Kong on 15-4-8. 6 | // Copyright (c) 2015年 CrazyPeter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###DanmukuMaster-iOS 2 | 3 | ####预览效果 4 | ![](http://g.recordit.co/CsA7KUkQJH.gif) 5 | 6 | ####简介: 7 | >弹幕引擎,使用coretext编写。比一般的UILabel要节省内存空间。经测试10秒内显示1000条弹幕可行。 8 | 9 | ####Feature: 10 | >1、可以挑选动画样式,分别是滚动、顶部停留、底部停留。 11 | 2、停留动画的弹幕计算了显示位置,不会叠加显示。 12 | >3、简单易用,内容解耦,可修改性强。 13 | 14 | 15 | ####使用方法 16 | ```javascript 17 | 1.首先定义一个全局变量: 18 | RiverRunCommentManager *_manger; 19 | 2.初始化弹幕 20 | _manger = [[RiverRunCommentManager alloc]initWithComments:_commentArray delegate:self andPresentView:self.view videoSize:self.view.bounds.size screenSize:self.view.bounds.size isLandscape:UIInterfaceOrientationIsLandscape(self.interfaceOrientation)]; 21 | 3.写好回调方法,返回值为当前播放时间,以s为单位 22 | - (CGFloat)willShowComments:(BOOL)seek { 23 | return _commentNUM; 24 | } 25 | 4.开启屏幕翻转时适应 26 | - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { 27 | [_manger setupPresentView:self.view 28 | videoSize:self.view.bounds.size 29 | screenSize: self.view.bounds.size 30 | isLandscape:UIInterfaceOrientationIsLandscape(self.interfaceOrientation)]; 31 | } 32 | 5.开启弹幕 33 | [_manger start]; 34 | 6.暂时关闭弹幕 35 | [_manger stop]; 36 | [_manger deleteAllCommentLayer]; 37 | 7.重新开启 38 | _manger.comments = _commentArray;(更新弹幕) 39 | [_manger start]; 40 | 8.退出时调用的方法 41 | [_manger stop]; 42 | _manger = nil; 43 | ``` 44 | 45 | ####弹幕的相关参数 46 | ```c++ 47 | /* 48 | 参数的含义: 49 | vpos:是开始出现的时间,以毫秒为单位,和服务器返回数据格式相反 数据类型int 50 | body:弹幕内容 数据类型string 51 | position:固定string字符,top或者bottom是停留在屏幕上下两端的,其他是正常飘过的弹幕 52 | “top” 53 | “bottom” 54 | fontSize:字体大小,数据类型int 55 | color:颜色,格式:#ffffff, 数据类型string 56 | duration:动画时间 数据类型float 57 | “”(其他) 58 | */ 59 | 举例: NSDictionary *commentInfo = @{ 60 | @"vpos": @(vpos), 61 | @"body": @“Hello World!!!", 62 | @"position": @([RiverRunCommentUtil commentPosition:[self getPosition]]), 63 | @"fontSize": @([RiverRunCommentUtil commentSize:[self getFontSize]]), 64 | @"color": @"#ffffff", 65 | @"duration":@(3.f), 66 | }; 67 | ``` 68 | 69 | ####已知bug 70 | >存在部分的内存泄漏问题 71 | 72 | 73 | #####其他: 74 | >定义的随机数相关 75 | 获得位置的随机数可用[RiverRunCommentUtil getPosition], 76 | 如 @"position": @([RiverRunCommentUtil commentPosition:[RiverRunCommentUtil getPosition]]) 77 | 获得fontsize随机数可用[RiverRunCommentUtil getFontSize] 78 | >如:@"fontSize": @([RiverRunCommentUtil commentSize:[RiverRunCommentUtil getFontSize]]) 79 | 80 | 81 | 82 | 83 | --------------------------------------------------------------------------------