├── LNDanmakuMaster ├── LNDanmakuMaster │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Main │ │ ├── DemoItemObj.m │ │ ├── MainViewController.h │ │ ├── DemoItemCollectionViewCell.h │ │ ├── DemoItemObj.h │ │ ├── DemoItemCollectionViewCell.m │ │ └── MainViewController.m │ ├── AppDelegate.h │ ├── Demo │ │ ├── VideoDanmakuDemo │ │ │ ├── VideoDanmakuPlayer │ │ │ │ ├── VideoDanmakuPlayer.m │ │ │ │ ├── Model │ │ │ │ │ ├── VideoDanmakuModel.m │ │ │ │ │ └── VideoDanmakuModel.h │ │ │ │ ├── VideoDanmakuPlayer.h │ │ │ │ ├── DanmakuFactory │ │ │ │ │ ├── VideoDanmakuStyleList.m │ │ │ │ │ ├── VideoDanmakuFactory.m │ │ │ │ │ ├── VideoDanmakuStyleList.h │ │ │ │ │ └── VideoDanmakuFactory.h │ │ │ │ ├── Dispatcher │ │ │ │ │ ├── VideoDanmakuDispatcher.m │ │ │ │ │ └── VideoDanmakuDispatcher.h │ │ │ │ └── Server │ │ │ │ │ ├── VideoDanmakuServer.h │ │ │ │ │ └── VideoDanmakuServer.m │ │ │ ├── VideoDanmakuDemoViewController.h │ │ │ ├── VideoPlayer │ │ │ │ ├── VideoPlayer.h │ │ │ │ └── VideoPlayer.m │ │ │ └── VideoDanmakuDemoViewController.m │ │ ├── GestureDemo │ │ │ ├── GestureDemoViewController.h │ │ │ └── GestureDemoViewController.m │ │ ├── TrackGroupDemo │ │ │ ├── TrackGroupDemoViewController.h │ │ │ └── TrackGroupDemoViewController.m │ │ ├── DispatcherDemo │ │ │ ├── DispatcherDemoViewController.h │ │ │ └── DispatcherDemoViewController.m │ │ └── TracksDemo │ │ │ ├── TracksDemoViewController.h │ │ │ └── TracksDemoViewController.m │ ├── LNDanmakuMaster │ │ ├── Attributes │ │ │ ├── Category │ │ │ │ ├── LNDanmakuAttributesHolder.m │ │ │ │ ├── LNDanmakuAttributesHolder.h │ │ │ │ ├── UIView+LNDanmakuAttributesHolder.h │ │ │ │ ├── CALayer+LNDanmakuAttributesHolder.h │ │ │ │ ├── UIView+LNDanmakuAttributesHolder.m │ │ │ │ └── CALayer+LNDanmakuAttributesHolder.m │ │ │ ├── LNDanmakuAttributes.m │ │ │ ├── LNDanmakuAttributes.h │ │ │ ├── LNDanmakuAbstractAttributes.h │ │ │ └── LNDanmakuAbstractAttributes.m │ │ ├── UIElements │ │ │ ├── Layer │ │ │ │ ├── LNDanmakuTextLayer.m │ │ │ │ ├── LNDanmakuTextLayer.h │ │ │ │ ├── LNDanmakuLayer.h │ │ │ │ └── LNDanmakuLayer.m │ │ │ └── View │ │ │ │ ├── LNDanmakuView.h │ │ │ │ └── LNDanmakuView.m │ │ ├── Track │ │ │ ├── LNDanmakuAbstractTrack.h │ │ │ ├── BaseTrackController │ │ │ │ ├── LNDanmakuBaseStableTrackController.h │ │ │ │ ├── LNDanmakuBaseFreeTrackController.h │ │ │ │ ├── LNDanmakuBaseMoveTrackController.h │ │ │ │ ├── LNDanmakuBaseStableTrackController.m │ │ │ │ ├── LNDanmakuBaseFreeTrackController.m │ │ │ │ └── LNDanmakuBaseMoveTrackController.m │ │ │ ├── TrackController │ │ │ │ ├── Stable │ │ │ │ │ ├── LNDanmakuStableTrack.h │ │ │ │ │ ├── LNDanmakuStableTrackController.h │ │ │ │ │ ├── LNDanmakuStableTrack.m │ │ │ │ │ └── LNDanmakuStableTrackController.m │ │ │ │ ├── Circle │ │ │ │ │ ├── LNDanmakuCircleTrack.h │ │ │ │ │ ├── LNDanmakuCircleTrackController.h │ │ │ │ │ ├── LNDanmakuCircleTrackController.m │ │ │ │ │ └── LNDanmakuCircleTrack.m │ │ │ │ ├── Christina │ │ │ │ │ ├── LNDanmakuChristinaTrack.h │ │ │ │ │ ├── LNDanmakuChristinaTrackController.h │ │ │ │ │ ├── LNDanmakuChristinaTrackController.m │ │ │ │ │ └── LNDanmakuChristinaTrack.m │ │ │ │ ├── Horizontal │ │ │ │ │ ├── LNDanmakuHorizontalMoveTrack.h │ │ │ │ │ ├── LNDanmakuHorizontalMoveTrackController.h │ │ │ │ │ ├── LNDanmakuHorizontalMoveTrack.m │ │ │ │ │ └── LNDanmakuHorizontalMoveTrackController.m │ │ │ │ ├── Pop │ │ │ │ │ ├── LNDanmakuPopTrackController.h │ │ │ │ │ ├── LNDanmakuPopTrackController.m │ │ │ │ │ ├── LNDanmakuPopTrack.h │ │ │ │ │ └── LNDanmakuPopTrack.m │ │ │ │ └── Sin │ │ │ │ │ ├── LNDanmakuSinTrackController.h │ │ │ │ │ ├── LNDanmakuSinTrack.h │ │ │ │ │ ├── LNDanmakuSinTrackController.m │ │ │ │ │ └── LNDanmakuSinTrack.m │ │ │ ├── LNDanmakuAbstractTrack.m │ │ │ ├── LNDanmakuAbstractTrackController.m │ │ │ └── LNDanmakuAbstractTrackController.h │ │ ├── Pool │ │ │ ├── NSObject+LNDanmakuPool.h │ │ │ ├── LNDanmakuPurePool.h │ │ │ ├── NSObject+LNDanmakuPool.m │ │ │ ├── LNDanmakuPool.h │ │ │ ├── LNDanmakuPool.m │ │ │ └── LNDanmakuPurePool.m │ │ ├── Clock │ │ │ ├── LNDanmakuClockProxy.h │ │ │ ├── LNDanmakuClock.h │ │ │ ├── LNDanmakuClockProxy.m │ │ │ └── LNDanmakuClock.m │ │ ├── LNDanmakuMaster.h │ │ ├── Dispatcher │ │ │ ├── LNDanmakuDispatcher.h │ │ │ ├── LNDanmakuDispatchQueue.h │ │ │ ├── LNDanmakuAbstractDispatcher.m │ │ │ ├── LNDanmakuAbstractDispatcher.h │ │ │ ├── LNDanmakuDispatchQueue.m │ │ │ └── LNDanmakuDispatcher.m │ │ ├── TrackGroup │ │ │ ├── LNDanmakuTrackGroup.h │ │ │ └── LNDanmakuTrackGroup.m │ │ ├── ContainerView │ │ │ ├── LNDanmakuContainerView.h │ │ │ └── LNDanmakuContainerView.m │ │ └── Player │ │ │ └── LNDanmakuPlayer.h │ ├── main.m │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── LNDanmakuMaster.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── LNDanmakuMasterTests │ ├── Info.plist │ └── LNDanmakuMasterTests.m └── LNDanmakuMasterUITests │ ├── Info.plist │ └── LNDanmakuMasterUITests.m ├── LICENSE ├── .gitignore ├── LNDanmakuMaster.podspec └── README.md /LNDanmakuMaster/LNDanmakuMaster/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Main/DemoItemObj.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoItemObj.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "DemoItemObj.h" 10 | 11 | @implementation DemoItemObj 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (nonatomic, strong) UIWindow *window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/VideoDanmakuPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuPlayer.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoDanmakuPlayer.h" 10 | 11 | @implementation VideoDanmakuPlayer 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/Model/VideoDanmakuModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuModel.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoDanmakuModel.h" 10 | 11 | @implementation VideoDanmakuModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/Category/LNDanmakuAttributesHolder.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAttributesHolder.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAttributesHolder.h" 10 | 11 | @implementation LNDanmakuAttributesHolder 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Main/MainViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface MainViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/LNDanmakuAttributes.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAttributes.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAttributes.h" 10 | 11 | @interface LNDanmakuAttributes () 12 | 13 | @end 14 | 15 | @implementation LNDanmakuAttributes 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/VideoDanmakuPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuPlayer.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VideoDanmakuPlayer : NSObject 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/UIElements/Layer/LNDanmakuTextLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuTextLayer.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuTextLayer.h" 10 | 11 | @implementation LNDanmakuTextLayer 12 | 13 | - (id)actionForKey:(NSString *)event 14 | { 15 | return nil; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/DanmakuFactory/VideoDanmakuStyleList.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuStyleList.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoDanmakuStyleList.h" 10 | 11 | @interface VideoDanmakuStyleList () 12 | 13 | @end 14 | 15 | @implementation VideoDanmakuStyleList 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/Dispatcher/VideoDanmakuDispatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuDispatcher.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoDanmakuDispatcher.h" 10 | 11 | @interface VideoDanmakuDispatcher () 12 | 13 | @end 14 | 15 | @implementation VideoDanmakuDispatcher 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/Dispatcher/VideoDanmakuDispatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuDispatcher.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VideoDanmakuDispatcher : NSObject 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/UIElements/View/LNDanmakuView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuView.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * Not necessary, sir. O(∩_∩)O 15 | **/ 16 | 17 | @interface LNDanmakuView : UIView 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/LNDanmakuAttributes.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAttributes.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractAttributes.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * Default attributes 15 | **/ 16 | 17 | @interface LNDanmakuAttributes : LNDanmakuAbstractAttributes 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/LNDanmakuAbstractTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractTrack.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface LNDanmakuAbstractTrack : NSObject 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Pool/NSObject+LNDanmakuPool.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+LNDanmakuPool.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSObject (LNDanmakuPool) 14 | 15 | - (void)setDanmakuPoolKey:(NSString *)string; 16 | - (NSString *)danmakuPoolKey; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/BaseTrackController/LNDanmakuBaseStableTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuBaseStableTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrackController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuBaseStableTrackController : LNDanmakuAbstractTrackController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Stable/LNDanmakuStableTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuStableTrack.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrack.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuStableTrack : LNDanmakuAbstractTrack 14 | 15 | @property (nonatomic, assign) CGPoint position; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/UIElements/Layer/LNDanmakuTextLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuTextLayer.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * If user choose other subclass of CALayer, should cancel CAAnimation too. 15 | **/ 16 | 17 | @interface LNDanmakuTextLayer : CALayer 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/LNDanmakuAbstractTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractTrack.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrack.h" 10 | 11 | @implementation LNDanmakuAbstractTrack 12 | 13 | - (void)resetAttributes:(LNDanmakuAbstractAttributes *)attributes 14 | { 15 | 16 | } 17 | 18 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes 19 | { 20 | 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | NSString * appDelegateClassName; 13 | @autoreleasepool { 14 | // Setup code that might create autoreleased objects goes here. 15 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 16 | } 17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 18 | } 19 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/UIElements/View/LNDanmakuView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuView.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuView.h" 10 | 11 | @implementation LNDanmakuView 12 | 13 | /* 14 | // Only override drawRect: if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | - (void)drawRect:(CGRect)rect { 17 | // Drawing code 18 | } 19 | */ 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Circle/LNDanmakuCircleTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuCircleTrack.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrack.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuCircleTrack : LNDanmakuAbstractTrack 14 | 15 | @property (nonatomic, assign) CGPoint center; 16 | @property (nonatomic, assign) CGFloat radius; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Main/DemoItemCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoItemCollectionViewCell.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DemoItemObj.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | extern NSString *const kDemoItemCollectionViewCell; 15 | 16 | @interface DemoItemCollectionViewCell : UICollectionViewCell 17 | 18 | - (void)setItemObj:(DemoItemObj *)item; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/BaseTrackController/LNDanmakuBaseFreeTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuBaseFreeTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrackController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuBaseFreeTrackController : LNDanmakuAbstractTrackController 14 | 15 | @property (nonatomic, assign) NSInteger maxCapacity; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Christina/LNDanmakuChristinaTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuChristinaTrack.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrack.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuChristinaTrack : LNDanmakuAbstractTrack 14 | 15 | @property (nonatomic, assign) CGPoint center; 16 | @property (nonatomic, assign) CGFloat a; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/BaseTrackController/LNDanmakuBaseMoveTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuBaseMoveTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrackController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuBaseMoveTrackController : LNDanmakuAbstractTrackController 14 | 15 | @property (nonatomic, assign) NSTimeInterval spaceTimeInterval; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Horizontal/LNDanmakuHorizontalMoveTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuHorizontalMoveTrack.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrack.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuHorizontalMoveTrack : LNDanmakuAbstractTrack 14 | 15 | @property (nonatomic, assign) CGPoint startPosition; 16 | @property (nonatomic, assign) CGFloat width; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Pop/LNDanmakuPopTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPopTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseFreeTrackController.h" 10 | #import "LNDanmakuPopTrack.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface LNDanmakuPopTrackController : LNDanmakuBaseFreeTrackController 15 | 16 | @property (nonatomic, strong, readonly) LNDanmakuPopTrack *popTrack; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Sin/LNDanmakuSinTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuSinTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseMoveTrackController.h" 10 | #import "LNDanmakuSinTrack.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface LNDanmakuSinTrackController : LNDanmakuBaseMoveTrackController 15 | 16 | @property (nonatomic, strong, readonly) LNDanmakuSinTrack *sinTrack; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/Server/VideoDanmakuServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuServer.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VideoDanmakuServer : NSObject 14 | 15 | - (void)requestDanmakuInfo:(NSString *)videoId 16 | startTime:(NSTimeInterval)startTime 17 | endTime:(NSTimeInterval)endTime; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/Server/VideoDanmakuServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuServer.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoDanmakuServer.h" 10 | 11 | @interface VideoDanmakuServer () 12 | 13 | @end 14 | 15 | @implementation VideoDanmakuServer 16 | 17 | - (void)requestDanmakuInfo:(NSString *)videoId 18 | startTime:(NSTimeInterval)startTime 19 | endTime:(NSTimeInterval)endTime 20 | { 21 | 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Circle/LNDanmakuCircleTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuCircleTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseMoveTrackController.h" 10 | #import "LNDanmakuCircleTrack.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface LNDanmakuCircleTrackController : LNDanmakuBaseMoveTrackController 15 | 16 | @property (nonatomic, strong, readonly) LNDanmakuCircleTrack *circleTrack; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Stable/LNDanmakuStableTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuStableTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseStableTrackController.h" 10 | #import "LNDanmakuStableTrack.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface LNDanmakuStableTrackController : LNDanmakuBaseStableTrackController 15 | 16 | @property (nonatomic, strong, readonly) LNDanmakuStableTrack *stableTrack; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/DanmakuFactory/VideoDanmakuFactory.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuFactory.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoDanmakuFactory.h" 10 | 11 | @interface VideoDanmakuFactory () 12 | 13 | @end 14 | 15 | @implementation VideoDanmakuFactory 16 | 17 | - (LNDanmakuAttributes *)attributesForDanmakuModel:(VideoDanmakuModel *)danmakuModel 18 | { 19 | LNDanmakuAttributes *attributes = [[LNDanmakuAttributes alloc] init]; 20 | return attributes; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Clock/LNDanmakuClockProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuClockProxy.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * Yeap, It's copied from YYProxy. (^~^) 15 | **/ 16 | 17 | @interface LNDanmakuClockProxy : NSProxy 18 | 19 | @property (nullable, nonatomic, weak, readonly) id target; 20 | - (instancetype)initWithTarget:(id)target; 21 | + (instancetype)proxyWithTarget:(id)target; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Christina/LNDanmakuChristinaTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuChristinaTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseMoveTrackController.h" 10 | #import "LNDanmakuChristinaTrack.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface LNDanmakuChristinaTrackController : LNDanmakuBaseMoveTrackController 15 | 16 | @property (nonatomic, strong, readonly) LNDanmakuChristinaTrack *christinaTrack; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/Category/LNDanmakuAttributesHolder.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAttributesHolder.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /* 15 | * Help every CALayer/UIView keep a weak pointer to attributes. 16 | **/ 17 | 18 | @interface LNDanmakuAttributesHolder : NSObject 19 | 20 | @property (nonatomic, weak) LNDanmakuAbstractAttributes *attributes; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Sin/LNDanmakuSinTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuSinTrack.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrack.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LNDanmakuSinTrack : LNDanmakuAbstractTrack 14 | 15 | @property (nonatomic, assign) CGPoint startPosition; 16 | @property (nonatomic, assign) CGFloat width; 17 | 18 | @property (nonatomic, assign) CGFloat A; 19 | 20 | @property (nonatomic, assign) CGFloat T;//percent 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/Category/UIView+LNDanmakuAttributesHolder.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+LNDanmakuAttributesHolder.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface UIView (LNDanmakuAttributesHolder) 15 | 16 | //weak/delegate 17 | - (void)setDanmakuAttributes:(LNDanmakuAbstractAttributes * _Nullable)attributes; 18 | - (LNDanmakuAbstractAttributes * _Nullable)danmakuAttributes; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Horizontal/LNDanmakuHorizontalMoveTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuHorizontalMoveTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseMoveTrackController.h" 10 | #import "LNDanmakuHorizontalMoveTrack.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface LNDanmakuHorizontalMoveTrackController : LNDanmakuBaseMoveTrackController 15 | 16 | @property (nonatomic, strong, readonly) LNDanmakuHorizontalMoveTrack *horizontalTrack; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/Category/CALayer+LNDanmakuAttributesHolder.h: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+LNDanmakuAttributesHolder.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface CALayer (LNDanmakuAttributesHolder) 15 | 16 | //weak/delegate 17 | - (void)setDanmakuAttributes:(LNDanmakuAbstractAttributes * _Nullable)attributes; 18 | - (LNDanmakuAbstractAttributes * _Nullable)danmakuAttributes; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Stable/LNDanmakuStableTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuStableTrack.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuStableTrack.h" 10 | 11 | @implementation LNDanmakuStableTrack 12 | 13 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes 14 | { 15 | [super updateAttributes:attributes]; 16 | CGFloat currentX = self.position.x - attributes.size.width/2.f; 17 | CGFloat currentY = self.position.y - attributes.size.height/2.f; 18 | attributes.position = CGPointMake(currentX, currentY); 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/UIElements/Layer/LNDanmakuLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuLayer.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /* 15 | * Help user make a screenshot for a complex but stable view. 16 | **/ 17 | 18 | /* 19 | * If user choose other subclass of CALayer, should cancel CAAnimation. 20 | **/ 21 | 22 | @interface LNDanmakuLayer : CALayer 23 | 24 | + (LNDanmakuLayer *)danmakuLayerWithView:(UIView *)view; 25 | + (UIImage *)captureImageForLayer:(CALayer *)layer; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Pool/LNDanmakuPurePool.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPurePool.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * A pure pool contains one kind of obj. 15 | **/ 16 | 17 | @interface LNDanmakuPurePool : NSObject 18 | 19 | @property (nonatomic, assign) NSInteger maxCapacity; 20 | 21 | @property (nonatomic, assign) Class targetClass; 22 | @property (nonatomic, copy) NSString *poolKey; 23 | 24 | - (void)saveObj:(NSObject *)obj; 25 | - (NSObject *)getObj; 26 | 27 | - (void)clearPurePool; 28 | 29 | 30 | @end 31 | 32 | NS_ASSUME_NONNULL_END 33 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/DanmakuFactory/VideoDanmakuStyleList.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuStyleList.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * 建议在一个公共的样式表中配置一个弹幕播放器可能播放到的所有样式参数。 15 | * 可能有这些好处: 16 | * 1.如果所有的弹幕在初始化时都需要用到CTFont/AttributesDic等这些东西,你可以统一使用一套而不用频繁创建相同的Font/Dic 17 | * 2.当你的弹幕有一个半透明的圆角矩形背景时,你可以全局使用一个用作背景的CALayer和图像预合成技术来不断印刷出想要的UIImage。 18 | * 3.修改样式时容易找到。 19 | **/ 20 | 21 | /* 22 | * 根据弹幕的不同种类可以有多个子样式表。 23 | **/ 24 | 25 | @interface VideoDanmakuStyleList : NSObject 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Pool/NSObject+LNDanmakuPool.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+LNDanmakuPool.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "NSObject+LNDanmakuPool.h" 10 | #import 11 | 12 | NSString *const kLNDanmakuPoolKey = @"kLNDanmakuPoolKey"; 13 | 14 | @implementation NSObject (LNDanmakuPool) 15 | 16 | - (void)setDanmakuPoolKey:(NSString *)string 17 | { 18 | objc_setAssociatedObject(self, kLNDanmakuPoolKey.UTF8String, string, OBJC_ASSOCIATION_COPY_NONATOMIC); 19 | } 20 | 21 | - (NSString *)danmakuPoolKey 22 | { 23 | return objc_getAssociatedObject(self, kLNDanmakuPoolKey.UTF8String); 24 | } 25 | 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Circle/LNDanmakuCircleTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuCircleTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuCircleTrackController.h" 10 | 11 | @interface LNDanmakuCircleTrackController () 12 | 13 | @property (nonatomic, strong) LNDanmakuCircleTrack *circleTrack; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuCircleTrackController 18 | 19 | - (LNDanmakuAbstractTrack *)track 20 | { 21 | return self.circleTrack; 22 | } 23 | 24 | - (LNDanmakuCircleTrack *)circleTrack 25 | { 26 | if (!_circleTrack) { 27 | _circleTrack = [[LNDanmakuCircleTrack alloc] init]; 28 | } 29 | return _circleTrack; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Stable/LNDanmakuStableTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuStableTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuStableTrackController.h" 10 | 11 | @interface LNDanmakuStableTrackController () 12 | 13 | @property (nonatomic, strong) LNDanmakuStableTrack *stableTrack; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuStableTrackController 18 | 19 | - (LNDanmakuAbstractTrack *)track 20 | { 21 | return self.stableTrack; 22 | } 23 | 24 | - (LNDanmakuStableTrack *)stableTrack 25 | { 26 | if (!_stableTrack) { 27 | _stableTrack = [[LNDanmakuStableTrack alloc] init]; 28 | } 29 | return _stableTrack; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/GestureDemo/GestureDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // GestureDemoViewController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * 这个Demo用来演示如何同时让弹幕框架默认的手势和用户自定义的手势同时生效. 15 | * CALayer:如果使用CALayer系列视图来承载弹幕,那么只能使用LNDanmaku自带的唯一一个Tap手势。 16 | * 通常情况下弹幕更注重"看"而非"点",因此通用的tap手势能满足大部分业务场景 17 | * UIView:如果不得不使用自定义手势,那么你需要使用自己的UIView并添加自定义手势。 18 | * 当然,如果你UIView没有添加自定义手势,LNDanmaku的点击手势也能识别它。 19 | * LNDanmaku不会阻拦任何用户的自定义手势,即便它们与本身的Gesture冲突,也会优先响应自定义手势。 20 | **/ 21 | 22 | /* 23 | * 那些黑色的弹幕使用UIView和自定义手势 24 | * 其他颜色的弹幕是用默认的手势 25 | **/ 26 | 27 | @interface GestureDemoViewController : UIViewController 28 | 29 | @end 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMasterTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMasterUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/DanmakuFactory/VideoDanmakuFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuFactory.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAttributes.h" 11 | #import "VideoDanmakuModel.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /* 16 | * 这个工厂做 Model -> Attributes 转换 17 | * model通常被放在attributes的customObj中,通过attributs.customObj自省、强转访问 18 | * 正常的封装逻辑是: 19 | * LNDanmakuPlayer层的回调对外传递的都是attributes。 20 | * VideoDanmakuPlayer(业务层)回调对外传递的都是业务模型,即attributes.customObj。 21 | **/ 22 | 23 | @interface VideoDanmakuFactory : NSObject 24 | 25 | - (LNDanmakuAttributes *)attributesForDanmakuModel:(VideoDanmakuModel *)danmakuModel; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Christina/LNDanmakuChristinaTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuChristinaTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuChristinaTrackController.h" 10 | 11 | @interface LNDanmakuChristinaTrackController () 12 | 13 | @property (nonatomic, strong) LNDanmakuChristinaTrack *christinaTrack; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuChristinaTrackController 18 | 19 | - (LNDanmakuAbstractTrack *)track 20 | { 21 | return self.christinaTrack; 22 | } 23 | 24 | - (LNDanmakuChristinaTrack *)christinaTrack 25 | { 26 | if (!_christinaTrack) { 27 | _christinaTrack = [[LNDanmakuChristinaTrack alloc] init]; 28 | } 29 | return _christinaTrack; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Pool/LNDanmakuPool.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPool.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /* 12 | * A pure pool contains different kinds of obj using several pure pools. 13 | * Just like using UICollectionView/UITableView. 14 | * If you just "get" but not put back, then you have stolen an instance from this pool! ([○・`Д´・ ○]) 15 | **/ 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | @interface LNDanmakuPool : NSObject 20 | 21 | - (void)registerClass:(Class)class forKey:(NSString *)key; 22 | - (NSObject *)instanceForKey:(NSString *)key; 23 | - (void)saveInstance:(NSObject *)instance; 24 | 25 | @end 26 | 27 | @interface LNDanmakuPool (Control) 28 | 29 | - (void)clearPool; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Main/DemoItemObj.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoItemObj.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, DemoType) 12 | { 13 | DemoTypeHorizontal = 0, 14 | DemoTypeStable = 1, 15 | DemoTypeCircle, 16 | DemoTypeChristina, 17 | DemoTypePop, 18 | DemoTypeSin, 19 | 20 | DemoTypeDispatcherDefault, 21 | DemoTypeDispatcherLowDensity, 22 | DemoTypeDispatcherMostFast, 23 | 24 | DemoTypeGesture, 25 | 26 | DemoTypeTrackGroup, 27 | 28 | DemoTypeVideoDanmaku, 29 | 30 | DemoTypeCount, 31 | }; 32 | 33 | NS_ASSUME_NONNULL_BEGIN 34 | 35 | @interface DemoItemObj : NSObject 36 | 37 | @property (nonatomic, assign) DemoType type; 38 | 39 | @end 40 | 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Sin/LNDanmakuSinTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuSinTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuSinTrackController.h" 10 | 11 | @interface LNDanmakuSinTrackController () 12 | 13 | @property (nonatomic, strong) LNDanmakuSinTrack *sinTrack; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuSinTrackController 18 | 19 | - (instancetype)init 20 | { 21 | self = [super init]; 22 | if (self) { 23 | 24 | } 25 | return self; 26 | } 27 | 28 | - (LNDanmakuAbstractTrack *)track 29 | { 30 | return self.sinTrack; 31 | } 32 | 33 | - (LNDanmakuSinTrack *)sinTrack 34 | { 35 | if (!_sinTrack) { 36 | _sinTrack = [[LNDanmakuSinTrack alloc] init]; 37 | } 38 | return _sinTrack; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Pop/LNDanmakuPopTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPopTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuPopTrackController.h" 10 | 11 | @interface LNDanmakuPopTrackController () 12 | 13 | @property (nonatomic, strong) LNDanmakuPopTrack *popTrack; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuPopTrackController 18 | 19 | - (instancetype)init 20 | { 21 | self = [super init]; 22 | if (self) { 23 | 24 | } 25 | return self; 26 | } 27 | 28 | 29 | - (LNDanmakuAbstractTrack *)track 30 | { 31 | return self.popTrack; 32 | } 33 | 34 | - (LNDanmakuPopTrack *)popTrack 35 | { 36 | if (!_popTrack) { 37 | _popTrack = [[LNDanmakuPopTrack alloc] init]; 38 | } 39 | return _popTrack; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | #import "MainViewController.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | 20 | _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 21 | _window.backgroundColor = [UIColor whiteColor]; 22 | MainViewController *mainController = [[MainViewController alloc] init]; 23 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:mainController]; 24 | _window.rootViewController = nav; 25 | [_window makeKeyAndVisible]; 26 | return YES; 27 | } 28 | 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/TrackGroupDemo/TrackGroupDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TrackGroupDemoViewController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * 这个Demo用来展示如何使用TrackGroup对弹幕进行分轨 15 | * TrackGroup是由LNDanmaku中实现播放的基本组件(TrackController+Dispatcher)组成的最小播放逻辑单元。 16 | * Dispatcher类似一个工头,TrackController类似一个工人。 17 | * 一个工头可以管理多个工人,同时一个工人也可以被不同的工头管理。 18 | * TrackGroup主要被设计来克服条件弹幕的播放问题。 19 | * 例如:某些送较大型礼物的弹幕通常会被放在屏幕最顶端的1~2个轨道上,而不能被放在其他轨道上。 20 | * 在这种情况下,可以利用TrackGroup将顶端的两个轨道放在一个分组中,而这两个轨道在正常情况下也会被普通轨道占用着。 21 | * QA:那么如何保证送礼物的弹幕具有更高的优先级? 22 | * TrackGroup内置的Dispatcher会有一个高优队列,这个高优队列通常用来插入用户自己发送的弹幕或者其他优先级较高的弹幕。 23 | **/ 24 | 25 | /* 26 | * rainbow! (*^▽^*) 27 | **/ 28 | 29 | @interface TrackGroupDemoViewController : UIViewController 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/DispatcherDemo/DispatcherDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DispatcherDemoViewController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * 这个Demo用来演示如何应用不同的分发策略来顺应不同轨道 15 | * Default: 从上到下寻找第一个可以放置的轨道分发 16 | * LowDensity: 从上到下寻找最空闲的轨道分发 17 | * MostFast: 从上到下寻找可以放置,且能最快显示的轨道分发 18 | * 统一选择条形轨道演示 19 | **/ 20 | 21 | /* 22 | * 三种分布策略的差别仅在松散的状态下才能看出差别,拥塞状态下都是全充满的 23 | **/ 24 | 25 | typedef NS_ENUM(NSInteger, DispatcherDemoViewControllerType) 26 | { 27 | DispatcherDemoViewControllerDefault = 0, 28 | DispatcherDemoViewControllerLowDensity, 29 | DispatcherDemoViewControllerMostFast, 30 | }; 31 | 32 | 33 | @interface DispatcherDemoViewController : UIViewController 34 | 35 | - (instancetype)initWithType:(DispatcherDemoViewControllerType)type; 36 | 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Horizontal/LNDanmakuHorizontalMoveTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuHorizontalMoveTrack.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuHorizontalMoveTrack.h" 10 | 11 | @interface LNDanmakuHorizontalMoveTrack () 12 | 13 | @end 14 | 15 | @implementation LNDanmakuHorizontalMoveTrack 16 | 17 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes 18 | { 19 | [super updateAttributes:attributes]; 20 | float percent = attributes.currentPercent; 21 | CGFloat totalDistance = self.width + attributes.size.width; 22 | CGFloat currentDistance = totalDistance * percent; 23 | CGFloat currentX = self.startPosition.x + self.width - currentDistance; 24 | CGFloat currentY = self.startPosition.y - attributes.size.height/2.f; 25 | attributes.position = CGPointMake(currentX, currentY); 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMasterTests/LNDanmakuMasterTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuMasterTests.m 3 | // LNDanmakuMasterTests 4 | // 5 | // Created by Levison on 31.1.21. 6 | // 7 | 8 | #import 9 | 10 | @interface LNDanmakuMasterTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation LNDanmakuMasterTests 15 | 16 | - (void)setUp { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | - (void)tearDown { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | } 23 | 24 | - (void)testExample { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | - (void)testPerformanceExample { 30 | // This is an example of a performance test case. 31 | [self measureBlock:^{ 32 | // Put the code you want to measure the time of here. 33 | }]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Horizontal/LNDanmakuHorizontalMoveTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuHorizontalMoveTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuHorizontalMoveTrackController.h" 10 | 11 | @interface LNDanmakuHorizontalMoveTrackController () 12 | 13 | @property (nonatomic, strong) LNDanmakuHorizontalMoveTrack *horizontalTrack; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuHorizontalMoveTrackController 18 | 19 | - (instancetype)init 20 | { 21 | self = [super init]; 22 | if (self) { 23 | 24 | } 25 | return self; 26 | } 27 | 28 | - (LNDanmakuAbstractTrack *)track 29 | { 30 | return self.horizontalTrack; 31 | } 32 | 33 | - (LNDanmakuHorizontalMoveTrack *)horizontalTrack 34 | { 35 | if (!_horizontalTrack) { 36 | _horizontalTrack = [[LNDanmakuHorizontalMoveTrack alloc] init]; 37 | } 38 | return _horizontalTrack; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuPlayer/Model/VideoDanmakuModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuModel.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /* 15 | * 请求到的网络模型 16 | **/ 17 | 18 | /* 19 | * 如果有其他类型的弹幕可以一并在这里枚举出,不同的只是UIView本身和播放的轨道 20 | **/ 21 | 22 | typedef NS_ENUM(NSInteger, VideoDanmakuType) 23 | { 24 | VideoDanmakuTypeText, //文字 25 | VideoDanmakuTypeImage, //图片 26 | VideoDanmakuTypeGift, //礼物 27 | }; 28 | 29 | @interface VideoDanmakuModel : NSObject 30 | 31 | @property (nonatomic, assign) VideoDanmakuType type; 32 | 33 | //text 34 | @property (nonatomic, copy) NSString *textContent; 35 | //image 36 | @property (nonatomic, copy) NSString *urlString; 37 | @property (nonatomic, assign) CGSize imageSize; 38 | 39 | //Gift 特殊类型 40 | @property (nonatomic, strong) NSObject *giftModel; 41 | 42 | @end 43 | 44 | NS_ASSUME_NONNULL_END 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 LevisonNN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuMaster.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #ifndef LNDanmakuMaster_h 10 | #define LNDanmakuMaster_h 11 | 12 | #import "LNDanmakuClock.h" 13 | 14 | #import "LNDanmakuPool.h" 15 | 16 | #import "LNDanmakuView.h" 17 | #import "LNDanmakuLayer.h" 18 | #import "LNDanmakuTextLayer.h" 19 | 20 | #import "LNDanmakuAttributes.h" 21 | 22 | #import "LNDanmakuBaseMoveTrackController.h" 23 | #import "LNDanmakuBaseStableTrackController.h" 24 | #import "LNDanmakuBaseFreeTrackController.h" 25 | 26 | #import "LNDanmakuHorizontalMoveTrackController.h" 27 | #import "LNDanmakuStableTrackController.h" 28 | #import "LNDanmakuChristinaTrackController.h" 29 | #import "LNDanmakuCircleTrackController.h" 30 | #import "LNDanmakuPopTrackController.h" 31 | #import "LNDanmakuSinTrackController.h" 32 | 33 | #import "LNDanmakuContainerView.h" 34 | 35 | #import "LNDanmakuDispatcher.h" 36 | 37 | #import "LNDanmakuTrackGroup.h" 38 | 39 | #import "LNDanmakuPlayer.h" 40 | 41 | 42 | #endif /* LNDanmakuMaster_h */ 43 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Circle/LNDanmakuCircleTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuCircleTrack.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuCircleTrack.h" 10 | 11 | @implementation LNDanmakuCircleTrack 12 | 13 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes 14 | { 15 | [super updateAttributes:attributes]; 16 | 17 | float percent = attributes.currentPercent; 18 | float angle = percent * 2 * M_PI; 19 | 20 | CGFloat offsetX = sin(angle) * self.radius; 21 | CGFloat offsetY = cos(angle) * self.radius; 22 | 23 | CGPoint center = CGPointMake(self.center.x + offsetX, self.center.y - offsetY); 24 | 25 | attributes.position = CGPointMake(center.x - attributes.size.width/2.f, center.y - attributes.size.height/2.f); 26 | 27 | CGAffineTransform affine = CGAffineTransformMakeRotation(angle); 28 | attributes.transform = CATransform3DMakeAffineTransform(affine); 29 | 30 | attributes.opacity = angle * (2*M_PI - angle)/(M_PI * M_PI); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Dispatcher/LNDanmakuDispatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuDispatcher.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractDispatcher.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM(NSInteger, LNDanmakuDispatchStrategy) 14 | { 15 | LNDanmakuDispatchStrategyDefault = 0, //Find the first track to insert. 16 | LNDanmakuDispatchStrategyLowDensity, //Find the most free track to insert. 17 | LNDanmakuDispatchStrategyMostFastDisplay //Find the track with shortest waiting time. 18 | }; 19 | 20 | typedef NS_ENUM(NSInteger, LNDanmakuRecoverDispatchStrategy) 21 | { 22 | LNDanmakuRecoverDispatchStrategyDefault = 0, 23 | LNDanmakuRecoverDispatchStrategyLowDensity, 24 | LNDanmakuRecoverDispatchStrategyMostFastDisplay, 25 | }; 26 | 27 | @interface LNDanmakuDispatcher : LNDanmakuAbstractDispatcher 28 | 29 | @property (nonatomic, assign) LNDanmakuDispatchStrategy dispatchStrategy; 30 | @property (nonatomic, assign) LNDanmakuRecoverDispatchStrategy recoverDispatcherStrategy; 31 | 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Pop/LNDanmakuPopTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPopTrack.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrack.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM(NSInteger, LNDanmakuPopTrackStrategy) 14 | { 15 | LNDanmakuPopTrackStrategyDynamic = 0, 16 | LNDanmakuPopTrackStrategyStatic 17 | }; 18 | 19 | @interface LNDanmakuPopTrack : LNDanmakuAbstractTrack 20 | 21 | @property (nonatomic, assign) float A; //Amplitude, default is 1.f 22 | @property (nonatomic, assign) float beta; //damping, default is 10.f 23 | @property (nonatomic, assign) float w; //palstance, default is M_PI/8.f 24 | @property (nonatomic, assign) float theta; //phase, default is M_PI/2.f 25 | 26 | /* 27 | * If user choose static we will use default parameters, and get every animation detail from a static array. 28 | * In this case, all track parameters will not effect. 29 | * Good performance, sir. \(^o^)/~ 30 | **/ 31 | @property (nonatomic, assign) LNDanmakuPopTrackStrategy strategy; 32 | 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/TrackGroup/LNDanmakuTrackGroup.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuTrackGroup.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractDispatcher.h" 11 | #import "LNDanmakuAbstractTrackController.h" 12 | 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /* 17 | * A track group contains one dispatcher and several track controllers. 18 | * They form the smallest unit that can work independently. 19 | * You insert attributes into track groups instead of player, so that they can work on specific tracks with specific strategies. 20 | * Different track groups can share tracks with each other or player. 21 | **/ 22 | 23 | @interface LNDanmakuTrackGroup : NSObject 24 | 25 | @property (nonatomic, strong, readonly) LNDanmakuAbstractDispatcher *dispatcher; 26 | 27 | @property (nonatomic, copy, readonly) NSArray *currentTrackControllers; 28 | - (void)addTrack:(LNDanmakuAbstractTrackController *)trackController; 29 | - (void)removeTrack:(LNDanmakuAbstractTrackController *)trackController; 30 | 31 | - (void)clear; 32 | 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Christina/LNDanmakuChristinaTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuChristinaTrack.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuChristinaTrack.h" 10 | 11 | @implementation LNDanmakuChristinaTrack 12 | 13 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes 14 | { 15 | [super updateAttributes:attributes]; 16 | 17 | float percent = attributes.currentPercent; 18 | float angle = percent * 2 * M_PI; 19 | 20 | CGFloat radius = self.a * (1 - cos(angle)); 21 | 22 | CGFloat offsetX = sin(angle) * radius; 23 | CGFloat offsetY = cos(angle) * radius; 24 | 25 | CGPoint center = CGPointMake(self.center.x + offsetX, self.center.y - offsetY); 26 | 27 | attributes.position = CGPointMake(center.x - attributes.size.width/2.f, center.y - attributes.size.height/2.f); 28 | 29 | float rotateAngle = 0.f; 30 | rotateAngle = 3*angle/2.f - M_PI/2.f; 31 | CGAffineTransform affine = CGAffineTransformMakeRotation(rotateAngle); 32 | attributes.transform = CATransform3DMakeAffineTransform(affine); 33 | 34 | attributes.opacity = angle * (2*M_PI - angle)/(M_PI * M_PI); 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Clock/LNDanmakuClock.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuClock.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * Clock is used as an engine for every DanmakuPlayer. 15 | * Common video maybe 24 FPS, danmaku player needs 60FPS to keep fluency. 16 | * Danmakus will keep moving even if video player stopped. So we should not use video-player's callback to drive danmaku player. 17 | * Clock encapsulates CADisplayLink and provides easy interfaces for use. 18 | **/ 19 | 20 | /* 21 | * LNDanmakuClock provides interval between last callback and current callback for trackController/dispatcher to control the progress. 22 | * This little piece of time was called "elapsingTime". 23 | **/ 24 | 25 | @protocol LNDanmakuClockDelegate 26 | 27 | - (void)danmakuClockUpdateTimeInterval:(NSTimeInterval)time; 28 | 29 | @end 30 | 31 | @interface LNDanmakuClock : NSObject 32 | 33 | @property (nonatomic, assign, readonly) BOOL isPaused; 34 | @property (nonatomic, weak) id delegate; 35 | 36 | - (void)startOrResume; 37 | - (void)pause; 38 | - (void)stop; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/Category/UIView+LNDanmakuAttributesHolder.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+LNDanmakuAttributesHolder.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "UIView+LNDanmakuAttributesHolder.h" 10 | #import "LNDanmakuAttributesHolder.h" 11 | #import 12 | 13 | NSString *const kLNDanmakuViewAttributesHolder = @"kLNDanmakuViewAttributesHolder"; 14 | 15 | @implementation UIView (LNDanmakuAttributesHolder) 16 | 17 | //weak/delegate 18 | - (void)setDanmakuAttributes:(LNDanmakuAbstractAttributes * _Nullable)attributes 19 | { 20 | LNDanmakuAttributesHolder *attributesHolder = objc_getAssociatedObject(self, kLNDanmakuViewAttributesHolder.UTF8String); 21 | if (!attributesHolder) { 22 | attributesHolder = [[LNDanmakuAttributesHolder alloc] init]; 23 | } 24 | attributesHolder.attributes = attributes; 25 | objc_setAssociatedObject(self, kLNDanmakuViewAttributesHolder.UTF8String, attributesHolder, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 26 | } 27 | 28 | - (LNDanmakuAbstractAttributes * _Nullable)danmakuAttributes 29 | { 30 | LNDanmakuAttributesHolder *attributesHolder = objc_getAssociatedObject(self, kLNDanmakuViewAttributesHolder.UTF8String); 31 | return attributesHolder.attributes; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/Category/CALayer+LNDanmakuAttributesHolder.m: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+LNDanmakuAttributesHolder.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "CALayer+LNDanmakuAttributesHolder.h" 10 | #import "LNDanmakuAttributesHolder.h" 11 | #import 12 | 13 | NSString *const kLNDanmakuLayerAttributesHolder = @"kLNDanmakuLayerAttributesHolder"; 14 | 15 | @implementation CALayer (LNDanmakuAttributesHolder) 16 | 17 | //weak/delegate 18 | - (void)setDanmakuAttributes:(LNDanmakuAbstractAttributes * _Nullable)attributes 19 | { 20 | LNDanmakuAttributesHolder *attributesHolder = objc_getAssociatedObject(self, kLNDanmakuLayerAttributesHolder.UTF8String); 21 | if (!attributesHolder) { 22 | attributesHolder = [[LNDanmakuAttributesHolder alloc] init]; 23 | } 24 | attributesHolder.attributes = attributes; 25 | objc_setAssociatedObject(self, kLNDanmakuLayerAttributesHolder.UTF8String, attributesHolder, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 26 | } 27 | 28 | - (LNDanmakuAbstractAttributes * _Nullable)danmakuAttributes 29 | { 30 | LNDanmakuAttributesHolder *attributesHolder = objc_getAssociatedObject(self, kLNDanmakuLayerAttributesHolder.UTF8String); 31 | return attributesHolder.attributes; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/UIElements/Layer/LNDanmakuLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuLayer.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuLayer.h" 10 | 11 | @implementation LNDanmakuLayer 12 | 13 | + (LNDanmakuLayer *)danmakuLayerWithView:(UIView *)view 14 | { 15 | UIImage *renderedImage = nil; 16 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, 0.0, [UIScreen mainScreen].scale); 17 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 18 | renderedImage = UIGraphicsGetImageFromCurrentImageContext(); 19 | UIGraphicsEndImageContext(); 20 | LNDanmakuLayer *danmakuLayer = [[LNDanmakuLayer alloc] init]; 21 | danmakuLayer.bounds = view.bounds; 22 | danmakuLayer.contents = (id)renderedImage.CGImage; 23 | return danmakuLayer; 24 | } 25 | 26 | + (UIImage *)captureImageForLayer:(CALayer *)layer 27 | { 28 | UIImage *renderedImage = nil; 29 | UIGraphicsBeginImageContextWithOptions(layer.bounds.size, 0.0, [UIScreen mainScreen].scale); 30 | [layer renderInContext:UIGraphicsGetCurrentContext()]; 31 | renderedImage = UIGraphicsGetImageFromCurrentImageContext(); 32 | UIGraphicsEndImageContext(); 33 | return renderedImage; 34 | } 35 | 36 | - (id)actionForKey:(NSString *)event 37 | { 38 | return nil; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Sin/LNDanmakuSinTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuSinTrack.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuSinTrack.h" 10 | 11 | @interface LNDanmakuSinTrack () 12 | 13 | @end 14 | 15 | @implementation LNDanmakuSinTrack 16 | 17 | - (instancetype)init 18 | { 19 | self = [super init]; 20 | if (self) { 21 | self.T = 1.f; 22 | self.A = 20.f; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes 28 | { 29 | [super updateAttributes:attributes]; 30 | 31 | float percent = attributes.currentPercent; 32 | 33 | CGFloat alpha = M_PI * 2.f * (percent/self.T); 34 | CGFloat bias = sin(alpha) * self.A; 35 | 36 | CGFloat totalDistance = self.width + attributes.size.width; 37 | CGFloat currentDistance = totalDistance * percent; 38 | CGFloat currentX = self.startPosition.x + self.width - currentDistance; 39 | CGFloat currentY = self.startPosition.y - attributes.size.height/2.f + bias; 40 | 41 | attributes.position = CGPointMake(currentX, currentY); 42 | 43 | CGAffineTransform affine = CGAffineTransformMakeRotation(-atan(cos(alpha))); 44 | attributes.transform = CATransform3DMakeAffineTransform(affine); 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/ContainerView/LNDanmakuContainerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuContainerView.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | #import "LNDanmakuAbstractTrackController.h" 12 | 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @protocol LNDanmakuContrainerViewDelegate 17 | 18 | @optional 19 | - (void)danmakuContainerViewDidLoadAttributes:(LNDanmakuAbstractAttributes *)attributes; 20 | - (void)danmakuContainerViewDidUnloadAttributes:(LNDanmakuAbstractAttributes *)attributes; 21 | - (void)danmakuContainerDidTappedAttributes:(LNDanmakuAbstractAttributes *)attributes; 22 | 23 | @end 24 | 25 | 26 | /* 27 | * ContainerView helps user to deal with some simple tap gesture when using CALayers. 28 | * In most of time, user will not touch danmuku but just watch, so is not worthy to create tap-gesture for each danmaku. 29 | * ContainerView hold one tap-gesture instead of 100 tap-gesture, and decide which attributes to react dynamically. 30 | **/ 31 | 32 | /* 33 | * What's more, containerView will also be delegate of track-controllers, and responsible for add/remove danmaku-views/layers. 34 | **/ 35 | 36 | 37 | @interface LNDanmakuContainerView : UIView 38 | 39 | @property (nonatomic, weak) id delegate; 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/TracksDemo/TracksDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TracksDemoViewController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /* 14 | * 这个Demo用来展示每种轨道的基础用法 15 | * 1.Horizontal轨道是最常见的从右向左播放的轨道,也是用途最多的一种。 16 | * 2.Stable轨道是定点展示轨道,类似B站那种从上向下一列的定点轨道。 17 | * 3.Pop轨道内嵌了一个Spring动画,这种弹性动画通过一个衰减正弦函数实现: 18 | * y = e^(a*x) * sinx 19 | * 看起来就像一个弹簧在做有阻尼的简谐振动。 20 | * ——————————————————————————————————————————————以上三种是相对常用的三种轨道 21 | * 4.Circle轨道是一开始和Horizontal轨道一起开发,用于在开发过程中纠正那些容易向条形轨道倾斜的地方,使开发成果拓展性更强。 22 | * 5.Christina轨道为了佐证:在某些情况下,使用空间单位"速度"定义播放进度,没有使用"时间"通用性更强。 23 | * 如果你所定义的速度单位是pt/s,那么这个变量就无法定义角速度。 24 | * 6.Sin轨道最开始用于演示如何自定义一个移动轨道。 25 | * —————————————————————————————————————————————— 26 | * 后三种轨道为了表达LNDanmaku是一个支持大规模定制的弹幕框架,在使用时无需为未来可能出现的形式和样式担忧。 27 | * 7. 如果希望定制TrackController的播放逻辑,也可以定制LNDanmakuAbstractTrackController。 28 | * 事实上这是相对复杂的,因此提供了三种BaseTrackController供使用者继承从而适应各种场景。 29 | **/ 30 | 31 | typedef NS_ENUM(NSInteger, TracksDemoViewControllerType) 32 | { 33 | TracksDemoViewControllerHorizontal = 0, 34 | TracksDemoViewControllerStable, 35 | TracksDemoViewControllerCircle, 36 | TracksDemoViewControllerChristina, 37 | TracksDemoViewControllerPop, 38 | TracksDemoViewControllerSin, 39 | }; 40 | 41 | @interface TracksDemoViewController : UIViewController 42 | 43 | - (instancetype)initWithType:(TracksDemoViewControllerType)type; 44 | 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMasterUITests/LNDanmakuMasterUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuMasterUITests.m 3 | // LNDanmakuMasterUITests 4 | // 5 | // Created by Levison on 31.1.21. 6 | // 7 | 8 | #import 9 | 10 | @interface LNDanmakuMasterUITests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation LNDanmakuMasterUITests 15 | 16 | - (void)setUp { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | 19 | // In UI tests it is usually best to stop immediately when a failure occurs. 20 | self.continueAfterFailure = NO; 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | - (void)tearDown { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | - (void)testExample { 30 | // UI tests must launch the application that they test. 31 | XCUIApplication *app = [[XCUIApplication alloc] init]; 32 | [app launch]; 33 | 34 | // Use recording to get started writing UI tests. 35 | // Use XCTAssert and related functions to verify your tests produce the correct results. 36 | } 37 | 38 | - (void)testLaunchPerformance { 39 | if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { 40 | // This measures how long it takes to launch your application. 41 | [self measureWithMetrics:@[[[XCTApplicationLaunchMetric alloc] init]] block:^{ 42 | [[[XCUIApplication alloc] init] launch]; 43 | }]; 44 | } 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/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 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuDemoViewController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | //Not finished yet. 14 | 15 | /* 16 | * 这个Demo用来演示如何在实际的视频弹幕场景下应用LNDanmaku。 17 | * 事实上,LNDanmaku并不具备直接与视频(进度)对接的能力,它仅仅作为弹幕的展示工具。 18 | * 也正因如此,LNDanmaku也可以应用在除了视频弹幕外的其他场景中。 19 | * 例如:在Feed流中加入一个不断播放图片的模块,LNDanmaku也同样能胜任。 20 | **/ 21 | 22 | /* 23 | * 通常来说,想要将LNDanmaku应用在视频播放中,你应当在LNDanmaku外部增加一个业务层用来保持视频播放器与LNDanmaku联动。 24 | * 这个业务层组件通常作为视频播放器的一个插件:XXVideoDanmakuPlayer。 25 | * 这个组件内部维护一个LNDanmaku用来播放,并自身接收来自视频播放器的回调来控制弹幕播放进度和一系列事件。 26 | * 视频播放器与弹幕播放器的交互是单方面的:视频播放器会影响弹幕播放器,但弹幕播放器不会影响视频播放器。 27 | **/ 28 | 29 | /* 30 | * 如何保证弹幕播放进度与视频播放进度吻合? 31 | * 在最初设计弹幕播放器时,我们计划不对弹幕播放器进行分层,只在内部对数据做隔离: 32 | * 使用两条队列A、B分别对应"不可播放弹幕"和"等待播放弹幕"。 33 | * 将队列A视为一整根胡萝卜,B视为一个临时容纳胡萝卜丁的盘子,那么: 34 | * 我们在每次视频播放器的帧回调时将胡萝卜A切下一小块"o",这个"o"会进入队列B的队尾,B中容纳的所有胡萝卜丁都是可以吃(播)的。 35 | * 如果B已经装满了,那么先加入B中的胡萝卜丁会被抛弃掉,这样就总是能保证萝卜丁是新鲜的(与视频内容对应的)。 36 | * 那么,弹幕播放器只要不断从B的队首取弹幕进行播放就好了。 37 | * 38 | * 后来,队列A被划分到了业务层(并不是所有的弹幕都用在视频上),B则留在了LNDanmaku底层,业务层通过不断向B中丢萝卜丁实现弹幕播放。 39 | * 出于访问性能和弹幕下发制约的一些因素,队列A最终以可变字典的形式实现,字典的key是数字类型,value是数组类型。 40 | * 我们将某个小时间段内的弹幕全部装在一系列小数组中,例如:0s对应一个列表的弹幕,这些弹幕的起播时间都在 0~1s之间,1对应1~2s,以此类推。 41 | * 这样做的好处是:如果你想seek到某个时间点就不用遍历一个装着整个视频所有弹幕的大数组,而是先取整,通过时间片映射取到一个小弹幕数组,遍历那个小数组。 42 | * 1s钟内弹幕吞吐量是固定的(与轨道数和弹幕平均存活时间相关),所以这个小数组一定足够小(大于吞吐量的部分会被队列B抛弃),所以一般不必担心这里遍历的耗时。 43 | **/ 44 | 45 | /* 46 | * 与视频seek对应的弹幕恢复功能一并在这个Demo中体现。 47 | **/ 48 | 49 | @interface VideoDanmakuDemoViewController : UIViewController 50 | 51 | @end 52 | 53 | NS_ASSUME_NONNULL_END 54 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSupportsIndirectInputEvents 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 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Dispatcher/LNDanmakuDispatchQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuDispatchQueue.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /* 15 | * Every dispatch queue has two subqueues: 16 | * Default queue: For those danmaku which is loaded from server at begining. 17 | * High priority queue: For those danmaku user just send or other cases. 18 | * When poping, dispatch queue will visit high priority queue first, if high priority queue is empty then visit default queue. 19 | **/ 20 | 21 | typedef NS_ENUM(NSInteger, LNDanmakuDispatchQueuePriority) 22 | { 23 | LNDanmakuDispatchQueuePriorityDefault = 0, 24 | LNDanmakuDispatchQueuePriorityHigh 25 | }; 26 | 27 | 28 | @protocol LNDanmakuDispatchQueueDelegate 29 | 30 | @optional 31 | - (void)danmakuDispatchQueueDidDrop:(LNDanmakuAbstractAttributes *)attributes; 32 | 33 | @end 34 | 35 | @interface LNDanmakuDispatchQueue : NSObject 36 | 37 | @property (nonatomic, weak) id delegate; 38 | 39 | @property (nonatomic, assign) NSInteger maxCapacity; 40 | 41 | - (void)push:(LNDanmakuAbstractAttributes *)attributes;//defaultPriority 42 | - (void)push:(LNDanmakuAbstractAttributes *)attributes priority:(LNDanmakuDispatchQueuePriority)priority; 43 | - (LNDanmakuAbstractAttributes *)top; 44 | - (LNDanmakuAbstractAttributes *)pop; 45 | 46 | - (NSArray *)clearQueue; 47 | - (BOOL)contains:(LNDanmakuAbstractAttributes *)attributes; 48 | - (void)remove:(LNDanmakuAbstractAttributes *)attributes; 49 | - (BOOL)isEmpty; 50 | 51 | @end 52 | 53 | NS_ASSUME_NONNULL_END 54 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Clock/LNDanmakuClockProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuClockProxy.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuClockProxy.h" 10 | 11 | @implementation LNDanmakuClockProxy 12 | 13 | - (instancetype)initWithTarget:(id)target { 14 | _target = target; 15 | return self; 16 | } 17 | 18 | + (instancetype)proxyWithTarget:(id)target { 19 | return [[LNDanmakuClockProxy alloc] initWithTarget:target]; 20 | } 21 | 22 | - (id)forwardingTargetForSelector:(SEL)selector { 23 | return _target; 24 | } 25 | 26 | - (void)forwardInvocation:(NSInvocation *)invocation { 27 | void *null = NULL; 28 | [invocation setReturnValue:&null]; 29 | } 30 | 31 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { 32 | return [NSObject instanceMethodSignatureForSelector:@selector(init)]; 33 | } 34 | 35 | - (BOOL)respondsToSelector:(SEL)aSelector { 36 | return [_target respondsToSelector:aSelector]; 37 | } 38 | 39 | - (BOOL)isEqual:(id)object { 40 | return [_target isEqual:object]; 41 | } 42 | 43 | - (NSUInteger)hash { 44 | return [_target hash]; 45 | } 46 | 47 | - (Class)superclass { 48 | return [_target superclass]; 49 | } 50 | 51 | - (Class)class { 52 | return [_target class]; 53 | } 54 | 55 | - (BOOL)isKindOfClass:(Class)aClass { 56 | return [_target isKindOfClass:aClass]; 57 | } 58 | 59 | - (BOOL)isMemberOfClass:(Class)aClass { 60 | return [_target isMemberOfClass:aClass]; 61 | } 62 | 63 | - (BOOL)conformsToProtocol:(Protocol *)aProtocol { 64 | return [_target conformsToProtocol:aProtocol]; 65 | } 66 | 67 | - (BOOL)isProxy { 68 | return YES; 69 | } 70 | 71 | - (NSString *)description { 72 | return [_target description]; 73 | } 74 | 75 | - (NSString *)debugDescription { 76 | return [_target debugDescription]; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Pool/LNDanmakuPool.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPool.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuPool.h" 10 | #import "LNDanmakuPurePool.h" 11 | #import "NSObject+LNDanmakuPool.h" 12 | 13 | @interface LNDanmakuPool () 14 | 15 | @property (nonatomic, strong) NSMutableDictionary *poolMDic; 16 | 17 | @end 18 | 19 | @implementation LNDanmakuPool 20 | 21 | - (void)registerClass:(Class)class forKey:(NSString *)key 22 | { 23 | NSAssert(key != nil, @"key为空"); 24 | LNDanmakuPurePool *purePool = [self.poolMDic objectForKey:key]; 25 | if (purePool) { 26 | [purePool setTargetClass:class]; 27 | [purePool setPoolKey:key]; 28 | } else { 29 | purePool = [[LNDanmakuPurePool alloc] init]; 30 | purePool.targetClass = class; 31 | purePool.poolKey = key; 32 | [self.poolMDic setObject:purePool forKey:key]; 33 | } 34 | } 35 | 36 | - (NSObject *)instanceForKey:(NSString *)key 37 | { 38 | LNDanmakuPurePool *purePool = [self.poolMDic objectForKey:key]; 39 | if (purePool) { 40 | return [purePool getObj]; 41 | } 42 | return nil; 43 | } 44 | 45 | - (void)saveInstance:(NSObject *)instance 46 | { 47 | LNDanmakuPurePool *purePool = [self.poolMDic objectForKey:instance.danmakuPoolKey]; 48 | if (purePool) { 49 | [purePool saveObj:instance]; 50 | } 51 | } 52 | 53 | - (NSMutableDictionary *)poolMDic 54 | { 55 | if (!_poolMDic) { 56 | _poolMDic = [[NSMutableDictionary alloc] init]; 57 | } 58 | return _poolMDic; 59 | } 60 | 61 | @end 62 | 63 | @implementation LNDanmakuPool (Control) 64 | 65 | - (void)clearPool 66 | { 67 | for (LNDanmakuPurePool *purePool in self.poolMDic.allValues) { 68 | [purePool clearPurePool]; 69 | } 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | .DS_Store 9 | */.DS_Store 10 | 11 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 12 | *.xcscmblueprint 13 | *.xccheckout 14 | 15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 16 | build/ 17 | DerivedData/ 18 | *.moved-aside 19 | *.pbxuser 20 | !default.pbxuser 21 | *.mode1v3 22 | !default.mode1v3 23 | *.mode2v3 24 | !default.mode2v3 25 | *.perspectivev3 26 | !default.perspectivev3 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | 31 | ## App packaging 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | # CocoaPods 37 | # 38 | # We recommend against adding the Pods directory to your .gitignore. However 39 | # you should judge for yourself, the pros and cons are mentioned at: 40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 41 | # 42 | # Pods/ 43 | # 44 | # Add this line if you want to avoid checking in source code from the Xcode workspace 45 | # *.xcworkspace 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/Build/ 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. 57 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 60 | 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots/**/*.png 64 | fastlane/test_output 65 | 66 | # Code Injection 67 | # 68 | # After new code Injection tools there's a generated folder /iOSInjectionProject 69 | # https://github.com/johnno1962/injectionforxcode 70 | 71 | iOSInjectionProject/ 72 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/LNDanmakuAbstractAttributes.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractAttributes.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @class LNDanmakuAbstractAttributes; 15 | 16 | //track 17 | @protocol LNDanmakuAttributesUpdater 18 | 19 | @required 20 | - (void)resetAttributes:(LNDanmakuAbstractAttributes *)attributes; 21 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes; 22 | 23 | @end 24 | 25 | /* 26 | * Use LNDanmakuAttributes instead. 27 | */ 28 | 29 | @interface LNDanmakuAbstractAttributes : NSObject 30 | 31 | //This property depends on what kind of track you want define. 32 | @property (nonatomic, assign) CGSize size; 33 | @property (nonatomic, assign) CGPoint position; 34 | @property (nonatomic, assign) CATransform3D transform; 35 | @property (nonatomic, assign) float opacity; 36 | 37 | //Normally, TrackControllers use 38 | //Seek/recover may also set this property at the beginning. 39 | //If you must change this property in other cases, be careful. 40 | @property (nonatomic, assign) NSTimeInterval currentAliveTime; 41 | 42 | 43 | //Will totally disappear 44 | @property (nonatomic, assign) NSTimeInterval danmakuTime; 45 | //did totally disappear 46 | @property (nonatomic, assign) NSTimeInterval trackTime; 47 | //danmakuTime + trackTime 48 | @property (nonatomic, assign, readonly) NSTimeInterval totalAliveTime;//danmakuTime + trackTime 49 | //(currentAliveTime/totalAliveTime) 50 | @property (nonatomic, assign, readonly) float currentPercent; 51 | 52 | @property (nonatomic, strong) id customObj; 53 | 54 | //priority: layer > view, needs [super setPresentLayer:presentLayer]; 55 | @property (nonatomic, strong) CALayer * _Nullable presentLayer; 56 | @property (nonatomic, strong) UIView * _Nullable presentView; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END 61 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/LNDanmakuAbstractTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractTrackController.h" 10 | 11 | static NSInteger currentDanmakuNum; 12 | 13 | 14 | @interface LNDanmakuAbstractTrackController () 15 | 16 | 17 | @end 18 | 19 | @implementation LNDanmakuAbstractTrackController 20 | 21 | - (instancetype)init 22 | { 23 | self = [super init]; 24 | if (self) { 25 | 26 | } 27 | return self; 28 | } 29 | 30 | - (void)update:(NSTimeInterval)elapsingTime 31 | { 32 | 33 | } 34 | 35 | - (void)clearTrack 36 | { 37 | 38 | } 39 | 40 | - (BOOL)isFree 41 | { 42 | return NO; 43 | } 44 | 45 | - (NSTimeInterval)estimatedFinishTimeForCurrentAttributes 46 | { 47 | return 0; 48 | } 49 | 50 | - (NSTimeInterval)estimatedMinDisplayWaitTimeFor:(LNDanmakuAbstractAttributes *)attributes 51 | { 52 | return 0; 53 | } 54 | 55 | - (BOOL)couldLoadNewAttributes:(LNDanmakuAbstractAttributes *)attributes 56 | { 57 | return NO; 58 | } 59 | 60 | - (BOOL)containsAttributes:(LNDanmakuAbstractAttributes *)attributes 61 | { 62 | return NO; 63 | } 64 | 65 | - (void)loadAttributes:(LNDanmakuAbstractAttributes *)attributes 66 | { 67 | if (isNeedDanmakuCountDanmakuNumber) { 68 | currentDanmakuNum ++; 69 | NSLog(@"Danmaku count:%ld",(long)currentDanmakuNum); 70 | } 71 | } 72 | 73 | - (void)unloadAttributes:(LNDanmakuAbstractAttributes *)attributes 74 | { 75 | if (isNeedDanmakuCountDanmakuNumber) { 76 | currentDanmakuNum --; 77 | NSLog(@"Danmaku count:%ld",(long)currentDanmakuNum); 78 | } 79 | } 80 | 81 | @end 82 | 83 | @implementation LNDanmakuAbstractTrackController (Recover) 84 | 85 | - (NSTimeInterval)estimatedMaxAliveTimeFor:(LNDanmakuAbstractAttributes *)attributes 86 | { 87 | return 0.f; 88 | } 89 | 90 | - (void)recoverLoadAttributes:(LNDanmakuAbstractAttributes *)attributes 91 | { 92 | 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/TrackGroup/LNDanmakuTrackGroup.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuTrackGroup.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuTrackGroup.h" 10 | #import "LNDanmakuDispatcher.h" 11 | 12 | @interface LNDanmakuTrackGroup () 13 | 14 | @property (nonatomic, strong) LNDanmakuDispatcher *dispatcher; 15 | 16 | @property (nonatomic, strong) NSMutableArray *trackControllerMArr; 17 | 18 | @end 19 | 20 | @implementation LNDanmakuTrackGroup 21 | 22 | - (instancetype)init 23 | { 24 | self = [super init]; 25 | if (self) { 26 | 27 | } 28 | return self; 29 | } 30 | 31 | - (void)addTrack:(LNDanmakuAbstractTrackController *)trackController 32 | { 33 | if ([trackController isKindOfClass:[LNDanmakuAbstractTrackController class]] && (![self.trackControllerMArr containsObject:trackController])) { 34 | [self.trackControllerMArr addObject:trackController]; 35 | } 36 | } 37 | - (void)removeTrack:(LNDanmakuAbstractTrackController *)trackController 38 | { 39 | if ([self.trackControllerMArr containsObject:trackController]) { 40 | [self.trackControllerMArr removeObject:trackController]; 41 | } 42 | } 43 | 44 | - (void)clear 45 | { 46 | [self.dispatcher clear]; 47 | for (LNDanmakuAbstractTrackController *trackController in self.trackControllerMArr) { 48 | [trackController clearTrack]; 49 | } 50 | } 51 | 52 | - (NSArray *)currentTrackControllers 53 | { 54 | return [NSArray arrayWithArray:self.trackControllerMArr]; 55 | } 56 | 57 | - (NSMutableArray *)trackControllerMArr 58 | { 59 | if (!_trackControllerMArr) { 60 | _trackControllerMArr = [[NSMutableArray alloc] init]; 61 | } 62 | return _trackControllerMArr; 63 | } 64 | 65 | - (LNDanmakuAbstractDispatcher *)dispatcher 66 | { 67 | if (!_dispatcher) { 68 | _dispatcher = [[LNDanmakuDispatcher alloc] init]; 69 | } 70 | return _dispatcher; 71 | } 72 | 73 | @end 74 | 75 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Clock/LNDanmakuClock.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuClock.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuClock.h" 10 | #import "LNDanmakuClockProxy.h" 11 | #import 12 | 13 | @interface LNDanmakuClock () 14 | 15 | @property (nonatomic, strong) CADisplayLink *displayLink; 16 | 17 | @property (nonatomic, assign) CFTimeInterval realWorldTime; 18 | 19 | @property (nonatomic, assign) CGFloat speedScale; 20 | 21 | @property (nonatomic, assign) BOOL isPaused; 22 | 23 | @property (nonatomic, assign) NSTimeInterval allTime; 24 | 25 | @end 26 | 27 | @implementation LNDanmakuClock 28 | 29 | - (instancetype)init 30 | { 31 | self = [super init]; 32 | if (self) { 33 | _realWorldTime = CACurrentMediaTime(); 34 | _speedScale = 1.f; 35 | _isPaused = YES; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)dealloc 41 | { 42 | [self stop]; 43 | } 44 | 45 | - (void)resetClock 46 | { 47 | [self stop]; 48 | self.realWorldTime = CACurrentMediaTime(); 49 | self.speedScale = 1.f; 50 | self.isPaused = NO; 51 | _displayLink = [CADisplayLink displayLinkWithTarget:[LNDanmakuClockProxy proxyWithTarget:self] selector:@selector(callback)]; 52 | [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 53 | } 54 | 55 | - (void)callback 56 | { 57 | CFTimeInterval newRealWorldTime = CACurrentMediaTime(); 58 | if ((!self.isPaused) && 59 | self.delegate && 60 | [self.delegate respondsToSelector:@selector(danmakuClockUpdateTimeInterval:)]) { 61 | 62 | [self.delegate danmakuClockUpdateTimeInterval:newRealWorldTime - self.realWorldTime]; 63 | } else { 64 | NSAssert(YES, @"LNDanmakuClock is iding"); 65 | } 66 | 67 | self.realWorldTime = newRealWorldTime; 68 | } 69 | 70 | - (void)startOrResume 71 | { 72 | if (!_displayLink) { 73 | [self resetClock]; 74 | } 75 | if (self.isPaused) { 76 | self.isPaused = NO; 77 | } 78 | } 79 | 80 | - (void)pause 81 | { 82 | self.isPaused = YES; 83 | } 84 | 85 | - (void)stop 86 | { 87 | self.isPaused = YES; 88 | [_displayLink invalidate]; 89 | _displayLink = nil; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/LNDanmakuAbstractTrackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractTrackController.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | #import "LNDanmakuAbstractTrack.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /* 16 | * Open current playing count. 17 | **/ 18 | #define isNeedDanmakuCountDanmakuNumber NO 19 | 20 | /* 21 | * Actually, it's not easy to define a trackController. 22 | * So we provide baseTrackControllers. 23 | * User can use subclasses of baseTrackControllers and return different tracks to customize. 24 | **/ 25 | 26 | @protocol LNDanmakuTrackControllerViewDelegate 27 | 28 | @required 29 | - (void)viewShouldLoadAttributes:(LNDanmakuAbstractAttributes *)attributes; 30 | - (void)viewShouldRemoveAttributes:(LNDanmakuAbstractAttributes *)attributes; 31 | 32 | @end 33 | 34 | @interface LNDanmakuAbstractTrackController : NSObject 35 | 36 | @property (nonatomic, strong, readonly) LNDanmakuAbstractTrack *track; 37 | 38 | @property (nonatomic, weak) id viewDelegate; 39 | 40 | @end 41 | 42 | @interface LNDanmakuAbstractTrackController (Override) 43 | 44 | - (void)update:(NSTimeInterval)elapsingTime; 45 | - (void)clearTrack; 46 | - (BOOL)isFree; 47 | //Give some time for trackController to finish current danmakus. 48 | - (NSTimeInterval)estimatedFinishTimeForCurrentAttributes; 49 | //In some cases, danmaku will not be displayed after it was dispatched to one trackController immediately. 50 | - (NSTimeInterval)estimatedMinDisplayWaitTimeFor:(LNDanmakuAbstractAttributes *)attributes; 51 | 52 | - (BOOL)containsAttributes:(LNDanmakuAbstractAttributes *)attributes; 53 | - (void)loadAttributes:(LNDanmakuAbstractAttributes *)attributes; 54 | - (void)unloadAttributes:(LNDanmakuAbstractAttributes *)attributes; 55 | 56 | @end 57 | 58 | @interface LNDanmakuAbstractTrackController (Recover) 59 | 60 | // If danmakuA was delay because danmakuB(pre-danmaku), this function gives a max-currentAliveTime so that danmakuA can just follow danmakuB. 61 | - (NSTimeInterval)estimatedMaxAliveTimeFor:(LNDanmakuAbstractAttributes *)attributes; 62 | - (void)recoverLoadAttributes:(LNDanmakuAbstractAttributes *)attributes; 63 | 64 | @end 65 | 66 | NS_ASSUME_NONNULL_END 67 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Pool/LNDanmakuPurePool.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPurePool.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuPurePool.h" 10 | #import "NSObject+LNDanmakuPool.h" 11 | 12 | @interface LNDanmakuPurePool () 13 | 14 | @property (nonatomic, strong) NSMutableSet *poolMSet; 15 | 16 | @end 17 | 18 | @implementation LNDanmakuPurePool 19 | 20 | - (instancetype)init 21 | { 22 | self = [super init]; 23 | if (self) { 24 | self.maxCapacity = 300; 25 | self.targetClass = [NSObject class]; 26 | self.poolKey = @"kNSObject"; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)saveObj:(NSObject *)obj 32 | { 33 | if (self.poolMSet.count > self.maxCapacity) { 34 | while (self.poolMSet.count > self.maxCapacity) { 35 | [self.poolMSet removeObject:self.poolMSet.anyObject]; 36 | } 37 | } else if (self.poolMSet.count == self.maxCapacity) { 38 | //Fulled . 39 | } else { 40 | if ([obj isKindOfClass:self.targetClass] && [[obj danmakuPoolKey] isEqualToString:self.poolKey]) { 41 | [self.poolMSet addObject:obj]; 42 | } 43 | } 44 | } 45 | 46 | - (NSObject *)getObj 47 | { 48 | NSObject *resultObj = nil; 49 | while (self.poolMSet.count > 0) { 50 | NSObject *obj = [self.poolMSet anyObject]; 51 | if ([obj isKindOfClass:self.targetClass] && [obj.danmakuPoolKey isEqualToString:self.poolKey]) { 52 | resultObj = obj; 53 | [self.poolMSet removeObject:obj]; 54 | break; 55 | } else { 56 | [self.poolMSet removeObject:obj]; 57 | } 58 | } 59 | 60 | if (resultObj) { 61 | return resultObj; 62 | } else { 63 | resultObj = [[self.targetClass alloc] init]; 64 | [resultObj setDanmakuPoolKey:self.poolKey]; 65 | return resultObj; 66 | } 67 | } 68 | 69 | - (void)clearPurePool 70 | { 71 | [self.poolMSet removeAllObjects]; 72 | } 73 | 74 | 75 | - (void)setTargetClass:(Class)targetClass 76 | { 77 | if (targetClass != _targetClass) { 78 | [self.poolMSet removeAllObjects]; 79 | _targetClass = targetClass; 80 | } 81 | } 82 | 83 | - (void)setPoolKey:(NSString *)poolKey 84 | { 85 | if (poolKey != _poolKey) { 86 | [self.poolMSet removeAllObjects]; 87 | _poolKey = [poolKey copy]; 88 | } 89 | } 90 | 91 | - (NSMutableSet *)poolMSet 92 | { 93 | if (!_poolMSet) { 94 | _poolMSet = [[NSMutableSet alloc] init]; 95 | } 96 | return _poolMSet; 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoPlayer/VideoPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoPlayer.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /* 15 | * 假设这就是一个视频播放器! (⊙_⊙) 16 | **/ 17 | 18 | /* 19 | * 视频的弹幕信息通常是这样下发的: 20 | * a. 通常的音视频流会有三种类型数据:视频、音频、字幕,专业搞弹幕业务的团队可能会将弹幕信息融入到直播流或是视频文件中。 21 | * 这涉及到流媒体的一些技术,一般不是专门做弹幕业务的公司不会这样做,三方音视频播放器通常也没有内嵌弹幕功能,所以我谨慎地推测大部分情况应该是采用下面这种方法。 22 | * b. 根据播放进度分片请求弹幕数据,这种相对第一种显得不是很优雅,但只要能够对准弹幕和视频的播放时间点,体验差别其实不是很大。 23 | * 24 | * 我并没有搞到音视频流,只是大致模仿一下采用分片请求弹幕数据的方法,提出了一种这样实现的思路。 25 | * 不论哪种拉弹幕数据的方式,只要能保证播放器播放时能够及时取到弹幕数据,用户体验其实差别不大。 26 | **/ 27 | 28 | /* 29 | * 保证弹幕和视频同步的核心: 30 | * 1.根据弹幕数量、轨道数量设置一个合理的队列(之前说的B队列)长度。 31 | * 队列越长实时性越差,但容错率更高;反之实时性更强,容错率更低;所以应该尝试在一个可接受的延时内选择最长长度。 32 | * 2.根据我调研的一些主流的视频App,目前能够做到在seek时弹幕瞬间恢复弹幕的只有B站的App(就是因为这个我感觉他应该是用a那种方法实现的)。 33 | * 腾讯视频和其他的一些App通常是seek后弹幕再从右侧重新播放出来。 34 | * 我们采用b方法虽然达不到a那种理想的效果,但LNDanmaku提供了弹幕恢复接口,使用这些方法可以实现seek到某个时间点后,该时间点附近的弹幕瞬间恢复。 35 | * 弹幕恢复在弹幕稀疏时与a方法结果是一致的,因为稀疏的几个弹幕不会因为追赶或者抛弃问题影响后面的弹幕。 36 | * 在拥塞时,恢复机制和队列本身的抛弃机制都会稍微影响恢复结果,但总能保证最优先考虑距离seek时间点最近的弹幕。 37 | * 由于弹幕本身只是一种烘托氛围的工具,所以只要能保证seek后也是满屏的弹幕,看着热闹就行了。(p≧w≦q) 38 | **/ 39 | 40 | @interface VideoModel : NSObject 41 | 42 | @property (nonatomic, assign) NSTimeInterval totalTime; 43 | 44 | //no use 45 | @property (nonatomic, copy) NSString *url; 46 | 47 | @end 48 | 49 | typedef NS_ENUM(NSInteger, VideoPlayerStatus) { 50 | VideoPlayerStatusNone, //刚初始化 51 | //VideoPlayerStatusPreparing, 52 | VideoPlayerStatusPlaying, 53 | //VideoPlayerStatusLoading, 54 | VideoPlayerStatusPaused, 55 | VideoPlayerStatusStopped, 56 | }; 57 | 58 | @protocol VideoPlayerDelegate 59 | 60 | @optional 61 | - (void)videoPlayerDidLoadModel:(VideoModel *)videoModel; 62 | - (void)videoPlayerProgress:(NSTimeInterval)currentTime totalTime:(NSTimeInterval)totalTime; 63 | - (void)videoPlayerStatusDidChangeFrom:(VideoPlayerStatus)originStatus to:(VideoPlayerStatus)targetStatus; 64 | 65 | @end 66 | 67 | 68 | @interface VideoPlayer : NSObject 69 | 70 | @property (nonatomic, weak) NSObject *delegate; 71 | 72 | @property (nonatomic, strong, readonly) UIView *playerView; 73 | 74 | @property (nonatomic, assign, readonly) NSTimeInterval totalTime; 75 | @property (nonatomic, assign, readonly) NSTimeInterval currentTime; 76 | 77 | @property (nonatomic, assign, readonly) VideoPlayerStatus status; 78 | 79 | - (instancetype)initWithVideoModel:(VideoModel *)videoModel; 80 | 81 | - (void)start; 82 | - (void)pause; 83 | - (void)resume; 84 | - (void)stop; 85 | 86 | - (void)seek:(NSTimeInterval)targetTime; 87 | 88 | @end 89 | 90 | NS_ASSUME_NONNULL_END 91 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Dispatcher/LNDanmakuAbstractDispatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractDispatcher.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractDispatcher.h" 10 | 11 | @interface LNDanmakuAbstractDispatcher () 12 | 13 | @property (nonatomic, assign) NSInteger pRestGrainNum; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuAbstractDispatcher 18 | 19 | //override 20 | - (void)dispatchNewAttributesToFreeTracks:(NSArray *)trackControllerArray 21 | { 22 | for (LNDanmakuAbstractTrackController *trackController in trackControllerArray) { 23 | if (trackController.isFree) { 24 | if (self.restGrainNum > 0) { 25 | [self decreaseRestGrainNum]; 26 | } else { 27 | break; 28 | } 29 | } 30 | } 31 | } 32 | //override 33 | - (void)insertNewAttributes:(NSArray *)newAttributesArray 34 | { 35 | 36 | } 37 | 38 | - (void)insertHighPriorityAttributes:(NSArray *)highPriorityAttributesArray 39 | { 40 | 41 | } 42 | 43 | - (void)clear 44 | { 45 | 46 | } 47 | 48 | - (BOOL)containsAttributes:(LNDanmakuAbstractAttributes *)attributes 49 | { 50 | return NO; 51 | } 52 | 53 | - (void)removeAttributes:(LNDanmakuAbstractAttributes *)attributes 54 | { 55 | } 56 | 57 | - (void)setMaxSmoothGrainNum:(NSInteger)maxSmoothGrainNum 58 | { 59 | if (maxSmoothGrainNum <= 1) { 60 | _maxSmoothGrainNum = 1; 61 | } 62 | _maxSmoothGrainNum = maxSmoothGrainNum; 63 | } 64 | 65 | - (NSInteger)restGrainNum 66 | { 67 | NSInteger superRestGrain = NSIntegerMax; 68 | if (self.grainDataSource && [self.grainDataSource respondsToSelector:@selector(restGrainNumForDispatcher:)]) { 69 | superRestGrain = [self.grainDataSource restGrainNumForDispatcher:self]; 70 | } 71 | NSInteger realRestGrain = MIN(superRestGrain, self.pRestGrainNum); 72 | return realRestGrain; 73 | } 74 | 75 | - (void)decreaseRestGrainNum 76 | { 77 | self.pRestGrainNum--; 78 | if (self.grainDataSource && [self.grainDataSource respondsToSelector:@selector(decreaseGrainNumForDispatcher:)]) { 79 | [self.grainDataSource decreaseGrainNumForDispatcher:self]; 80 | } 81 | } 82 | 83 | - (void)danmakuClockUpdateTimeInterval:(NSTimeInterval)time 84 | { 85 | self.pRestGrainNum = self.maxSmoothGrainNum; 86 | } 87 | 88 | @end 89 | 90 | @implementation LNDanmakuAbstractDispatcher (Recover) 91 | 92 | - (void)recoverInsertAttributes:(NSArray *)attributesArray toTrackControllers:(nonnull NSArray *)trackControllerArray 93 | { 94 | 95 | } 96 | 97 | @end 98 | 99 | -------------------------------------------------------------------------------- /LNDanmakuMaster.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "LNDanmakuMaster" 3 | s.version = "0.0.2" 4 | s.summary = "A feature-rich danmaku component." 5 | s.homepage = "https://github.com/LevisonNN/LNDanmakuMaster" 6 | s.author = { "Levison" => "levisoncn@gmail.com" } 7 | s.platform = :ios, "8.0" 8 | s.source = { :git => "https://github.com/LevisonNN/LNDanmakuMaster.git", :tag => "#{s.version}" } 9 | #s.source_files = "Classes", "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/**/*.{h,m}" 10 | s.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster.h" 11 | s.license = "MIT" 12 | s.requires_arc = true 13 | 14 | s.subspec 'UIElements' do |ss| 15 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/UIElements/**/*.{h,m}" 16 | end 17 | 18 | s.subspec 'Attributes' do |ss| 19 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/**/*.{h,m}" 20 | end 21 | 22 | s.subspec 'Pool' do |ss| 23 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Pool/**/*.{h,m}" 24 | end 25 | 26 | s.subspec 'ContainerView' do |ss| 27 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/ContainerView/**/*.{h,m}" 28 | ss.dependency "LNDanmakuMaster/Attributes" 29 | ss.dependency "LNDanmakuMaster/Track" 30 | end 31 | 32 | s.subspec 'Dispatcher' do |ss| 33 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Dispatcher/**/*.{h,m}" 34 | ss.dependency "LNDanmakuMaster/Attributes" 35 | ss.dependency "LNDanmakuMaster/Track" 36 | ss.dependency "LNDanmakuMaster/Clock" 37 | end 38 | 39 | s.subspec 'Track' do |ss| 40 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/**/*.{h,m}" 41 | ss.dependency "LNDanmakuMaster/Attributes" 42 | end 43 | 44 | s.subspec 'TrackGroup' do |ss| 45 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/TrackGroup/**/*.{h,m}" 46 | ss.dependency "LNDanmakuMaster/Attributes" 47 | ss.dependency "LNDanmakuMaster/Dispatcher" 48 | ss.dependency "LNDanmakuMaster/Track" 49 | ss.dependency "LNDanmakuMaster/Clock" 50 | end 51 | 52 | s.subspec 'Clock' do |ss| 53 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Clock/**/*.{h,m}" 54 | end 55 | 56 | s.subspec 'Player' do |ss| 57 | ss.source_files = "LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Player/**/*.{h,m}" 58 | ss.dependency "LNDanmakuMaster/Dispatcher" 59 | ss.dependency "LNDanmakuMaster/ContainerView" 60 | ss.dependency "LNDanmakuMaster/Track" 61 | ss.dependency "LNDanmakuMaster/TrackGroup" 62 | ss.dependency "LNDanmakuMaster/Clock" 63 | ss.dependency "LNDanmakuMaster/Pool" 64 | ss.dependency "LNDanmakuMaster/Attributes" 65 | end 66 | end 67 | 68 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Dispatcher/LNDanmakuAbstractDispatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractDispatcher.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractAttributes.h" 11 | #import "LNDanmakuAbstractTrackController.h" 12 | #import "LNDanmakuClock.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @class LNDanmakuAbstractDispatcher; 17 | 18 | @protocol LNDanmakuAbstractDispatcherDelegate 19 | 20 | @optional 21 | - (void)dispatcher:(LNDanmakuAbstractDispatcher *)dispatcher 22 | willDispatch:(LNDanmakuAbstractAttributes *)attributes 23 | to:(LNDanmakuAbstractTrackController *)trackController; 24 | - (void)dispatcher:(LNDanmakuAbstractDispatcher *)dispatcher 25 | didDispatch:(LNDanmakuAbstractAttributes *)attributes 26 | to:(LNDanmakuAbstractTrackController *)trackController; 27 | 28 | - (void)dispatcher:(LNDanmakuAbstractDispatcher *)dispatcher 29 | drop:(LNDanmakuAbstractAttributes *)attributes; 30 | 31 | - (void)dispatcher:(LNDanmakuAbstractDispatcher *)dispatcher 32 | clear:(NSArray *)attributesArr; 33 | 34 | @end 35 | 36 | @protocol LNDanmakuAbstractDispatcherSmoothGrainDataSource 37 | 38 | @required 39 | - (NSInteger)restGrainNumForDispatcher:(LNDanmakuAbstractDispatcher *)dispatcher; 40 | - (void)decreaseGrainNumForDispatcher:(LNDanmakuAbstractDispatcher *)dispatcher; 41 | 42 | @end 43 | 44 | @interface LNDanmakuAbstractDispatcher : NSObject 45 | 46 | @property (nonatomic, weak) id delegate; 47 | 48 | @property (nonatomic, weak) id grainDataSource; 49 | 50 | @property (nonatomic, assign) NSInteger maxSmoothGrainNum; 51 | 52 | - (NSInteger)restGrainNum; 53 | - (void)decreaseRestGrainNum; 54 | 55 | @end 56 | 57 | @interface LNDanmakuAbstractDispatcher (Override) 58 | 59 | //private use 60 | - (void)dispatchNewAttributesToFreeTracks:(NSArray *)trackControllerArray; 61 | 62 | //public use 63 | - (void)insertNewAttributes:(NSArray *)newAttributesArray; 64 | - (void)insertHighPriorityAttributes:(NSArray *)highPriorityAttributesArray; 65 | - (void)clear; 66 | - (BOOL)containsAttributes:(LNDanmakuAbstractAttributes *)attributes; 67 | - (void)removeAttributes:(LNDanmakuAbstractAttributes *)attributes; 68 | @end 69 | 70 | @interface LNDanmakuAbstractDispatcher (Recover) 71 | 72 | - (void)recoverInsertAttributes:(NSArray *)attributesArray toTrackControllers:(NSArray *)trackControllerArray; 73 | 74 | @end 75 | 76 | 77 | NS_ASSUME_NONNULL_END 78 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoDanmakuDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoDanmakuDemoViewController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoDanmakuDemoViewController.h" 10 | #import "VideoPlayer.h" 11 | 12 | @interface VideoDanmakuDemoViewController () 13 | 14 | @property (nonatomic, strong) VideoPlayer *player; 15 | 16 | @property (nonatomic, strong) UISlider *slider; 17 | 18 | @property (nonatomic, strong) VideoModel *videoModel; 19 | 20 | @end 21 | 22 | @implementation VideoDanmakuDemoViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | [self addSubviews]; 27 | [self addConstraints]; 28 | } 29 | 30 | - (void)addSubviews 31 | { 32 | [self.view addSubview:self.player.playerView]; 33 | [self.view addSubview:self.slider]; 34 | } 35 | 36 | - (void)addConstraints 37 | { 38 | self.player.playerView.frame = self.view.bounds; 39 | self.slider.frame = CGRectMake(50.f, self.view.bounds.size.height - 44.f, self.view.bounds.size.width - 100.f, 44.f); 40 | } 41 | 42 | - (void)viewDidAppear:(BOOL)animated 43 | { 44 | [super viewDidAppear:animated]; 45 | [self.player start]; 46 | } 47 | 48 | - (void)viewDidDisappear:(BOOL)animated 49 | { 50 | [super viewDidDisappear:animated]; 51 | [self.player stop]; 52 | } 53 | 54 | - (VideoPlayer *)player 55 | { 56 | if (!_player) { 57 | _player = [[VideoPlayer alloc] initWithVideoModel:self.videoModel]; 58 | _player.delegate = self; 59 | } 60 | return _player; 61 | } 62 | 63 | - (VideoModel *)videoModel 64 | { 65 | if (!_videoModel) { 66 | _videoModel = [[VideoModel alloc] init]; 67 | _videoModel.totalTime = 200.f; 68 | _videoModel.url = @""; 69 | } 70 | return _videoModel; 71 | } 72 | 73 | - (UISlider *)slider 74 | { 75 | if (!_slider) { 76 | _slider = [[UISlider alloc] init]; 77 | [_slider addTarget:self action:@selector(sliderValueDidChange) forControlEvents:UIControlEventValueChanged]; 78 | _slider.minimumValue = 0.f; 79 | _slider.maximumValue = self.videoModel.totalTime; 80 | } 81 | return _slider; 82 | } 83 | 84 | - (void)sliderValueDidChange 85 | { 86 | if (!self.slider.isTracking) { 87 | [self.player seek:self.slider.value]; 88 | } 89 | } 90 | 91 | #pragma -mark videoDelegate 92 | 93 | - (void)videoPlayerProgress:(NSTimeInterval)currentTime totalTime:(NSTimeInterval)totalTime 94 | { 95 | if (!self.slider.isTracking) { 96 | self.slider.value = currentTime; 97 | } 98 | } 99 | 100 | - (void)videoPlayerStatusDidChangeFrom:(VideoPlayerStatus)originStatus to:(VideoPlayerStatus)targetStatus 101 | { 102 | 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Main/DemoItemCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoItemCollectionViewCell.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "DemoItemCollectionViewCell.h" 10 | 11 | NSString *const kDemoItemCollectionViewCell = @"kDemoItemCollectionViewCell"; 12 | 13 | @interface DemoItemCollectionViewCell () 14 | 15 | @property (nonatomic, strong) UILabel *titleLabel; 16 | 17 | @property (nonatomic, strong) DemoItemObj *itemObj; 18 | 19 | @end 20 | 21 | @implementation DemoItemCollectionViewCell 22 | 23 | - (instancetype)initWithFrame:(CGRect)frame 24 | { 25 | self = [super initWithFrame:frame]; 26 | if (self) { 27 | [self addSubviews]; 28 | [self addConstraints]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)setItemObj:(DemoItemObj *)item 34 | { 35 | _itemObj = item; 36 | switch (item.type) { 37 | case DemoTypeStable:{ 38 | self.titleLabel.text = @"定点轨道"; 39 | } break; 40 | case DemoTypeCircle:{ 41 | self.titleLabel.text = @"圆形轨道"; 42 | } break; 43 | case DemoTypeChristina:{ 44 | self.titleLabel.text = @"心形轨道"; 45 | } break; 46 | case DemoTypePop:{ 47 | self.titleLabel.text = @"气泡轨道"; 48 | } break; 49 | case DemoTypeSin:{ 50 | self.titleLabel.text = @"正弦轨道"; 51 | } break; 52 | case DemoTypeDispatcherDefault: { 53 | self.titleLabel.text = @"最密分发"; 54 | } break; 55 | case DemoTypeDispatcherLowDensity: { 56 | self.titleLabel.text = @"最疏分发"; 57 | } break; 58 | case DemoTypeDispatcherMostFast: { 59 | self.titleLabel.text = @"最快分发"; 60 | } break; 61 | case DemoTypeGesture: { 62 | self.titleLabel.text = @"弹幕手势"; 63 | } break; 64 | case DemoTypeTrackGroup: { 65 | self.titleLabel.text = @"轨道组"; 66 | } break; 67 | case DemoTypeVideoDanmaku: { 68 | self.titleLabel.text = @"视频弹幕"; 69 | } break; 70 | default:{ 71 | self.titleLabel.text = @"横向轨道"; 72 | 73 | } break; 74 | } 75 | self.contentView.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:(rand()%255)/255.f]; 76 | } 77 | 78 | - (void)addSubviews 79 | { 80 | [self.contentView addSubview:self.titleLabel]; 81 | } 82 | 83 | - (void)addConstraints 84 | { 85 | self.titleLabel .frame = self.contentView.frame; 86 | } 87 | 88 | - (void)layoutSubviews 89 | { 90 | [super layoutSubviews]; 91 | [self addConstraints]; 92 | } 93 | 94 | - (UILabel *)titleLabel 95 | { 96 | if (!_titleLabel) { 97 | _titleLabel = [[UILabel alloc] init]; 98 | _titleLabel.textAlignment = NSTextAlignmentCenter; 99 | } 100 | return _titleLabel; 101 | } 102 | 103 | @end 104 | 105 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/DispatcherDemo/DispatcherDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DispatcherDemoViewController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "DispatcherDemoViewController.h" 10 | #import "LNDanmakuMaster.h" 11 | 12 | @interface DispatcherDemoViewController () 13 | 14 | @property (nonatomic, assign) DispatcherDemoViewControllerType type; 15 | 16 | @property (nonatomic, strong) LNDanmakuPlayer* danmakuPlayer; 17 | 18 | @property (nonatomic, strong) NSTimer *timer; 19 | 20 | @end 21 | 22 | @implementation DispatcherDemoViewController 23 | 24 | - (instancetype)initWithType:(DispatcherDemoViewControllerType)type 25 | { 26 | self = [super init]; 27 | if (self) { 28 | self.type = type; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)viewDidLoad { 34 | [super viewDidLoad]; 35 | 36 | [self setupPlayer]; 37 | [self addSubviews]; 38 | [self addConstraints]; 39 | 40 | _timer = [NSTimer timerWithTimeInterval:0.256f target:self selector:@selector(addRandomDanmaku) userInfo:nil repeats:YES]; 41 | 42 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 43 | } 44 | 45 | - (void)viewDidDisappear:(BOOL)animated 46 | { 47 | [super viewDidDisappear:animated]; 48 | if (_timer) { 49 | [_timer invalidate]; 50 | _timer = nil; 51 | } 52 | } 53 | 54 | - (void)dealloc 55 | { 56 | NSLog(@"释放了"); 57 | } 58 | 59 | - (void)addRandomDanmaku 60 | { 61 | LNDanmakuLayer *danmakuLayer = [[LNDanmakuLayer alloc] init]; 62 | danmakuLayer.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:1.f].CGColor; 63 | danmakuLayer.cornerRadius = 12.f; 64 | LNDanmakuAttributes *attributes = [[LNDanmakuAttributes alloc] init]; 65 | attributes.presentLayer = danmakuLayer; 66 | attributes.size = CGSizeMake((rand()%100)/100.f * 88.f + 22.f, 24.f); 67 | attributes.danmakuTime = 2.f; 68 | attributes.trackTime = 2.f + ((rand()%100)/100.f)*4.f; 69 | [self.danmakuPlayer insertAttributes:@[attributes]]; 70 | } 71 | 72 | - (void)addSubviews 73 | { 74 | [self.view addSubview:self.danmakuPlayer.containerView]; 75 | } 76 | 77 | - (void)addConstraints 78 | { 79 | self.danmakuPlayer.containerView.frame = self.view.bounds; 80 | } 81 | 82 | - (void)setupPlayer 83 | { 84 | for (int i = 0; i < 20; i++) { 85 | LNDanmakuHorizontalMoveTrackController *horizontalTrackController = [[LNDanmakuHorizontalMoveTrackController alloc] init]; 86 | horizontalTrackController.horizontalTrack.startPosition = CGPointMake(0, 44.f + 30.f * i); 87 | horizontalTrackController.horizontalTrack.width = self.view.frame.size.width; 88 | horizontalTrackController.spaceTimeInterval = 0.2f; 89 | [self.danmakuPlayer addTrack:horizontalTrackController]; 90 | } 91 | 92 | [self.danmakuPlayer start]; 93 | } 94 | 95 | - (LNDanmakuPlayer *)danmakuPlayer 96 | { 97 | if (!_danmakuPlayer) { 98 | _danmakuPlayer = [[LNDanmakuPlayer alloc] init]; 99 | switch (self.type) { 100 | case DispatcherDemoViewControllerMostFast: { 101 | _danmakuPlayer.dispatcher.dispatchStrategy = LNDanmakuDispatchStrategyMostFastDisplay; 102 | } break; 103 | case DispatcherDemoViewControllerLowDensity: { 104 | _danmakuPlayer.dispatcher.dispatchStrategy = LNDanmakuDispatchStrategyLowDensity; 105 | } break;; 106 | default:{ 107 | _danmakuPlayer.dispatcher.dispatchStrategy = LNDanmakuDispatchStrategyDefault; 108 | } break; 109 | } 110 | } 111 | return _danmakuPlayer; 112 | } 113 | 114 | @end 115 | 116 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/GestureDemo/GestureDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // GestureDemoViewController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "GestureDemoViewController.h" 10 | #import "LNDanmakuMaster.h" 11 | 12 | @interface GestureDemoViewController () 13 | 14 | @property (nonatomic, strong) LNDanmakuPlayer* danmakuPlayer; 15 | 16 | @property (nonatomic, strong) NSTimer *timer; 17 | 18 | @end 19 | 20 | @implementation GestureDemoViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | 25 | [self setupPlayer]; 26 | [self addSubviews]; 27 | [self addConstraints]; 28 | 29 | _timer = [NSTimer timerWithTimeInterval:0.256f target:self selector:@selector(addRandomDanmaku) userInfo:nil repeats:YES]; 30 | 31 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 32 | } 33 | 34 | - (void)viewDidDisappear:(BOOL)animated 35 | { 36 | [super viewDidDisappear:animated]; 37 | if (_timer) { 38 | [_timer invalidate]; 39 | _timer = nil; 40 | } 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | NSLog(@"释放了"); 46 | } 47 | 48 | - (void)addRandomDanmaku 49 | { 50 | LNDanmakuAttributes *attributes = [[LNDanmakuAttributes alloc] init]; 51 | if (rand()%2) { 52 | //Layer 53 | LNDanmakuLayer *danmakuLayer = [[LNDanmakuLayer alloc] init]; 54 | danmakuLayer.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:1.f].CGColor; 55 | danmakuLayer.cornerRadius = 12.f; 56 | attributes.presentLayer = danmakuLayer; 57 | 58 | } else { 59 | UIView *danmakuView = [[UIView alloc] init]; 60 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(customTapGesture:)]; 61 | danmakuView.backgroundColor = [UIColor blackColor]; 62 | danmakuView.layer.cornerRadius = 12.f; 63 | [danmakuView addGestureRecognizer:tapGesture]; 64 | attributes.presentView = danmakuView; 65 | } 66 | attributes.size = CGSizeMake((rand()%100)/100.f * 88.f + 22.f, 24.f); 67 | attributes.danmakuTime = 2.f; 68 | attributes.trackTime = 4.f + ((rand()%100)/100.f)*4.f; 69 | [self.danmakuPlayer insertAttributes:@[attributes]]; 70 | } 71 | 72 | - (void)addSubviews 73 | { 74 | [self.view addSubview:self.danmakuPlayer.containerView]; 75 | } 76 | 77 | - (void)addConstraints 78 | { 79 | self.danmakuPlayer.containerView.frame = self.view.bounds; 80 | } 81 | 82 | - (void)setupPlayer 83 | { 84 | for (int i = 0; i < 20; i++) { 85 | LNDanmakuHorizontalMoveTrackController *horizontalTrackController = [[LNDanmakuHorizontalMoveTrackController alloc] init]; 86 | horizontalTrackController.horizontalTrack.startPosition = CGPointMake(0, 44.f + 30.f * i); 87 | horizontalTrackController.horizontalTrack.width = self.view.frame.size.width; 88 | horizontalTrackController.spaceTimeInterval = 0.2f; 89 | [self.danmakuPlayer addTrack:horizontalTrackController]; 90 | } 91 | 92 | [self.danmakuPlayer start]; 93 | } 94 | 95 | - (LNDanmakuPlayer *)danmakuPlayer 96 | { 97 | if (!_danmakuPlayer) { 98 | _danmakuPlayer = [[LNDanmakuPlayer alloc] init]; 99 | _danmakuPlayer.delegate = self; 100 | } 101 | return _danmakuPlayer; 102 | } 103 | 104 | - (void)danmakuPlayerDidTapAttributes:(LNDanmakuAbstractAttributes *)attributes 105 | { 106 | self.title = @"触发默认手势"; 107 | NSLog(@"默认tap: %@", attributes); 108 | } 109 | 110 | - (void)customTapGesture:(UIGestureRecognizer *)tapGesture 111 | { 112 | self.title = @"触发自定义手势"; 113 | NSLog(@"自定义手势: %@",tapGesture); 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Attributes/LNDanmakuAbstractAttributes.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuAbstractAttributes.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuAbstractAttributes.h" 10 | #import "CALayer+LNDanmakuAttributesHolder.h" 11 | #import "UIView+LNDanmakuAttributesHolder.h" 12 | 13 | 14 | static NSInteger createCount; 15 | 16 | @interface LNDanmakuAbstractAttributes () 17 | 18 | @end 19 | 20 | @implementation LNDanmakuAbstractAttributes 21 | { 22 | CATransform3D _transform; 23 | } 24 | 25 | - (instancetype)init 26 | { 27 | self = [super init]; 28 | if (self) { 29 | _opacity = 1.f; 30 | _transform = CATransform3DIdentity; 31 | createCount ++; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)setSize:(CGSize)size 37 | { 38 | _size = size; 39 | [self updateSize]; 40 | } 41 | 42 | - (void)updateSize 43 | { 44 | if (self.presentLayer) { 45 | self.presentLayer.bounds = CGRectMake(0, 0, _size.width, _size.height); 46 | } else if (self.presentView) { 47 | self.presentView.bounds = CGRectMake(0, 0, _size.width, _size.height); 48 | } 49 | } 50 | 51 | - (void)setPosition:(CGPoint)position 52 | { 53 | _position = position; 54 | [self updatePosition]; 55 | } 56 | 57 | - (void)updatePosition 58 | { 59 | if (self.presentLayer) { 60 | self.presentLayer.position = CGPointMake(_position.x + _size.width/2.f, _position.y + _size.height/2.f); 61 | } else if (self.presentView) { 62 | self.presentView.center = CGPointMake(_position.x + _size.width/2.f, _position.y + _size.height/2.f); 63 | } 64 | } 65 | 66 | - (void)updateFrame 67 | { 68 | //frame布局在transform时有点问题 69 | [self updateSize]; 70 | [self updatePosition]; 71 | } 72 | 73 | - (void)setTransform:(CATransform3D)transform 74 | { 75 | _transform = transform; 76 | [self updateTransform]; 77 | } 78 | 79 | - (void)updateTransform 80 | { 81 | if (_presentLayer) { 82 | _presentLayer.transform = _transform; 83 | } else if (_presentView) { 84 | _presentView.layer.transform = _transform; 85 | } 86 | } 87 | 88 | - (void)setOpacity:(float)opacity 89 | { 90 | _opacity = opacity; 91 | [self updateOpacity]; 92 | } 93 | 94 | - (void)updateOpacity 95 | { 96 | if (_presentLayer) { 97 | _presentLayer.opacity = _opacity; 98 | } else { 99 | _presentView.layer.opacity = _opacity; 100 | } 101 | } 102 | 103 | - (void)setPresentLayer:(CALayer *)presentLayer 104 | { 105 | if (_presentView) { 106 | return; 107 | } 108 | 109 | if (_presentLayer) { 110 | [_presentLayer setDanmakuAttributes:nil]; 111 | } 112 | _presentLayer = presentLayer; 113 | [_presentLayer setDanmakuAttributes:self]; 114 | 115 | [self updateFrame]; 116 | [self updateOpacity]; 117 | [self updateTransform]; 118 | } 119 | 120 | - (void)setPresentView:(UIView *)presentView 121 | { 122 | if (_presentLayer) { 123 | return; 124 | } 125 | 126 | if (_presentView) { 127 | [_presentView setDanmakuAttributes:nil]; 128 | } 129 | _presentView = presentView; 130 | [_presentView setDanmakuAttributes:self]; 131 | [self updateFrame]; 132 | [self updateOpacity]; 133 | [self updateTransform]; 134 | } 135 | 136 | - (NSTimeInterval)totalAliveTime 137 | { 138 | return self.trackTime + self.danmakuTime; 139 | } 140 | 141 | - (float)currentPercent 142 | { 143 | if (self.totalAliveTime > 0.f && self.currentAliveTime >= 0.f) { 144 | float percent = self.currentAliveTime/self.totalAliveTime; 145 | return percent > 1.f ? 1.f: percent; 146 | } 147 | return 0.f; 148 | } 149 | 150 | - (void)dealloc 151 | { 152 | createCount--; 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/ContainerView/LNDanmakuContainerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuContainerView.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuContainerView.h" 10 | #import "CALayer+LNDanmakuAttributesHolder.h" 11 | #import "UIView+LNDanmakuAttributesHolder.h" 12 | 13 | @interface LNDanmakuContainerView () 14 | 15 | @property (nonatomic, strong) UITapGestureRecognizer *tapGesture; 16 | 17 | @end 18 | 19 | @implementation LNDanmakuContainerView 20 | 21 | - (instancetype)init 22 | { 23 | self = [super init]; 24 | if (self) { 25 | [self addGestureRecognizer:self.tapGesture]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)setTapGestureEnabled:(BOOL)enabled 31 | { 32 | self.tapGesture.enabled = enabled; 33 | } 34 | 35 | - (UITapGestureRecognizer *)tapGesture 36 | { 37 | if (!_tapGesture) { 38 | _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapped:)]; 39 | } 40 | return _tapGesture; 41 | } 42 | 43 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 44 | { 45 | if (self.hidden) { 46 | return [super hitTest:point withEvent:event]; 47 | } 48 | for (CALayer *layer in [self.layer.sublayers reverseObjectEnumerator]) { 49 | if ([layer hitTest:point]) { 50 | if (layer.danmakuAttributes) { 51 | return self; 52 | } else { 53 | return [super hitTest:point withEvent:event]; 54 | } 55 | break; 56 | } 57 | } 58 | return nil; 59 | } 60 | 61 | - (void)didTapped:(UITapGestureRecognizer *)tap 62 | { 63 | CGPoint point = [tap locationInView:self]; 64 | CALayer *tappedLayer = nil; 65 | UIView *tappedView = nil; 66 | for (CALayer *layer in [self.layer.sublayers reverseObjectEnumerator]) { 67 | if ([layer hitTest:point]) { 68 | if (layer.danmakuAttributes) { 69 | tappedLayer = layer; 70 | } else { 71 | if ([layer.delegate isKindOfClass:[UIView class]]) { 72 | tappedView = (UIView *)layer.delegate; 73 | } 74 | } 75 | break; 76 | } 77 | } 78 | 79 | LNDanmakuAbstractAttributes *targetAttributes; 80 | if (tappedLayer) { 81 | targetAttributes = [tappedLayer danmakuAttributes]; 82 | } else if (tappedView) { 83 | targetAttributes = [tappedView danmakuAttributes]; 84 | } 85 | 86 | if (targetAttributes && self.delegate && [self.delegate respondsToSelector:@selector(danmakuContainerDidTappedAttributes:)]) { 87 | [self.delegate danmakuContainerDidTappedAttributes:targetAttributes]; 88 | } 89 | } 90 | 91 | - (void)viewShouldLoadAttributes:(LNDanmakuAbstractAttributes *)attributes 92 | { 93 | if (attributes.presentView) { 94 | [self addSubview:attributes.presentView]; 95 | } 96 | 97 | if (attributes.presentLayer) { 98 | [self.layer addSublayer:attributes.presentLayer]; 99 | } 100 | 101 | if (self.delegate && [self.delegate respondsToSelector:@selector(danmakuContainerViewDidLoadAttributes:)]) { 102 | [self.delegate danmakuContainerViewDidLoadAttributes:attributes]; 103 | } 104 | } 105 | - (void)viewShouldRemoveAttributes:(LNDanmakuAbstractAttributes *)attributes 106 | { 107 | if ([self.subviews containsObject:attributes.presentView]) { 108 | [attributes.presentView removeFromSuperview]; 109 | } 110 | 111 | if ([self.layer.sublayers containsObject:attributes.presentLayer]) { 112 | [attributes.presentLayer removeFromSuperlayer]; 113 | } 114 | 115 | if (self.delegate && [self.delegate respondsToSelector:@selector(danmakuContainerViewDidUnloadAttributes:)]) { 116 | [self.delegate danmakuContainerViewDidUnloadAttributes:attributes]; 117 | } 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/VideoDanmakuDemo/VideoPlayer/VideoPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoPlayer.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "VideoPlayer.h" 10 | 11 | @implementation VideoModel 12 | 13 | @end 14 | 15 | @interface VideoPlayer () 16 | 17 | @property (nonatomic, strong) UIView *playerView; 18 | 19 | @property (nonatomic, assign) VideoPlayerStatus status; 20 | @property (nonatomic, strong) NSTimer *timer; 21 | 22 | @property (nonatomic, assign) NSTimeInterval totalTime; 23 | @property (nonatomic, assign) NSTimeInterval currentTime; 24 | 25 | @property (nonatomic, assign) CGFloat r; // step = 1 26 | @property (nonatomic, assign) CGFloat g; // step = 3 27 | @property (nonatomic, assign) CGFloat b; // step = 5 28 | 29 | @end 30 | 31 | @implementation VideoPlayer 32 | 33 | - (instancetype)initWithVideoModel:(VideoModel *)videoModel 34 | { 35 | self = [super init]; 36 | if (self) { 37 | self.totalTime = videoModel.totalTime; 38 | self.currentTime = 0.f; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)setStatus:(VideoPlayerStatus)status 44 | { 45 | if (_status != status) { 46 | VideoPlayerStatus originStatus = _status; 47 | _status = status; 48 | if (self.delegate && [self.delegate respondsToSelector:@selector(videoPlayerStatusDidChangeFrom:to:)]) { 49 | [self.delegate videoPlayerStatusDidChangeFrom:originStatus to:status]; 50 | } 51 | } 52 | } 53 | 54 | - (void)invalidTimer 55 | { 56 | if (_timer) { 57 | [_timer invalidate]; 58 | _timer = nil; 59 | } 60 | } 61 | 62 | - (void)restartTimer 63 | { 64 | [self invalidTimer]; 65 | _timer = [NSTimer timerWithTimeInterval:0.025f target:self selector:@selector(flashAndCallBack) userInfo:nil repeats:YES]; 66 | [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 67 | } 68 | 69 | //这看起来只是颜色变化,实则在播放视频 o(* ̄︶ ̄*)o 70 | - (void)flashAndCallBack 71 | { 72 | if (self.status != VideoPlayerStatusPlaying) { 73 | return; 74 | } 75 | 76 | if (self.totalTime - self.currentTime < 0.025f) { 77 | //播放结束 78 | [self stop]; 79 | } else { 80 | self.currentTime += 0.025f; 81 | } 82 | 83 | NSInteger accumulatedR = ((NSInteger)floor(self.currentTime/0.025f) * 1)%510; 84 | NSInteger accumulatedG = ((NSInteger)floor(self.currentTime/0.025f) * 3)%510; 85 | NSInteger accumulatedB = ((NSInteger)floor(self.currentTime/0.025f) * 5)%510; 86 | 87 | if (accumulatedR > 255) { 88 | self.r = (510 - accumulatedR)/255.f; 89 | } else { 90 | self.r = accumulatedR/255.f; 91 | } 92 | 93 | if (accumulatedG > 255) { 94 | self.g = (510 - accumulatedG)/255.f; 95 | } else { 96 | self.g = accumulatedG/255.f; 97 | } 98 | 99 | if (accumulatedB > 255) { 100 | self.b = (510 - accumulatedB)/255.f; 101 | } else { 102 | self.b = accumulatedB/255.f; 103 | } 104 | 105 | self.playerView.backgroundColor = [UIColor colorWithRed:self.r green:self.g blue:self.b alpha:0.2f]; 106 | if (self.delegate && [self.delegate respondsToSelector:@selector(videoPlayerProgress:totalTime:)]) { 107 | [self.delegate videoPlayerProgress:self.currentTime totalTime:self.totalTime]; 108 | } 109 | } 110 | 111 | - (void)start 112 | { 113 | self.status = VideoPlayerStatusPlaying; 114 | self.currentTime = 0.f; 115 | [self invalidTimer]; 116 | [self restartTimer]; 117 | } 118 | - (void)pause 119 | { 120 | self.status = VideoPlayerStatusPaused; 121 | } 122 | 123 | - (void)resume 124 | { 125 | self.status = VideoPlayerStatusPlaying; 126 | } 127 | 128 | - (void)stop 129 | { 130 | self.status = VideoPlayerStatusStopped; 131 | [self invalidTimer]; 132 | self.currentTime = 0.f; 133 | } 134 | 135 | - (void)seek:(NSTimeInterval)targetTime 136 | { 137 | if (targetTime >= 0.f && targetTime <= self.totalTime) { 138 | self.currentTime = targetTime; 139 | } 140 | } 141 | 142 | - (UIView *)playerView 143 | { 144 | if (!_playerView) { 145 | _playerView = [[UIView alloc] init]; 146 | } 147 | return _playerView; 148 | } 149 | 150 | @end 151 | 152 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/BaseTrackController/LNDanmakuBaseStableTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuBaseStableTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseStableTrackController.h" 10 | 11 | @interface LNDanmakuBaseStableTrackController () 12 | 13 | @property (nonatomic, strong) LNDanmakuAbstractAttributes *currentAttributes; 14 | 15 | @property (nonatomic, assign) NSTimeInterval freeInterval; 16 | 17 | @end 18 | 19 | @implementation LNDanmakuBaseStableTrackController 20 | 21 | - (instancetype)init 22 | { 23 | self = [super init]; 24 | if (self) { 25 | 26 | } 27 | return self; 28 | } 29 | 30 | - (void)update:(NSTimeInterval)elapsingTime 31 | { 32 | if (self.currentAttributes) { 33 | self.currentAttributes.currentAliveTime += elapsingTime; 34 | if (self.currentAttributes.currentAliveTime > self.currentAttributes.totalAliveTime) { 35 | [self unloadAttributes:self.currentAttributes]; 36 | } else { 37 | [self.track updateAttributes:self.currentAttributes]; 38 | } 39 | } else { 40 | if (self.freeInterval < 2.f) { 41 | self.freeInterval += elapsingTime; 42 | } 43 | } 44 | } 45 | 46 | - (void)clearTrack 47 | { 48 | [self unloadAttributes:self.currentAttributes]; 49 | self.freeInterval = 2.f; 50 | } 51 | 52 | - (BOOL)isFree 53 | { 54 | if (self.currentAttributes == nil && self.freeInterval >= 2.f) { 55 | return YES; 56 | } 57 | return NO; 58 | } 59 | 60 | - (BOOL)containsAttributes:(LNDanmakuAbstractAttributes *)attributes 61 | { 62 | if (self.currentAttributes == attributes) { 63 | return YES; 64 | } 65 | return NO; 66 | } 67 | 68 | - (NSTimeInterval)estimatedFinishTimeForCurrentAttributes 69 | { 70 | if (self.currentAttributes) { 71 | NSTimeInterval rest = self.currentAttributes.totalAliveTime - self.currentAttributes.currentAliveTime; 72 | return rest>0.f?rest + 2.f : 2.f; 73 | } else { 74 | NSTimeInterval rest = 2.f - self.freeInterval; 75 | return rest>0.f?rest:0.f; 76 | } 77 | } 78 | 79 | - (NSTimeInterval)estimatedMinDisplayWaitTimeFor:(LNDanmakuAbstractAttributes *)attributes 80 | { 81 | return [self estimatedFinishTimeForCurrentAttributes]; 82 | } 83 | 84 | //recover 85 | - (NSTimeInterval)estimatedMaxAliveTimeFor:(LNDanmakuAbstractAttributes *)attributes 86 | { 87 | if (self.estimatedFinishTimeForCurrentAttributes > 0.f) { 88 | return -1.f; 89 | } else { 90 | return attributes.totalAliveTime; 91 | } 92 | } 93 | 94 | - (void)recoverLoadAttributes:(LNDanmakuAbstractAttributes *)attributes 95 | { 96 | if (![self isFree]) { 97 | return; 98 | } 99 | 100 | if ([self containsAttributes:attributes]) { 101 | return; 102 | } 103 | 104 | [super recoverLoadAttributes:attributes]; 105 | 106 | NSTimeInterval maxAliveTime = [self estimatedMaxAliveTimeFor:attributes]; 107 | if (maxAliveTime >= 0.f) { 108 | self.currentAttributes = attributes; 109 | [self.viewDelegate viewShouldLoadAttributes:attributes]; 110 | [self.track updateAttributes:attributes]; 111 | } else { 112 | return; 113 | } 114 | } 115 | 116 | //recover 117 | 118 | - (void)loadAttributes:(LNDanmakuAbstractAttributes *)attributes 119 | { 120 | if (![self isFree]) { 121 | return; 122 | } 123 | 124 | if (!attributes || attributes == self.currentAttributes) { 125 | return; 126 | } 127 | 128 | [self.track resetAttributes:attributes]; 129 | [super loadAttributes:attributes]; 130 | self.currentAttributes = attributes; 131 | [self.viewDelegate viewShouldLoadAttributes:attributes]; 132 | [self.track updateAttributes:attributes]; 133 | self.freeInterval = 0.f; 134 | } 135 | 136 | - (void)unloadAttributes:(LNDanmakuAbstractAttributes *)attributes 137 | { 138 | if (attributes == self.currentAttributes && attributes) { 139 | [super unloadAttributes:attributes]; 140 | [self.viewDelegate viewShouldRemoveAttributes:attributes]; 141 | self.currentAttributes = nil; 142 | } 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Player/LNDanmakuPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPlayer.h 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LNDanmakuAbstractDispatcher.h" 11 | #import "LNDanmakuContainerView.h" 12 | #import "LNDanmakuTrackGroup.h" 13 | #import "LNDanmakuDispatcher.h" 14 | 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /* 19 | * The player combines and packages dispatcher、container、track controller、clock. 20 | **/ 21 | 22 | typedef NS_ENUM(NSUInteger, LNDanmakuPlayerStatus) { 23 | LNDanmakuPlayerStatusNone = 0, //Just initialized. 24 | LNDanmakuPlayerStatusPlay = 1, //Playing. 25 | LNDanmakuPlayerStatusStop = 2, //Terminated, 26 | LNDanmakuPlayerStatusPause = 3, //Paused. 27 | }; 28 | 29 | @protocol LNDanmakuPlayerDelegate 30 | 31 | @optional 32 | //Queue life circle. 33 | - (void)danmakuPlayerDidInsertAttributes:(LNDanmakuAbstractAttributes *)attributes to:(LNDanmakuAbstractDispatcher *)dispatcher; 34 | - (void)danmakuPlayerDidDropAttributes:(LNDanmakuAbstractAttributes *)attributes from:(LNDanmakuAbstractDispatcher *)dispatcher; 35 | - (void)danmakuPlayerDidClearAttributes:(LNDanmakuAbstractAttributes *)attributes from:(LNDanmakuAbstractDispatcher *)dispatcher; 36 | - (void)danmakuPlayerWillDispatchAttributes:(LNDanmakuAbstractAttributes *)attributes 37 | from:(LNDanmakuAbstractDispatcher *)dispatcher 38 | to:(LNDanmakuAbstractTrackController *)trackController; 39 | - (void)danmakuPlayerDidDispatchAttributes:(LNDanmakuAbstractAttributes *)attributes 40 | from:(LNDanmakuAbstractDispatcher *)dispatcher 41 | to:(LNDanmakuAbstractTrackController *)trackController; 42 | //Track life circle. 43 | - (void)danmakuPlayerDidDisplayAttributes:(LNDanmakuAbstractAttributes *)attributes; 44 | - (void)danmakuPlayerDidEndDisplayAttributes:(LNDanmakuAbstractAttributes *)attributes; 45 | 46 | //Will be put into pool. 47 | - (void)danmakuPlayerWillRecoverAttributes:(LNDanmakuAbstractAttributes *)attributes; 48 | 49 | //Was tapped. 50 | - (void)danmakuPlayerDidTapAttributes:(LNDanmakuAbstractAttributes *)attributes; 51 | 52 | @end 53 | 54 | @interface LNDanmakuPlayer : NSObject 55 | 56 | @property (nonatomic, weak) id delegate; 57 | 58 | //@property (nonatomic, strong, readonly) LNDanmakuClock *clock; 59 | @property (nonatomic, strong, readonly) LNDanmakuContainerView *containerView; 60 | //@property (nonatomic, strong, readonly) NSMutableArray *trackControllerMArr; 61 | @property (nonatomic, strong, readonly) LNDanmakuDispatcher *dispatcher; 62 | 63 | @property (nonatomic, assign, readonly) LNDanmakuPlayerStatus status; 64 | 65 | @end 66 | 67 | @interface LNDanmakuPlayer (Track) 68 | 69 | - (void)addTrack:(LNDanmakuAbstractTrackController *)trackController; 70 | 71 | - (void)removeTrack:(LNDanmakuAbstractTrackController *)trackController; 72 | 73 | - (void)removeAllTracks; 74 | 75 | @end 76 | 77 | 78 | @interface LNDanmakuPlayer (Data) 79 | 80 | - (void)insertAttributes:(NSArray *)attributesArr; 81 | - (void)insertHighPriorityAttributes:(NSArray *)attributesArr; 82 | 83 | - (void)registerLayer:(Class)class forKey:(NSString *)key; 84 | - (CALayer *)layerForKey:(NSString *)key; 85 | 86 | - (void)registerView:(Class)class forKey:(NSString *)key; 87 | - (UIView *)viewForKey:(NSString *)key; 88 | 89 | @end 90 | 91 | 92 | @interface LNDanmakuPlayer (Control) 93 | 94 | - (void)start; 95 | - (void)pause; 96 | - (void)resume; 97 | - (void)stop; 98 | 99 | //Clear all. 100 | - (void)clear; 101 | //Clear queue. 102 | - (void)clearQueue; 103 | //Clear screen/containerView. 104 | - (void)clearScreen; 105 | //Clear pool. 106 | - (void)clearPool; 107 | 108 | - (void)removeAttributes:(LNDanmakuAbstractAttributes *)attributes; 109 | 110 | @end 111 | 112 | @interface LNDanmakuPlayer (TrackGroup) 113 | 114 | //@property (nonatomic, copy, readonly) NSArray *currentTrackGroups; 115 | 116 | - (void)addTrackGroup:(LNDanmakuTrackGroup *)trackGroup; 117 | - (void)removeTrackGroup:(LNDanmakuTrackGroup *)trackGroup; 118 | 119 | - (void)insertAttributes:(NSArray *)attributesArr toGroup:(LNDanmakuTrackGroup *_Nullable)group; 120 | - (void)insertHighPriorityAttributes:(NSArray *)attributesArr toGroup:(LNDanmakuTrackGroup *_Nullable)group; 121 | 122 | 123 | @end 124 | 125 | @interface LNDanmakuPlayer (Recover) 126 | 127 | - (void)recoverLoadAttributes:(NSArray *)attributesArr; 128 | - (void)recoverLoadAttributes:(NSArray *)attributesArr toGroup:(LNDanmakuTrackGroup *_Nullable)group; 129 | 130 | @end 131 | 132 | 133 | 134 | NS_ASSUME_NONNULL_END 135 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/TrackController/Pop/LNDanmakuPopTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuPopTrack.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuPopTrack.h" 10 | 11 | @interface LNDanmakuPopTrack () 12 | 13 | @property (nonatomic, copy) NSArray *defaultPopScaleArrA; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuPopTrack 18 | 19 | - (instancetype)init 20 | { 21 | self = [super init]; 22 | if (self) { 23 | self.A = 1.f; 24 | self.beta = 10.f; 25 | self.w = M_PI/16.f; 26 | self.theta = M_PI_2; 27 | self.strategy = LNDanmakuPopTrackStrategyDynamic; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)resetAttributes:(LNDanmakuAbstractAttributes *)attributes 33 | { 34 | attributes.opacity = 0.f; 35 | } 36 | 37 | - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes 38 | { 39 | if (attributes.opacity == 0.f) { 40 | attributes.opacity = 1.f; 41 | } 42 | 43 | switch (self.strategy) { 44 | case LNDanmakuPopTrackStrategyStatic: { 45 | [self updateScaleWithStatic:attributes]; 46 | } break; 47 | default: { 48 | [self updateScaleWithDynamic:attributes]; 49 | } break; 50 | } 51 | } 52 | 53 | - (void)updateScaleWithStatic:(LNDanmakuAbstractAttributes *)attributes 54 | { 55 | NSInteger t = (NSInteger)(attributes.currentAliveTime * 60.f); 56 | float scale = 1; 57 | if (self.defaultPopScaleArrA.count > t) { 58 | scale = [self.defaultPopScaleArrA[MAX(0, t - 1)] floatValue]/100.f; 59 | } 60 | 61 | CGAffineTransform affine = CGAffineTransformMakeScale(scale, scale); 62 | attributes.transform = CATransform3DMakeAffineTransform(affine); 63 | attributes.opacity = MIN(1.f, t/10.f); 64 | 65 | NSTimeInterval restTime = attributes.totalAliveTime - attributes.currentAliveTime; 66 | if (restTime < 0.8f) { 67 | attributes.opacity = (1.f - (0.8f - restTime)/0.8f); 68 | } 69 | } 70 | 71 | 72 | - (void)updateScaleWithDynamic:(LNDanmakuAbstractAttributes *)attributes 73 | { 74 | NSInteger t = (NSInteger)(attributes.currentAliveTime * 60.f); 75 | CGFloat decay = 1.f/expf(self.beta*(t/60.f)); 76 | float scale = self.A; 77 | if (decay < 0.01) { 78 | scale = self.A; 79 | decay = 0.f; 80 | } else { 81 | scale = ((self.A *sinf(self.w * t - self.theta))*decay + self.A); 82 | } 83 | 84 | CGAffineTransform affine = CGAffineTransformMakeScale(scale, scale); 85 | attributes.transform = CATransform3DMakeAffineTransform(affine); 86 | attributes.opacity = (1.f - decay); 87 | 88 | 89 | NSTimeInterval restTime = attributes.totalAliveTime - attributes.currentAliveTime; 90 | if (restTime < 0.8f) { 91 | attributes.opacity = (1.f - (0.8f - restTime)/0.8f); 92 | } 93 | } 94 | 95 | //振幅 : 1.f , 阻尼 : 10.f ,角速度 M_PI/16.f, 相位: M_PI_2 (向前 90°) 96 | - (NSArray *)defaultPopScaleArrA 97 | { 98 | if (!_defaultPopScaleArrA) { 99 | _defaultPopScaleArrA = @[ 100 | @(0.000000), 101 | @(16.97831), 102 | @(33.80114), 103 | @(49.56881), 104 | @(63.69593), 105 | @(75.85501), 106 | @(85.92186), 107 | @(93.92482), 108 | @(100.0000), 109 | @(104.3530), 110 | @(107.2279), 111 | @(108.8824), 112 | @(109.5696), 113 | @(109.5252), 114 | @(108.9590), 115 | @(108.0507), 116 | @(106.9483), 117 | @(105.7686), 118 | @(104.5997), 119 | @(103.5041), 120 | @(102.5225), 121 | @(101.6776), 122 | @(100.9781), 123 | @(100.4221), 124 | @(100.0000), 125 | @(99.69753), 126 | @(99.49778), 127 | @(99.38282), 128 | @(99.33506), 129 | @(99.33815), 130 | @(99.37749), 131 | @(99.44060), 132 | @(99.51720), 133 | @(99.59917), 134 | @(99.68039), 135 | @(99.75652), 136 | @(99.82472), 137 | @(99.88343), 138 | @(99.93203), 139 | @(99.97067), 140 | @(100.0000), 141 | @(100.0210), 142 | @(100.0348), 143 | @(100.0428), 144 | @(100.0462), 145 | @(100.0459), 146 | @(100.0432), 147 | @(100.0388), 148 | @(100.0335), 149 | @(100.0278), 150 | @(100.0222), 151 | @(100.0169), 152 | @(100.0121), 153 | @(100.0081), 154 | @(100.0047), 155 | @(100.0020), 156 | @(100.0000), 157 | @(99.99854), 158 | @(99.99757), 159 | @(99.99701) 160 | ]; 161 | } 162 | return _defaultPopScaleArrA; 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LNDanmakuMaster 2 | 3 | #### LNDanmakuMaster是一个轻量的弹幕播放器,通过:创建播放器->创建轨道->添加弹幕的方式进行弹幕播放,提供丰富轨道样式的同时也支持自定义轨道;对传入的弹幕的视图层没有要求(任意的View/Layer);提供多种(目前是3)弹幕分布策略;支持使用轨道组播放特殊弹幕;提供与分布策略的对应的弹幕seek策略。 4 | #### 简书链接:[LNDanmakuMaster](https://www.jianshu.com/p/4a5448dd4a60) 5 | 6 | * 你可以直接下载这个链接并运行上面丰富的Demo,或参考Demo代码实现自己的弹幕播放器,也可以直接使用Cocoapods👇 7 | 8 | ### Cocoapods 9 | 10 | ``` 11 | pod 'LNDanmakuMaster' 12 | ``` 13 | 14 | ### 子文档 15 | 16 | [0.iOS端弹幕相关调研](https://www.jianshu.com/p/719573470ffb) 17 | 18 | [1.从一个弹幕开始:LNDanmakuAttributes](https://www.jianshu.com/p/7177086eaf79) 19 | 20 | [2.动画的核心:LNDanmakuTrackController](https://www.jianshu.com/p/6280c3270762) 21 | 22 | [3.暂存和分发:LNDanmakuDispatcher](https://www.jianshu.com/p/d7b534cb2dda) 23 | 24 | [4.弹幕复用:LNDanmakuPool](https://www.jianshu.com/p/182b198efc69) 25 | 26 | [5.弹幕容器和手势:LNDanmakuContainerView](https://www.jianshu.com/p/f23708dc2df2) 27 | 28 | [6.处理条件弹幕:LNDanmakuTrackGroup](https://www.jianshu.com/p/9bd0498d7393) 29 | 30 | [7.封装组件:LNDanmakuPlayer](https://www.jianshu.com/p/c9ce18a64e14) 31 | 32 | ### 弹幕机制 33 | 34 | 1. 驱动机制 35 | 36 | 视频播放器的刷新率通常为29frame/s,弹幕播放器采用同样的刷新频率会有卡顿感,因此弹幕播放器通常使用自己的刷新驱动,要么是UIView animation,要么是CADisplayLink;CADisplaylink支持更多的细节、进度控制,因此这里我们选用的是CADisplayLink。 37 | LNDanmakuClock是这样一个驱动,封装了CADisplayLink,增加了一些暂停、销毁的控制方法,这个时钟每次的输出就是从上一次DisplayLink回调到这次displayLink回调的一小段时间。 38 | 39 | 2. 进度控制机制 40 | 41 | LNDanmakuMaster整体采用时间定义进度,这也是它与其他弹幕框架的主要区别之一,我们的所有进度控制、追赶控制、繁忙度控制,都是使用时间计算的。 42 | 使用时间变量替代空间变量的优势包括: 43 | * 如果你的轨道路线并非直线:如果你希望一个弹幕既可以在水平轨道播放也可以在圆形轨道播放,既要给出角速度,也要给出线速度,如果使用时间单位,只需要给出运行的总时间。 44 | * 如果你先控制一个曲线轨道两条弹幕之间的间距,空间间距需要计算曲线的长度,而对时间间距来说处理起来就和直线一样。 45 | * 在做一些判断时,如果使用空间条件做判断,则需要进行速度、时间相关大量乘除法运算,使用时间可能只需要加减法,虽然我不知道具体是否有优势,但直观上感觉乘除法是没有加减法快的。 46 | * 如果是像B站那种中间出现一列的弹幕,它不需要速度,只需要显示的时间就够了。 47 | 总之,使用时间体系替代速度体系,是能统一多种轨道进度控制的一个好方法。 48 | 49 | 3. 刷新机制 50 | 51 | 根据Clock的输出,我们可以得到一个稳定的回调得知刚刚经过了多长一段时间,那么弹幕的刷新过程就成为:在弹幕的剩余存活时间中扣除刚刚经过的那段时间,并根据扣除后的剩余时间占总时间的百分比来刷新弹幕的位置、大小等信息。 52 | 经过以上三个主要机制的介绍,已经有了实现一个弹幕框架的所有主要逻辑,剩下的就是一些模块的细分和细节上的雕琢。 53 | 54 | ### 模块分工 55 | 56 | LNDanmakuMaster将整个弹幕框架分成以下几个部分(Abstract代表支持重写定制): 57 | 58 | | 模块名称 | 类名 | 备注 | 59 | | ------ | ------ | ------ | 60 | | 播放器 | LNDanmakuPlayer | 播放器相当于对整个弹幕框架其他组件的整合,对外提供调用方法和时机代理 | 61 | | 分发器 | LNDanmakuAbstractDispatcher | Dispatcher类似管理轨道的工头儿,根据轨道集合的状态决定弹幕放到哪里才是最好的 | 62 | | 轨道控制器 | LNDanmakuAbstractTrackController | 轨道控制器类似一个工人,定期使用工具(Track)维护自己的弹幕,并向Dispatcher反馈自己的(繁忙/空闲)状态 | 63 | | 轨道 | LNDanmakuAbstractTrack | Track的职责完全符合轨道的定义,它不维护弹幕,只维护任意一条弹幕弹幕在这个轨道上的刷新的位置、大小、仿射变换等属性与时间进度的映射,像是一个空间信息与时间信息的函数 | 64 | | 样式 | LNDanmakuAbstractAttributes | 样式是弹幕的载体,包含了一条弹幕的所有信息,例如:存活时间、位置、携带的业务模型、展示时使用的View/Layer等等。对,没错,与CollectionViewAttributes十分类似,并根据播放器特性增加了时间戳信息 | 65 | 66 | 额外的组件 67 | 68 | | 模块名称 | 类名 | 备注 | 69 | | ------ | ------ | ------ | 70 | | 轨道组 | LNDanmakuTrackGroup | 这个组件是用来做一些特殊弹幕播放的,是类似一个Player的更小单元(但没有自己的驱动),内部包含了一个Dispatcher和若干TrackController | 71 | 72 | 这个组件的意义: 73 | 某些弹幕播放对轨道有一定的要求,又或是轨道对自己播放的弹幕有一定要求,例如:送礼物的轨道只能出现在屏幕顶端,而不是中央,来避免影响用户观看视频;或者是,圆形的轨道不能那些较长的文字,这样它看起来就不是那么圆了,等等。 74 | 一但从两个方面考虑这个问题,就会陷入:轨道挑选弹幕/弹幕挑选轨道的困境,而实际上两种情况是都存在的,这两个问题最后统一使用轨道组解决,用户指定一个轨道的分组,并可以跨过Player层,直接向这个轨道组抛弹幕,那么这个弹幕就只有可能出现在这个轨道组包含的轨道中;这个功能在Demo中一个彩虹样式的轨道中得以体现,我将七种颜色的弹幕分别抛入七个轨道组中(每个轨道组有三根轨道,两个相邻的轨道组公用中间那个重合的轨道),这样它们就呈现除了一种彩虹的效果。 75 | 76 | ### 使用示例 77 | 78 | 以上介绍完了这个框架的所有重要组件,这里举例介绍构建一个最简单弹幕播放器的过程: 79 | 1.懒加载一个danmakuPlayer: 80 | 这个是起码要做,不需要做任何配置它就可以正常工作 81 | ``` 82 | - (LNDanmakuPlayer *)danmakuPlayer 83 | { 84 | if (!_danmakuPlayer) { 85 | _danmakuPlayer = [[LNDanmakuPlayer alloc] init]; 86 | } 87 | return _danmakuPlayer; 88 | } 89 | ``` 90 | 2.把这个播放器的容器View加到屏幕上: 91 | ``` 92 | [self.view addSubview:self.danmakuPlayer.containerView]; 93 | ``` 94 | 3.给这个Player加一些轨道: 95 | Player支持自定义就是主要体现在自定义轨道和弹幕样式上,所以,所有的轨道都是你亲手加上去的,你可以在init/viewDidLoad等初始化的时机做这个时,也可以在Player懒加载时一并加好 96 | ``` 97 | - (void)addTrack 98 | for (int i = 0; i < 20; i++) { 99 | LNDanmakuHorizontalMoveTrackController *horizontalTrackController = [[LNDanmakuHorizontalMoveTrackController alloc] init]; 100 | horizontalTrackController.horizontalTrack.startPosition = CGPointMake(0, 44.f + 30.f * i); 101 | horizontalTrackController.horizontalTrack.width = self.view.frame.size.width; 102 | horizontalTrackController.spaceTimeInterval = 0.f; 103 | [self.danmakuPlayer addTrack:horizontalTrackController]; 104 | } 105 | } 106 | ``` 107 | 4.让播放器动起来! 108 | 现在这个播放器就可以从外界接口随便一个弹幕并播放在屏幕上了 109 | ``` 110 | - (void)startPlay { 111 | [self.danmakuPlayer start]; 112 | } 113 | ``` 114 | 5.让我们尝试放一个简单的弹幕放上去: 115 | ``` 116 | - (void)addRandomDanmaku 117 | { 118 | LNDanmakuAttributes *attributes = [[LNDanmakuAttributes alloc] init]; 119 | UIView *colorView = [[UIView alloc] init]; 120 | colorView.backgroundColor = [UIColor redColor]; 121 | attributes.presentView = colorView; 122 | attributes.trackTime =4.f; 123 | attributes.size = CGSizeMake(88.f, 44.f); 124 | [_player insertAttributes:@[attributes]]; 125 | } 126 | ``` 127 | 128 | 这个框架尽量使用最符合正常逻辑的方法定义了每个组件的分工来保证它使用起来是最舒服的,并尽可能封装了那些看起来比较复杂的逻辑:分发、刷新、追赶等等;当然,我觉得一个合理的框架应该是下限很低,上限也很高的,而不是一成不变使用规则(正如掌控疾风的某位男子应该算得上是设计得比较成功的角色),所以,当使用者需要深入探讨这些逻辑的时候,也可以从外部轻易定制他们,玩出自己的特色,这个组件也会朝着尽量少的使用规则、尽量朴素的代码、更丰富的功能方向发展。 129 | 130 | ### 最后附上几个Demo中的效果图 131 | 132 | * 横向的轨道 133 | 134 | [![yVlmjO.gif](https://upload-images.jianshu.io/upload_images/8942216-2d01f255d5b61f6e.gif?imageMogr2/auto-orient/strip)](https://imgchr.com/i/yVlmjO) 135 | 136 | * pop动画轨道 137 | 138 | [![yVlegK.gif](https://upload-images.jianshu.io/upload_images/8942216-a6efbf1514bd29a3.gif?imageMogr2/auto-orient/strip)](https://imgchr.com/i/yVlegK) 139 | 140 | * 波浪轨道+轨道分组 141 | 142 | [![yVluuD.gif](https://upload-images.jianshu.io/upload_images/8942216-a5badf677a749f52.gif?imageMogr2/auto-orient/strip)](https://imgchr.com/i/yVluuD) 143 | 144 | * 心形轨道 145 | 146 | [![yVlkNR.gif](https://upload-images.jianshu.io/upload_images/8942216-30f48a64ae7f3365.gif?imageMogr2/auto-orient/strip)](https://imgchr.com/i/yVlkNR) 147 | 148 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/BaseTrackController/LNDanmakuBaseFreeTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuBaseFreeTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseFreeTrackController.h" 10 | 11 | @interface LNDanmakuBaseFreeTrackController () 12 | 13 | @property (nonatomic, strong) NSMutableSet *currentAttributesMSet; 14 | @property (nonatomic, strong) LNDanmakuAbstractAttributes *firstAttributes; 15 | @property (nonatomic, strong) LNDanmakuAbstractAttributes *lastAttributes; 16 | 17 | @end 18 | 19 | @implementation LNDanmakuBaseFreeTrackController 20 | 21 | - (instancetype)init 22 | { 23 | self = [super init]; 24 | if (self) { 25 | self.maxCapacity = 100.f; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)update:(NSTimeInterval)elapsingTime 31 | { 32 | if (elapsingTime < 0.f) { 33 | return; 34 | } 35 | 36 | NSMutableSet *shouldUnloadAttributes = [[NSMutableSet alloc] init]; 37 | for (LNDanmakuAbstractAttributes *attributes in self.currentAttributesMSet) { 38 | attributes.currentAliveTime += elapsingTime; 39 | NSTimeInterval restAliveTime = attributes.totalAliveTime - attributes.currentAliveTime; 40 | if (restAliveTime > self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) { 41 | self.lastAttributes = attributes; 42 | } 43 | 44 | if (!self.firstAttributes) { 45 | self.firstAttributes = attributes; 46 | } else if (restAliveTime < self.firstAttributes.totalAliveTime - self.firstAttributes.currentAliveTime) { 47 | self.firstAttributes = attributes; 48 | } 49 | 50 | if (restAliveTime <= 0) { 51 | [shouldUnloadAttributes addObject:attributes]; 52 | } else { 53 | [self.track updateAttributes:attributes]; 54 | } 55 | } 56 | 57 | for (LNDanmakuAbstractAttributes *attributes in shouldUnloadAttributes) { 58 | [self unloadAttributes:attributes]; 59 | } 60 | } 61 | 62 | - (void)clearTrack 63 | { 64 | NSSet *attributesArr = [NSSet setWithSet:self.currentAttributesMSet]; 65 | for (LNDanmakuAbstractAttributes *attributes in attributesArr) { 66 | [self unloadAttributes:attributes]; 67 | } 68 | } 69 | 70 | - (BOOL)isFree 71 | { 72 | if (self.currentAttributesMSet.count < self.maxCapacity) { 73 | return YES; 74 | } 75 | return NO; 76 | } 77 | 78 | - (BOOL)containAttributes:(LNDanmakuAbstractAttributes *)attributes 79 | { 80 | if ([self.currentAttributesMSet containsObject:attributes]) { 81 | return YES; 82 | } 83 | return NO; 84 | } 85 | 86 | - (NSTimeInterval)estimatedFinishTimeForCurrentAttributes 87 | { 88 | if (self.lastAttributes) { 89 | return self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime; 90 | } else { 91 | return 0.f; 92 | } 93 | } 94 | 95 | - (NSTimeInterval)estimatedMinDisplayWaitTimeFor:(LNDanmakuAbstractAttributes *)attributes 96 | { 97 | if (self.currentAttributesMSet.count < self.maxCapacity) { 98 | return 0.f; 99 | } else if (self.firstAttributes) { 100 | return self.firstAttributes.totalAliveTime - self.firstAttributes.currentAliveTime; 101 | } else { 102 | return CGFLOAT_MAX; 103 | } 104 | } 105 | 106 | //recover 107 | - (NSTimeInterval)estimatedMaxAliveTimeFor:(LNDanmakuAbstractAttributes *)attributes 108 | { 109 | if (self.isFree) { 110 | return attributes.totalAliveTime; 111 | } else { 112 | return -1.f; 113 | } 114 | } 115 | 116 | - (void)recoverLoadAttributes:(LNDanmakuAbstractAttributes *)attributes 117 | { 118 | if (![self isFree]) { 119 | return; 120 | } 121 | 122 | if ([self containAttributes:attributes]) { 123 | return; 124 | } 125 | 126 | [super recoverLoadAttributes:attributes]; 127 | //TODO 实现 128 | NSTimeInterval maxAliveTime = [self estimatedMaxAliveTimeFor:attributes]; 129 | if (maxAliveTime > 0.f) { 130 | [self.currentAttributesMSet addObject:attributes]; 131 | [self.viewDelegate viewShouldLoadAttributes:attributes]; 132 | [self.track updateAttributes:attributes]; 133 | NSTimeInterval restAliveTime = attributes.totalAliveTime - attributes.currentAliveTime; 134 | if (restAliveTime > self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) { 135 | self.lastAttributes = attributes; 136 | } 137 | } else { 138 | return; 139 | } 140 | } 141 | 142 | //recover 143 | 144 | - (void)loadAttributes:(LNDanmakuAbstractAttributes *)attributes 145 | { 146 | if (![self isFree]) { 147 | return; 148 | } 149 | 150 | if ([self containAttributes:attributes]) { 151 | return; 152 | } 153 | 154 | [self.track resetAttributes:attributes]; 155 | [super loadAttributes:attributes]; 156 | 157 | [self.currentAttributesMSet addObject:attributes]; 158 | [self.viewDelegate viewShouldLoadAttributes:attributes]; 159 | } 160 | 161 | - (void)unloadAttributes:(LNDanmakuAbstractAttributes *)attributes 162 | { 163 | if ([self containAttributes:attributes]) { 164 | [super unloadAttributes:attributes]; 165 | [self.viewDelegate viewShouldRemoveAttributes:attributes]; 166 | [self.currentAttributesMSet removeObject:attributes]; 167 | } 168 | 169 | if (attributes == self.lastAttributes) { 170 | self.lastAttributes = nil; 171 | } 172 | 173 | if (attributes == self.firstAttributes) { 174 | self.firstAttributes = nil; 175 | } 176 | } 177 | 178 | - (NSMutableSet *)currentAttributesMSet 179 | { 180 | if (!_currentAttributesMSet) { 181 | _currentAttributesMSet = [[NSMutableSet alloc] init]; 182 | } 183 | return _currentAttributesMSet; 184 | } 185 | 186 | @end 187 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Dispatcher/LNDanmakuDispatchQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuDispatchQueue.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuDispatchQueue.h" 10 | 11 | @interface LNDanmakuDispatchSubQueue : NSObject 12 | 13 | @property (nonatomic, strong) NSMutableOrderedSet *mSet; 14 | 15 | @end 16 | 17 | @implementation LNDanmakuDispatchSubQueue 18 | 19 | - (instancetype)init 20 | { 21 | self = [super init]; 22 | if (self) { 23 | 24 | } 25 | return self; 26 | } 27 | 28 | - (void)push:(LNDanmakuAbstractAttributes *)attributes 29 | { 30 | if (!attributes) { 31 | return; 32 | } 33 | [self.mSet addObject:attributes]; 34 | } 35 | 36 | - (LNDanmakuAbstractAttributes *)top 37 | { 38 | if (self.isEmpty) { 39 | return nil; 40 | } 41 | LNDanmakuAbstractAttributes *attributes = [self.mSet objectAtIndex:0]; 42 | return attributes; 43 | } 44 | 45 | - (LNDanmakuAbstractAttributes *)pop 46 | { 47 | if (self.isEmpty) { 48 | return nil; 49 | } 50 | LNDanmakuAbstractAttributes *attributes = [self.mSet objectAtIndex:0]; 51 | [self.mSet removeObject:attributes]; 52 | return attributes; 53 | } 54 | 55 | - (NSArray *)clearQueue 56 | { 57 | NSArray *resultArray = [self.mSet.array copy]; 58 | [self.mSet removeAllObjects]; 59 | return resultArray; 60 | } 61 | 62 | - (BOOL)contains:(LNDanmakuAbstractAttributes *)attributes 63 | { 64 | if ([self.mSet containsObject:attributes]) { 65 | return YES; 66 | } 67 | return NO; 68 | } 69 | 70 | - (void)remove:(LNDanmakuAbstractAttributes *)attributes 71 | { 72 | [self.mSet removeObject:attributes]; 73 | } 74 | 75 | - (BOOL)isEmpty 76 | { 77 | if (self.mSet.count > 0) { 78 | return NO; 79 | } 80 | return YES; 81 | } 82 | 83 | - (NSInteger)length 84 | { 85 | return self.mSet.count; 86 | } 87 | 88 | - (NSMutableOrderedSet *)mSet 89 | { 90 | if (!_mSet) { 91 | _mSet = [[NSMutableOrderedSet alloc] init]; 92 | } 93 | return _mSet; 94 | } 95 | 96 | @end 97 | 98 | 99 | @interface LNDanmakuDispatchQueue () 100 | 101 | @property (nonatomic, strong) LNDanmakuDispatchSubQueue *highQueue; 102 | @property (nonatomic, strong) LNDanmakuDispatchSubQueue *defaultQueue; 103 | 104 | @end 105 | 106 | @implementation LNDanmakuDispatchQueue 107 | 108 | - (instancetype)init 109 | { 110 | self = [super init]; 111 | if (self) { 112 | self.maxCapacity = 60; 113 | } 114 | return self; 115 | } 116 | 117 | - (void)setMaxCapacity:(NSInteger)maxCapacity 118 | { 119 | if (maxCapacity > 0) { 120 | _maxCapacity = maxCapacity; 121 | } 122 | } 123 | 124 | - (void)push:(LNDanmakuAbstractAttributes *)attributes 125 | { 126 | if (!attributes) { 127 | return; 128 | } 129 | [self push:attributes priority:LNDanmakuDispatchQueuePriorityDefault]; 130 | } 131 | 132 | - (void)push:(LNDanmakuAbstractAttributes *)attributes priority:(LNDanmakuDispatchQueuePriority)priority 133 | { 134 | if (!attributes) { 135 | return; 136 | } 137 | switch (priority) { 138 | case LNDanmakuDispatchQueuePriorityHigh: { 139 | [self.highQueue push:attributes]; 140 | while ([self.highQueue length] > self.maxCapacity) { 141 | LNDanmakuAbstractAttributes *droppedAttributes = [self.highQueue pop]; 142 | if (self.delegate && [self.delegate respondsToSelector:@selector(danmakuDispatchQueueDidDrop:)]) { 143 | [self.delegate danmakuDispatchQueueDidDrop:droppedAttributes]; 144 | } 145 | } 146 | } break; 147 | default: { 148 | [self.defaultQueue push:attributes]; 149 | while ([self.defaultQueue length] > self.maxCapacity) { 150 | LNDanmakuAbstractAttributes *droppedAttributes = [self.defaultQueue pop]; 151 | if (self.delegate && [self.delegate respondsToSelector:@selector(danmakuDispatchQueueDidDrop:)]) { 152 | [self.delegate danmakuDispatchQueueDidDrop:droppedAttributes]; 153 | } 154 | } 155 | } break; 156 | } 157 | } 158 | 159 | - (LNDanmakuAbstractAttributes *)top 160 | { 161 | if (![self.highQueue isEmpty]) { 162 | return [self.highQueue top]; 163 | } 164 | return [self.defaultQueue top]; 165 | } 166 | 167 | - (LNDanmakuAbstractAttributes *)pop 168 | { 169 | if ([self.highQueue top]) { 170 | return [self.highQueue pop]; 171 | } 172 | return [self.defaultQueue pop]; 173 | } 174 | 175 | - (NSArray *)clearQueue 176 | { 177 | NSMutableArray *clearedAttributes = [[NSMutableArray alloc] init]; 178 | [clearedAttributes addObjectsFromArray:[self.highQueue clearQueue]]; 179 | [clearedAttributes addObjectsFromArray:[self.defaultQueue clearQueue]]; 180 | return [NSArray arrayWithArray:clearedAttributes]; 181 | } 182 | 183 | - (BOOL)contains:(LNDanmakuAbstractAttributes *)attributes 184 | { 185 | if ([self.defaultQueue contains:attributes] || [self.highQueue contains:attributes]) { 186 | return YES; 187 | } 188 | return NO; 189 | } 190 | 191 | - (void)remove:(LNDanmakuAbstractAttributes *)attributes 192 | { 193 | if ([self.defaultQueue contains:attributes]) { 194 | [self.defaultQueue remove:attributes]; 195 | } 196 | if ([self.highQueue contains:attributes]) { 197 | [self.highQueue remove:attributes]; 198 | } 199 | } 200 | 201 | - (BOOL)isEmpty 202 | { 203 | return [self.highQueue isEmpty] && [self.defaultQueue isEmpty]; 204 | } 205 | 206 | - (LNDanmakuDispatchSubQueue *)highQueue 207 | { 208 | if (!_highQueue) { 209 | _highQueue = [[LNDanmakuDispatchSubQueue alloc] init]; 210 | } 211 | return _highQueue; 212 | } 213 | 214 | - (LNDanmakuDispatchSubQueue *)defaultQueue 215 | { 216 | if (!_defaultQueue) { 217 | _defaultQueue = [[LNDanmakuDispatchSubQueue alloc] init]; 218 | } 219 | return _defaultQueue; 220 | } 221 | 222 | @end 223 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/TracksDemo/TracksDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TracksDemoViewController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "TracksDemoViewController.h" 10 | 11 | #import "LNDanmakuMaster.h" 12 | 13 | @interface TracksDemoViewController () 14 | 15 | @property (nonatomic, strong) LNDanmakuPlayer* danmakuPlayer; 16 | 17 | @property (nonatomic, strong) NSTimer *timer; 18 | 19 | @property (nonatomic, assign) TracksDemoViewControllerType type; 20 | 21 | @end 22 | 23 | @implementation TracksDemoViewController 24 | 25 | - (instancetype)initWithType:(TracksDemoViewControllerType)type 26 | { 27 | self = [super init]; 28 | if (self) { 29 | self.type = type; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | 37 | [self setupPlayer]; 38 | [self addSubviews]; 39 | [self addConstraints]; 40 | 41 | _timer = [NSTimer timerWithTimeInterval:0.128f target:self selector:@selector(addRandomDanmaku) userInfo:nil repeats:YES]; 42 | 43 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 44 | } 45 | 46 | - (void)viewDidDisappear:(BOOL)animated 47 | { 48 | [super viewDidDisappear:animated]; 49 | if (_timer) { 50 | [_timer invalidate]; 51 | _timer = nil; 52 | } 53 | } 54 | 55 | - (void)dealloc 56 | { 57 | NSLog(@"释放了"); 58 | } 59 | 60 | - (void)addRandomDanmaku 61 | { 62 | LNDanmakuLayer *danmakuLayer = [[LNDanmakuLayer alloc] init]; 63 | danmakuLayer.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:1.f].CGColor; 64 | danmakuLayer.cornerRadius = 12.f; 65 | LNDanmakuAttributes *attributes = [[LNDanmakuAttributes alloc] init]; 66 | attributes.presentLayer = danmakuLayer; 67 | attributes.size = CGSizeMake((rand()%100)/100.f * 200.f + 22.f, 24.f); 68 | // attributes.danmakuTime = 1.f; 69 | // attributes.trackTime = 1.f + ((rand()%100)/100.f)*3.f; 70 | 71 | float totalTime = 5.f; 72 | attributes.danmakuTime = (attributes.size.width/(attributes.size.width + self.view.bounds.size.width)) * totalTime; 73 | attributes.trackTime = (self.view.bounds.size.width/(attributes.size.width + self.view.bounds.size.width)) * totalTime; 74 | 75 | //特殊处理 76 | switch (self.type) { 77 | case TracksDemoViewControllerCircle: 78 | case TracksDemoViewControllerChristina: 79 | case TracksDemoViewControllerSin: 80 | { 81 | //曲线适合比较短的弹幕 82 | attributes.size = CGSizeMake(44.f, 24.f); 83 | } break; 84 | case TracksDemoViewControllerPop: { 85 | //pop轨道需要设定出现位置 86 | attributes.position = CGPointMake((rand()%100)/100.f * self.view.bounds.size.width, (rand()%100)/100.f * self.view.bounds.size.height); 87 | } break; 88 | default: 89 | break; 90 | } 91 | 92 | 93 | 94 | [self.danmakuPlayer insertAttributes:@[attributes]]; 95 | } 96 | 97 | - (void)addSubviews 98 | { 99 | [self.view addSubview:self.danmakuPlayer.containerView]; 100 | } 101 | 102 | - (void)addConstraints 103 | { 104 | self.danmakuPlayer.containerView.frame = self.view.bounds; 105 | } 106 | 107 | - (void)setupPlayer 108 | { 109 | switch (self.type) { 110 | case TracksDemoViewControllerStable: { 111 | for (int i = 0; i < 20; i++) { 112 | LNDanmakuStableTrackController *stableTrackController = [[LNDanmakuStableTrackController alloc] init]; 113 | stableTrackController.stableTrack.position = CGPointMake(self.view.bounds.size.width/2.f, 44.f + 30.f * i); 114 | [self.danmakuPlayer addTrack:stableTrackController]; 115 | } 116 | } break; 117 | case TracksDemoViewControllerCircle: { 118 | for (int i = 0; i < 4; i++) { 119 | LNDanmakuCircleTrackController *circleTrackController = [[LNDanmakuCircleTrackController alloc] init]; 120 | circleTrackController.circleTrack.center = CGPointMake(self.view.bounds.size.width/2.f, self.view.bounds.size.height/2.f); 121 | circleTrackController.circleTrack.radius = (self.view.bounds.size.width/2.f - 30*i); 122 | [self.danmakuPlayer addTrack:circleTrackController]; 123 | if (circleTrackController.circleTrack.radius < 50.f) { 124 | break; 125 | } 126 | } 127 | } break; 128 | case TracksDemoViewControllerChristina: { 129 | for (int i = 0; i < 4; i++) { 130 | LNDanmakuChristinaTrackController *christinaTrackController = [[LNDanmakuChristinaTrackController alloc] init]; 131 | christinaTrackController.christinaTrack.center = CGPointMake(self.view.bounds.size.width/2.f, self.view.bounds.size.height/2.f); 132 | christinaTrackController.christinaTrack.a = (self.view.bounds.size.width/2.f - 30*i); 133 | [self.danmakuPlayer addTrack:christinaTrackController]; 134 | if (christinaTrackController.christinaTrack.a < 50.f) { 135 | break; 136 | } 137 | } 138 | } break; 139 | case TracksDemoViewControllerPop: { 140 | LNDanmakuPopTrackController *popTrackController = [[LNDanmakuPopTrackController alloc] init]; 141 | 142 | [self.danmakuPlayer addTrack:popTrackController]; 143 | popTrackController.popTrack.strategy = LNDanmakuPopTrackStrategyStatic; 144 | } break; 145 | case TracksDemoViewControllerSin: { 146 | //horizontal 147 | for (int i = 0; i < 4; i++) { 148 | LNDanmakuSinTrackController *sinTrackController = [[LNDanmakuSinTrackController alloc] init]; 149 | sinTrackController.sinTrack.startPosition = CGPointMake(0, i*20.f + 132.f); 150 | sinTrackController.sinTrack.width = self.view.frame.size.width; 151 | sinTrackController.sinTrack.A = 80.f; 152 | sinTrackController.sinTrack.T = 1.f; 153 | sinTrackController.spaceTimeInterval = 0.f; 154 | [self.danmakuPlayer addTrack:sinTrackController]; 155 | } 156 | } break; 157 | default:{ 158 | //horizontal 159 | for (int i = 0; i < 20; i++) { 160 | LNDanmakuHorizontalMoveTrackController *horizontalTrackController = [[LNDanmakuHorizontalMoveTrackController alloc] init]; 161 | horizontalTrackController.horizontalTrack.startPosition = CGPointMake(0, 44.f + 30.f * i); 162 | horizontalTrackController.horizontalTrack.width = self.view.frame.size.width; 163 | horizontalTrackController.spaceTimeInterval = 0.f; 164 | [self.danmakuPlayer addTrack:horizontalTrackController]; 165 | } 166 | } break; 167 | } 168 | 169 | [self.danmakuPlayer start]; 170 | } 171 | 172 | - (LNDanmakuPlayer *)danmakuPlayer 173 | { 174 | if (!_danmakuPlayer) { 175 | _danmakuPlayer = [[LNDanmakuPlayer alloc] init]; 176 | _danmakuPlayer.delegate = self; 177 | } 178 | return _danmakuPlayer; 179 | } 180 | 181 | @end 182 | 183 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Main/MainViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "MainViewController.h" 10 | #import "DemoItemCollectionViewCell.h" 11 | #import "DemoItemObj.h" 12 | #import "TracksDemoViewController.h" 13 | #import "DispatcherDemoViewController.h" 14 | #import "GestureDemoViewController.h" 15 | #import "TrackGroupDemoViewController.h" 16 | #import "VideoDanmakuDemoViewController.h" 17 | 18 | #import "LNDanmakuMaster.h" 19 | 20 | @interface MainViewController () 21 | 22 | @property (nonatomic, copy) NSArray *demoList; 23 | 24 | @property (nonatomic, strong) UICollectionView *collectionView; 25 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout; 26 | 27 | @end 28 | 29 | @implementation MainViewController 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | 34 | 35 | [self loadDemoList]; 36 | 37 | [self addSubviews]; 38 | [self addConstraints]; 39 | // Do any additional setup after loading the view. 40 | } 41 | 42 | - (void)viewWillAppear:(BOOL)animated 43 | { 44 | [super viewWillAppear:animated]; 45 | self.navigationController.navigationBar.translucent = NO; 46 | } 47 | 48 | - (void)viewDidAppear:(BOOL)animated 49 | { 50 | [super viewDidAppear:animated]; 51 | } 52 | 53 | - (void)addSubviews 54 | { 55 | [self.view addSubview:self.collectionView]; 56 | } 57 | 58 | - (void)addConstraints 59 | { 60 | self.collectionView.frame = self.view.bounds; 61 | } 62 | 63 | - (void)loadDemoList 64 | { 65 | NSMutableArray *listMArr = [[NSMutableArray alloc] init]; 66 | 67 | for (NSInteger i = 0; i < DemoTypeCount; i++) { 68 | DemoItemObj *horizontalItem = [[DemoItemObj alloc] init]; 69 | horizontalItem.type = i; 70 | [listMArr addObject:horizontalItem]; 71 | } 72 | 73 | self.demoList = [listMArr copy]; 74 | } 75 | 76 | - (UICollectionView *)collectionView 77 | { 78 | if (!_collectionView) { 79 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout]; 80 | _collectionView.delegate = self; 81 | _collectionView.dataSource = self; 82 | _collectionView.backgroundColor = [UIColor whiteColor]; 83 | [_collectionView registerClass:DemoItemCollectionViewCell.class forCellWithReuseIdentifier:kDemoItemCollectionViewCell]; 84 | 85 | } 86 | return _collectionView; 87 | } 88 | 89 | - (UICollectionViewFlowLayout *)flowLayout 90 | { 91 | if (!_flowLayout) { 92 | _flowLayout = [[UICollectionViewFlowLayout alloc] init]; 93 | _flowLayout.sectionInset = UIEdgeInsetsMake(20.f, 20.f, 20.f, 20.f); 94 | } 95 | return _flowLayout; 96 | } 97 | 98 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 99 | { 100 | return 1; 101 | } 102 | 103 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 104 | { 105 | return self.demoList.count; 106 | } 107 | 108 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 109 | { 110 | return CGSizeMake(100.f, 44.f); 111 | } 112 | 113 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 114 | { 115 | DemoItemCollectionViewCell *cell = (DemoItemCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kDemoItemCollectionViewCell forIndexPath:indexPath]; 116 | [cell setItemObj:self.demoList[indexPath.row]]; 117 | return cell; 118 | } 119 | 120 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 121 | { 122 | [collectionView deselectItemAtIndexPath:indexPath animated:NO]; 123 | switch (self.demoList[indexPath.row].type) { 124 | //track 125 | case DemoTypeStable: { 126 | TracksDemoViewController *stableController = [[TracksDemoViewController alloc] initWithType:TracksDemoViewControllerStable]; 127 | [self.navigationController pushViewController:stableController animated:YES]; 128 | } break; 129 | case DemoTypeCircle: { 130 | TracksDemoViewController *circleController = [[TracksDemoViewController alloc] initWithType:TracksDemoViewControllerCircle]; 131 | [self.navigationController pushViewController:circleController animated:YES]; 132 | } break; 133 | case DemoTypeChristina: { 134 | TracksDemoViewController *christinaController = [[TracksDemoViewController alloc] initWithType:TracksDemoViewControllerChristina]; 135 | [self.navigationController pushViewController:christinaController animated:YES]; 136 | } break; 137 | case DemoTypePop: { 138 | TracksDemoViewController *popController = [[TracksDemoViewController alloc] initWithType:TracksDemoViewControllerPop]; 139 | [self.navigationController pushViewController:popController animated:YES]; 140 | } break; 141 | case DemoTypeSin: { 142 | TracksDemoViewController *sinController = [[TracksDemoViewController alloc] initWithType:TracksDemoViewControllerSin]; 143 | [self.navigationController pushViewController:sinController animated:YES]; 144 | } break; 145 | 146 | //dispatcher 147 | case DemoTypeDispatcherDefault: { 148 | DispatcherDemoViewController *defaultController = [[DispatcherDemoViewController alloc] initWithType:DispatcherDemoViewControllerDefault]; 149 | [self.navigationController pushViewController:defaultController animated:YES]; 150 | } break; 151 | case DemoTypeDispatcherLowDensity: { 152 | DispatcherDemoViewController *lowDensityController = [[DispatcherDemoViewController alloc] initWithType:DispatcherDemoViewControllerLowDensity]; 153 | [self.navigationController pushViewController:lowDensityController animated:YES]; 154 | } break; 155 | case DemoTypeDispatcherMostFast: { 156 | DispatcherDemoViewController *mostFastController = [[DispatcherDemoViewController alloc] initWithType:DispatcherDemoViewControllerMostFast]; 157 | [self.navigationController pushViewController:mostFastController animated:YES]; 158 | } break; 159 | 160 | //gesture 161 | case DemoTypeGesture: { 162 | GestureDemoViewController *gestureController = [[GestureDemoViewController alloc] init]; 163 | [self.navigationController pushViewController:gestureController animated:YES]; 164 | } break; 165 | 166 | //trackGroup 167 | case DemoTypeTrackGroup: { 168 | TrackGroupDemoViewController *trackGroupController = [[TrackGroupDemoViewController alloc] init]; 169 | [self.navigationController pushViewController:trackGroupController animated:YES]; 170 | } break; 171 | 172 | case DemoTypeVideoDanmaku: { 173 | VideoDanmakuDemoViewController *videoDanmakuController = [[VideoDanmakuDemoViewController alloc] init]; 174 | [self.navigationController pushViewController:videoDanmakuController animated:YES]; 175 | } break; 176 | 177 | default: { 178 | TracksDemoViewController *horizontalController = [[TracksDemoViewController alloc] init]; 179 | [self.navigationController pushViewController:horizontalController animated:YES]; 180 | } break; 181 | } 182 | } 183 | 184 | @end 185 | 186 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/Demo/TrackGroupDemo/TrackGroupDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TrackGroupDemoViewController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "TrackGroupDemoViewController.h" 10 | #import "LNDanmakuMaster.h" 11 | 12 | @interface TrackGroupDemoViewController () 13 | 14 | @property (nonatomic, strong) LNDanmakuPlayer* danmakuPlayer; 15 | 16 | @property (nonatomic, strong) NSTimer *timer; 17 | 18 | @property (nonatomic, strong) LNDanmakuTrackGroup *redGroup; 19 | @property (nonatomic, strong) LNDanmakuTrackGroup *orangeGroup; 20 | @property (nonatomic, strong) LNDanmakuTrackGroup *yellowGroup; 21 | @property (nonatomic, strong) LNDanmakuTrackGroup *greenGroup; 22 | @property (nonatomic, strong) LNDanmakuTrackGroup *cyanGroup; 23 | @property (nonatomic, strong) LNDanmakuTrackGroup *blueGroup; 24 | @property (nonatomic, strong) LNDanmakuTrackGroup *purpleGroup; 25 | 26 | @end 27 | 28 | @implementation TrackGroupDemoViewController 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | 33 | [self setupPlayer]; 34 | [self addSubviews]; 35 | [self addConstraints]; 36 | 37 | _timer = [NSTimer timerWithTimeInterval:0.032f target:self selector:@selector(addRandomDanmaku) userInfo:nil repeats:YES]; 38 | 39 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 40 | } 41 | 42 | - (void)viewDidDisappear:(BOOL)animated 43 | { 44 | [super viewDidDisappear:animated]; 45 | if (_timer) { 46 | [_timer invalidate]; 47 | _timer = nil; 48 | } 49 | } 50 | 51 | - (void)dealloc 52 | { 53 | NSLog(@"释放了"); 54 | } 55 | 56 | - (void)addRandomDanmaku 57 | { 58 | LNDanmakuAttributes *attributes = [[LNDanmakuAttributes alloc] init]; 59 | LNDanmakuLayer *danmakuLayer = [[LNDanmakuLayer alloc] init]; 60 | 61 | danmakuLayer.cornerRadius = 12.f; 62 | attributes.presentLayer = danmakuLayer; 63 | 64 | attributes.size = CGSizeMake(44.f, 24.f); 65 | attributes.trackTime = 4.f + ((rand()%100)/100.f)*4.f; 66 | attributes.danmakuTime = (attributes.size.width/self.view.bounds.size.width) * attributes.trackTime; 67 | 68 | switch (random()%7) { 69 | case 0: { 70 | danmakuLayer.backgroundColor = [UIColor colorWithRed:255.f/255.f green:0.f blue:0.f alpha:0.5f].CGColor; 71 | [self.danmakuPlayer insertAttributes:@[attributes] toGroup:self.redGroup]; 72 | } break; 73 | case 1: { 74 | danmakuLayer.backgroundColor = [UIColor colorWithRed:255.f/255.f green:165.f/255.f blue:0.f alpha:0.5f].CGColor; 75 | [self.danmakuPlayer insertAttributes:@[attributes] toGroup:self.orangeGroup]; 76 | } break; 77 | case 2: { 78 | danmakuLayer.backgroundColor = [UIColor colorWithRed:255.f/255.f green:255.f/255.f blue:0.f alpha:0.5f].CGColor; 79 | [self.danmakuPlayer insertAttributes:@[attributes] toGroup:self.yellowGroup]; 80 | } break; 81 | case 3: { 82 | danmakuLayer.backgroundColor = [UIColor colorWithRed:0.f/255.f green:255.f/255.f blue:0.f alpha:0.5f].CGColor; 83 | [self.danmakuPlayer insertAttributes:@[attributes] toGroup:self.greenGroup]; 84 | } break; 85 | case 4: { 86 | danmakuLayer.backgroundColor = [UIColor colorWithRed:0.f/255.f green:127.f/255.f blue:255.f/255.f alpha:0.5f].CGColor; 87 | [self.danmakuPlayer insertAttributes:@[attributes] toGroup:self.blueGroup]; 88 | } break; 89 | case 5: { 90 | danmakuLayer.backgroundColor = [UIColor colorWithRed:0.f/255.f green:0.f/255.f blue:255.f/255.f alpha:0.5f].CGColor; 91 | [self.danmakuPlayer insertAttributes:@[attributes] toGroup:self.cyanGroup]; 92 | } break; 93 | case 6: { 94 | danmakuLayer.backgroundColor = [UIColor colorWithRed:139.f/255.f green:0.f/255.f blue:255.f/255.f alpha:0.5f].CGColor; 95 | [self.danmakuPlayer insertAttributes:@[attributes] toGroup:self.purpleGroup]; 96 | } break; 97 | default: 98 | break; 99 | } 100 | } 101 | 102 | - (void)addSubviews 103 | { 104 | [self.view addSubview:self.danmakuPlayer.containerView]; 105 | } 106 | 107 | - (void)addConstraints 108 | { 109 | self.danmakuPlayer.containerView.frame = self.view.bounds; 110 | } 111 | 112 | - (void)setupPlayer 113 | { 114 | NSInteger times = 2; 115 | for (int i = 0; i < 13 * times; i++) { 116 | LNDanmakuSinTrackController *sinTrackController = [[LNDanmakuSinTrackController alloc] init]; 117 | sinTrackController.sinTrack.startPosition = CGPointMake(0, i*10.f + 100.f); 118 | sinTrackController.sinTrack.width = self.view.frame.size.width; 119 | sinTrackController.sinTrack.A = 80.f; 120 | sinTrackController.sinTrack.T = 1.f; 121 | sinTrackController.spaceTimeInterval = 0.f; 122 | if (i < 1 * times) { 123 | [self.redGroup addTrack:sinTrackController]; 124 | } else if (i < 2 * times) { 125 | [self.redGroup addTrack:sinTrackController]; 126 | [self.orangeGroup addTrack:sinTrackController]; 127 | } else if (i < 3 * times) { 128 | [self.orangeGroup addTrack:sinTrackController]; 129 | } else if (i < 4 * times) { 130 | [self.orangeGroup addTrack:sinTrackController]; 131 | [self.yellowGroup addTrack:sinTrackController]; 132 | } else if (i < 5 * times) { 133 | [self.yellowGroup addTrack:sinTrackController]; 134 | } else if (i < 6 * times) { 135 | [self.yellowGroup addTrack:sinTrackController]; 136 | [self.greenGroup addTrack:sinTrackController]; 137 | } else if (i < 7 * times) { 138 | [self.greenGroup addTrack:sinTrackController]; 139 | } else if (i < 8 * times) { 140 | [self.greenGroup addTrack:sinTrackController]; 141 | [self.cyanGroup addTrack:sinTrackController]; 142 | } else if (i < 9 * times) { 143 | [self.cyanGroup addTrack:sinTrackController]; 144 | } else if (i < 10 * times) { 145 | [self.cyanGroup addTrack:sinTrackController]; 146 | [self.blueGroup addTrack:sinTrackController]; 147 | } else if (i < 11 * times) { 148 | [self.blueGroup addTrack:sinTrackController]; 149 | } else if (i < 12 * times) { 150 | [self.blueGroup addTrack:sinTrackController]; 151 | [self.purpleGroup addTrack:sinTrackController]; 152 | } else { 153 | [self.purpleGroup addTrack:sinTrackController]; 154 | } 155 | } 156 | 157 | [self.danmakuPlayer addTrackGroup:self.redGroup]; 158 | [self.danmakuPlayer addTrackGroup:self.orangeGroup]; 159 | [self.danmakuPlayer addTrackGroup:self.yellowGroup]; 160 | [self.danmakuPlayer addTrackGroup:self.greenGroup]; 161 | [self.danmakuPlayer addTrackGroup:self.cyanGroup]; 162 | [self.danmakuPlayer addTrackGroup:self.blueGroup]; 163 | [self.danmakuPlayer addTrackGroup:self.purpleGroup]; 164 | 165 | [self.danmakuPlayer start]; 166 | } 167 | 168 | - (LNDanmakuPlayer *)danmakuPlayer 169 | { 170 | if (!_danmakuPlayer) { 171 | _danmakuPlayer = [[LNDanmakuPlayer alloc] init]; 172 | } 173 | return _danmakuPlayer; 174 | } 175 | 176 | - (LNDanmakuTrackGroup *)redGroup 177 | { 178 | if (!_redGroup) { 179 | _redGroup = [[LNDanmakuTrackGroup alloc] init]; 180 | } 181 | return _redGroup; 182 | } 183 | 184 | - (LNDanmakuTrackGroup *)orangeGroup 185 | { 186 | if (!_orangeGroup) { 187 | _orangeGroup = [[LNDanmakuTrackGroup alloc] init]; 188 | } 189 | return _orangeGroup; 190 | } 191 | 192 | 193 | - (LNDanmakuTrackGroup *)yellowGroup 194 | { 195 | if (!_yellowGroup) { 196 | _yellowGroup = [[LNDanmakuTrackGroup alloc] init]; 197 | } 198 | return _yellowGroup; 199 | } 200 | 201 | - (LNDanmakuTrackGroup *)greenGroup 202 | { 203 | if (!_greenGroup) { 204 | _greenGroup = [[LNDanmakuTrackGroup alloc] init]; 205 | } 206 | return _greenGroup; 207 | } 208 | 209 | - (LNDanmakuTrackGroup *)blueGroup 210 | { 211 | if (!_blueGroup) { 212 | _blueGroup = [[LNDanmakuTrackGroup alloc] init]; 213 | } 214 | return _blueGroup; 215 | } 216 | 217 | - (LNDanmakuTrackGroup *)cyanGroup 218 | { 219 | if (!_cyanGroup) { 220 | _cyanGroup = [[LNDanmakuTrackGroup alloc] init]; 221 | } 222 | return _cyanGroup; 223 | } 224 | 225 | - (LNDanmakuTrackGroup *)purpleGroup 226 | { 227 | if (!_purpleGroup) { 228 | _purpleGroup = [[LNDanmakuTrackGroup alloc] init]; 229 | } 230 | return _purpleGroup; 231 | } 232 | @end 233 | 234 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Track/BaseTrackController/LNDanmakuBaseMoveTrackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuBaseMoveTrackController.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuBaseMoveTrackController.h" 10 | 11 | @interface LNDanmakuBaseMoveTrackController () 12 | 13 | @property (nonatomic, strong) NSMutableSet *currentAttributesMSet; 14 | @property (nonatomic, strong) LNDanmakuAbstractAttributes *candidateAttributes; 15 | 16 | @property (nonatomic, strong) LNDanmakuAbstractAttributes *lastAttributes; 17 | 18 | @end 19 | 20 | @implementation LNDanmakuBaseMoveTrackController 21 | 22 | - (instancetype)init 23 | { 24 | self = [super init]; 25 | if (self) { 26 | self.spaceTimeInterval = 0.2f; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)update:(NSTimeInterval)elapsingTime 32 | { 33 | if (elapsingTime < 0.f) { 34 | return; 35 | } 36 | 37 | if (elapsingTime < 0.018 && elapsingTime > 0.016) { 38 | elapsingTime = 0.0167; 39 | } 40 | 41 | NSMutableSet *shouldUnloadAttributes = [[NSMutableSet alloc] init]; 42 | for (LNDanmakuAbstractAttributes *attributes in self.currentAttributesMSet) { 43 | attributes.currentAliveTime += elapsingTime; 44 | NSTimeInterval restAliveTime = attributes.totalAliveTime - attributes.currentAliveTime; 45 | //NSLog(@"%lf --- %lf", attributes.currentAliveTime ,restAliveTime); 46 | if (restAliveTime > self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) { 47 | self.lastAttributes = attributes; 48 | } 49 | if (restAliveTime <= 0) { 50 | [shouldUnloadAttributes addObject:attributes]; 51 | } else { 52 | [self.track updateAttributes:attributes]; 53 | } 54 | } 55 | 56 | for (LNDanmakuAbstractAttributes *attributes in shouldUnloadAttributes) { 57 | [self unloadAttributes:attributes]; 58 | } 59 | [self checkCanLoadCandidate]; 60 | } 61 | 62 | - (void)clearTrack 63 | { 64 | NSSet *attributesArr = [NSSet setWithSet:self.currentAttributesMSet]; 65 | for (LNDanmakuAbstractAttributes *attributes in attributesArr) { 66 | [self unloadAttributes:attributes]; 67 | } 68 | if (self.candidateAttributes) { 69 | [self unloadAttributes:self.candidateAttributes]; 70 | } 71 | } 72 | 73 | - (void)checkCanLoadCandidate 74 | { 75 | if (self.candidateAttributes) { 76 | BOOL canLoadCandidate = NO; 77 | if (!self.lastAttributes) { 78 | canLoadCandidate = YES; 79 | } else { 80 | if (self.candidateAttributes.trackTime >= self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime + self.spaceTimeInterval && 81 | self.lastAttributes.currentAliveTime >= self.lastAttributes.danmakuTime + self.spaceTimeInterval) { 82 | canLoadCandidate = YES; 83 | } else { 84 | canLoadCandidate = NO; 85 | } 86 | } 87 | 88 | if (canLoadCandidate) { 89 | [self.currentAttributesMSet addObject:self.candidateAttributes]; 90 | [self.viewDelegate viewShouldLoadAttributes:self.candidateAttributes]; 91 | [self.track updateAttributes:self.candidateAttributes]; 92 | NSTimeInterval restAliveTime = self.candidateAttributes.totalAliveTime - self.candidateAttributes.currentAliveTime; 93 | if (restAliveTime > self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) { 94 | self.lastAttributes = self.candidateAttributes; 95 | } 96 | self.candidateAttributes = nil; 97 | } 98 | } 99 | } 100 | 101 | - (BOOL)isFree 102 | { 103 | if (self.candidateAttributes) { 104 | return NO; 105 | } 106 | return YES; 107 | } 108 | 109 | - (NSTimeInterval)estimatedFinishTimeForCurrentAttributes 110 | { 111 | if (self.lastAttributes) { 112 | if (self.candidateAttributes) { 113 | return MAX(self.candidateAttributes.trackTime, self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime + self.spaceTimeInterval) + self.candidateAttributes.danmakuTime; 114 | } 115 | return self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime; 116 | } else { 117 | return 0.f; 118 | } 119 | } 120 | 121 | - (NSTimeInterval)estimatedMinDisplayWaitTimeFor:(LNDanmakuAbstractAttributes *)attributes 122 | { 123 | NSTimeInterval estimatedTime = 0.0; 124 | if (self.candidateAttributes) { 125 | estimatedTime = 1000.0; 126 | } else { 127 | if (!self.lastAttributes) { 128 | estimatedTime = 0.0; 129 | } else { 130 | if (self.lastAttributes.currentAliveTime <= self.lastAttributes.danmakuTime) { 131 | //last danmaku is not totally displayed. 132 | NSTimeInterval lowSpeedMaxWaitTime = self.lastAttributes.danmakuTime - self.lastAttributes.currentAliveTime + self.spaceTimeInterval; 133 | NSTimeInterval highSpeedMaxWaitTime = (self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) - (attributes.trackTime - self.spaceTimeInterval); 134 | estimatedTime = MAX(0.0, MAX(highSpeedMaxWaitTime, lowSpeedMaxWaitTime)); 135 | } else if (self.lastAttributes.currentAliveTime - self.lastAttributes.danmakuTime < self.spaceTimeInterval){ 136 | //last danmaku totally displayed,consider some time spacing. 137 | NSTimeInterval lowSpeedMaxWaitTime = self.spaceTimeInterval - (self.lastAttributes.currentAliveTime - self.lastAttributes.danmakuTime); 138 | NSTimeInterval highSpeedMaxWaitTime = (self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) - (attributes.trackTime - self.spaceTimeInterval); 139 | estimatedTime = MAX(0.0, MAX(highSpeedMaxWaitTime, lowSpeedMaxWaitTime)); 140 | } else { 141 | //last danmaku totally displayed, and no time spacing. 142 | NSTimeInterval lowSpeedMaxWaitTime = 0.0; 143 | NSTimeInterval highSpeedMaxWaitTime = (self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) - (attributes.trackTime - self.spaceTimeInterval); 144 | estimatedTime = MAX(0.0, MAX(highSpeedMaxWaitTime, lowSpeedMaxWaitTime)); 145 | } 146 | } 147 | } 148 | return estimatedTime; 149 | } 150 | 151 | //recover 152 | 153 | - (NSTimeInterval)estimatedMaxAliveTimeFor:(LNDanmakuAbstractAttributes *)attributes 154 | { 155 | if (self.candidateAttributes) { 156 | return -1.f; 157 | } else if (self.lastAttributes) { 158 | NSTimeInterval lastRestTrackTime = self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime; 159 | if (self.lastAttributes.currentAliveTime > self.lastAttributes.danmakuTime + self.spaceTimeInterval && attributes.trackTime > lastRestTrackTime + self.spaceTimeInterval) { 160 | return MIN(attributes.trackTime - (lastRestTrackTime + self.spaceTimeInterval), self.lastAttributes.currentAliveTime - self.lastAttributes.danmakuTime - self.spaceTimeInterval) ; 161 | } else { 162 | return 0.f; 163 | } 164 | } else { 165 | return attributes.totalAliveTime; 166 | } 167 | } 168 | 169 | - (void)recoverLoadAttributes:(LNDanmakuAbstractAttributes *)attributes 170 | { 171 | if (![self isFree]) { 172 | return; 173 | } 174 | 175 | if ([self containsAttributes:attributes]) { 176 | return; 177 | } 178 | 179 | [super recoverLoadAttributes:attributes]; 180 | 181 | NSTimeInterval maxAliveTime = [self estimatedMaxAliveTimeFor:attributes]; 182 | if (maxAliveTime > 0.f) { 183 | [self.currentAttributesMSet addObject:attributes]; 184 | [self.viewDelegate viewShouldLoadAttributes:attributes]; 185 | [self.track updateAttributes:attributes]; 186 | NSTimeInterval restAliveTime = attributes.totalAliveTime - attributes.currentAliveTime; 187 | if (restAliveTime > self.lastAttributes.totalAliveTime - self.lastAttributes.currentAliveTime) { 188 | self.lastAttributes = attributes; 189 | } 190 | } else if (maxAliveTime == 0.f){ 191 | self.candidateAttributes = attributes; 192 | } else { 193 | return; 194 | } 195 | } 196 | 197 | //recover 198 | 199 | - (BOOL)containsAttributes:(LNDanmakuAbstractAttributes *)attributes 200 | { 201 | if ([self.currentAttributesMSet containsObject:attributes] || self.candidateAttributes == attributes) { 202 | return YES; 203 | } 204 | return NO; 205 | } 206 | 207 | - (void)loadAttributes:(LNDanmakuAbstractAttributes *)attributes 208 | { 209 | if (![self isFree]) { 210 | return; 211 | } 212 | 213 | if ([self containsAttributes:attributes]) { 214 | return; 215 | } 216 | [self.track resetAttributes:attributes]; 217 | [super loadAttributes:attributes]; 218 | self.candidateAttributes = attributes; 219 | } 220 | 221 | - (void)unloadAttributes:(LNDanmakuAbstractAttributes *)attributes 222 | { 223 | if (![self containsAttributes:attributes]) { 224 | return; 225 | } 226 | 227 | if ([self.currentAttributesMSet containsObject:attributes]) { 228 | [super unloadAttributes:attributes]; 229 | [self.currentAttributesMSet removeObject:attributes]; 230 | [self.viewDelegate viewShouldRemoveAttributes:attributes]; 231 | } else if (self.candidateAttributes == attributes) { 232 | [super unloadAttributes:attributes]; 233 | self.candidateAttributes = nil; 234 | } 235 | 236 | if (attributes == self.lastAttributes) { 237 | self.lastAttributes = nil; 238 | } 239 | } 240 | 241 | - (NSMutableSet *)currentAttributesMSet 242 | { 243 | if (!_currentAttributesMSet) { 244 | _currentAttributesMSet = [[NSMutableSet alloc] init]; 245 | } 246 | return _currentAttributesMSet; 247 | } 248 | 249 | @end 250 | -------------------------------------------------------------------------------- /LNDanmakuMaster/LNDanmakuMaster/LNDanmakuMaster/Dispatcher/LNDanmakuDispatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNDanmakuDispatcher.m 3 | // LNDanmakuMaster 4 | // 5 | // Created by Levison on 31.1.21. 6 | // Copyright © 2021 Levison. All rights reserved. 7 | // 8 | 9 | #import "LNDanmakuDispatcher.h" 10 | #import "LNDanmakuDispatchQueue.h" 11 | 12 | @interface LNDanmakuDispatcher () 13 | 14 | @property (nonatomic, strong) LNDanmakuDispatchQueue *attributesQueue; 15 | 16 | @end 17 | 18 | @implementation LNDanmakuDispatcher 19 | 20 | - (instancetype)init 21 | { 22 | self = [super init]; 23 | if (self) { 24 | self.maxSmoothGrainNum = 1; 25 | 26 | self.dispatchStrategy = LNDanmakuDispatchStrategyMostFastDisplay; 27 | self.recoverDispatcherStrategy = LNDanmakuRecoverDispatchStrategyMostFastDisplay; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)dispatchNewAttributesToFreeTracks:(NSArray *)trackControllerArray 33 | { 34 | if ([self.attributesQueue isEmpty]) { 35 | return; 36 | } 37 | //n * log(n - maxGrainNum) is wasted。 38 | if (self.restGrainNum == 0) { 39 | return; 40 | } else if (self.restGrainNum == 1) { 41 | // O(n) 42 | [self _dispatchOneNewAttributesToFreeTracks:trackControllerArray]; 43 | } else { 44 | // O(n*logn) 45 | [self _dispatchSeveralNewAttributesToFreeTracks:trackControllerArray]; 46 | } 47 | } 48 | 49 | - (NSArray *)getFreeTrackControllers:(NSArray *)trackControllerArray 50 | { 51 | NSMutableArray *trackControllerMArr = [[NSMutableArray alloc] init]; 52 | for (LNDanmakuAbstractTrackController *trackController in trackControllerArray) { 53 | if ([trackController isFree]) { 54 | [trackControllerMArr addObject:trackController]; 55 | } 56 | } 57 | trackControllerArray = [NSArray arrayWithArray:trackControllerMArr]; 58 | return trackControllerArray; 59 | } 60 | 61 | 62 | - (void)_dispatchOneNewAttributesToFreeTracks:(NSArray *)trackControllerArray 63 | { 64 | if ([self.attributesQueue isEmpty] || self.restGrainNum <= 0) { 65 | return; 66 | } 67 | trackControllerArray = [self getFreeTrackControllers:trackControllerArray]; 68 | LNDanmakuAbstractTrackController *targetTrackController = [trackControllerArray firstObject]; 69 | switch (self.dispatchStrategy) { 70 | case LNDanmakuDispatchStrategyMostFastDisplay: { 71 | LNDanmakuAbstractAttributes *targetAttributes = [self.attributesQueue top]; 72 | NSTimeInterval minWaitTime = MAXFLOAT; 73 | if (targetAttributes) { 74 | for (LNDanmakuAbstractTrackController * obj in trackControllerArray) { 75 | NSTimeInterval currentWaitTime = [obj estimatedMinDisplayWaitTimeFor:targetAttributes]; 76 | if (currentWaitTime < minWaitTime) { 77 | minWaitTime = currentWaitTime; 78 | targetTrackController = obj; 79 | } 80 | } 81 | } 82 | } break; 83 | case LNDanmakuDispatchStrategyLowDensity: { 84 | LNDanmakuAbstractAttributes *targetAttributes = [self.attributesQueue top]; 85 | NSTimeInterval minFinishTime = MAXFLOAT; 86 | if (targetAttributes) { 87 | for (LNDanmakuAbstractTrackController * obj in trackControllerArray) { 88 | NSTimeInterval currentFinishTime = [obj estimatedFinishTimeForCurrentAttributes]; 89 | if (currentFinishTime < minFinishTime) { 90 | minFinishTime = currentFinishTime; 91 | targetTrackController = obj; 92 | } 93 | } 94 | } 95 | } break; 96 | default: { 97 | 98 | } break; 99 | } 100 | 101 | if (self.restGrainNum > 0 && targetTrackController) { 102 | LNDanmakuAbstractAttributes *attributes = [self.attributesQueue pop]; 103 | if (attributes) { 104 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:willDispatch:to:)]) { 105 | [self.delegate dispatcher:self willDispatch:attributes to:targetTrackController]; 106 | } 107 | [targetTrackController loadAttributes:attributes]; 108 | [self decreaseRestGrainNum]; 109 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:didDispatch:to:)]) { 110 | [self.delegate dispatcher:self didDispatch:attributes to:targetTrackController]; 111 | } 112 | } 113 | } 114 | } 115 | 116 | - (void)_dispatchSeveralNewAttributesToFreeTracks:(NSArray *)trackControllerArray 117 | { 118 | if ([self.attributesQueue isEmpty] || self.restGrainNum <= 0) { 119 | return; 120 | } 121 | 122 | trackControllerArray = [self getFreeTrackControllers:trackControllerArray]; 123 | NSMutableArray *mArr = [NSMutableArray arrayWithArray:trackControllerArray]; 124 | 125 | NSMutableDictionary *mDic = [[NSMutableDictionary alloc] init]; 126 | 127 | switch (self.dispatchStrategy) { 128 | case LNDanmakuDispatchStrategyMostFastDisplay: { 129 | LNDanmakuAbstractAttributes *targetAttributes = [self.attributesQueue top]; 130 | if (targetAttributes) { 131 | [mArr sortWithOptions:NSSortStable usingComparator:^NSComparisonResult(LNDanmakuAbstractTrackController * _Nonnull obj1, LNDanmakuAbstractTrackController * _Nonnull obj2) { 132 | NSTimeInterval waitTime1 = [obj1 estimatedMinDisplayWaitTimeFor:targetAttributes]; 133 | NSTimeInterval waitTime2 = [obj2 estimatedMinDisplayWaitTimeFor:targetAttributes]; 134 | [mDic setObject:@(waitTime1) forKey:obj1.description]; 135 | [mDic setObject:@(waitTime2) forKey:obj2.description]; 136 | if (waitTime1 < waitTime2) { 137 | return NSOrderedAscending; 138 | } else if (waitTime1 > waitTime2) { 139 | return NSOrderedDescending; 140 | } else { 141 | return NSOrderedSame; 142 | } 143 | }]; 144 | } 145 | } break; 146 | case LNDanmakuDispatchStrategyLowDensity: { 147 | [mArr sortWithOptions:NSSortStable usingComparator:^NSComparisonResult(LNDanmakuAbstractTrackController * _Nonnull obj1, LNDanmakuAbstractTrackController * _Nonnull obj2) { 148 | NSTimeInterval finishTime1 = [obj1 estimatedFinishTimeForCurrentAttributes]; 149 | NSTimeInterval finishTime2 = [obj2 estimatedFinishTimeForCurrentAttributes]; 150 | if (finishTime1 < finishTime2) { 151 | return NSOrderedAscending; 152 | } else if (finishTime1 > finishTime2) { 153 | return NSOrderedDescending; 154 | } else { 155 | return NSOrderedSame; 156 | } 157 | }]; 158 | } break; 159 | default: { 160 | 161 | } break; 162 | } 163 | 164 | for (LNDanmakuAbstractTrackController *trackController in mArr) { 165 | if (self.restGrainNum > 0) { 166 | LNDanmakuAbstractAttributes *attributes = [self.attributesQueue pop]; 167 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:willDispatch:to:)]) { 168 | [self.delegate dispatcher:self willDispatch:attributes to:trackController]; 169 | } 170 | [trackController loadAttributes:attributes]; 171 | [self decreaseRestGrainNum]; 172 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:didDispatch:to:)]) { 173 | [self.delegate dispatcher:self didDispatch:attributes to:trackController]; 174 | } 175 | } else { 176 | break; 177 | } 178 | } 179 | } 180 | 181 | - (void)insertNewAttributes:(NSArray *)newAttributesArray 182 | { 183 | for (LNDanmakuAbstractAttributes *attributes in newAttributesArray) { 184 | [self.attributesQueue push:attributes]; 185 | } 186 | } 187 | 188 | - (void)insertHighPriorityAttributes:(NSArray *)highPriorityAttributesArray 189 | { 190 | for (LNDanmakuAbstractAttributes *attributes in highPriorityAttributesArray) { 191 | [self.attributesQueue push:attributes priority:LNDanmakuDispatchQueuePriorityHigh]; 192 | } 193 | } 194 | 195 | - (void)clear 196 | { 197 | NSArray *clearedAttributesArr = [self.attributesQueue clearQueue]; 198 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:clear:)]) { 199 | [self.delegate dispatcher:self clear:clearedAttributesArr]; 200 | } 201 | } 202 | 203 | - (BOOL)containsAttributes:(LNDanmakuAbstractAttributes *)attributes 204 | { 205 | if ([self.attributesQueue contains:attributes]) { 206 | return YES; 207 | } 208 | return NO; 209 | } 210 | 211 | - (void)removeAttributes:(LNDanmakuAbstractAttributes *)attributes 212 | { 213 | if ([self.attributesQueue contains:attributes]) { 214 | [self.attributesQueue remove:attributes]; 215 | } 216 | } 217 | 218 | - (LNDanmakuDispatchQueue *)attributesQueue 219 | { 220 | if (!_attributesQueue) { 221 | _attributesQueue = [[LNDanmakuDispatchQueue alloc] init]; 222 | _attributesQueue.delegate = self; 223 | } 224 | return _attributesQueue; 225 | } 226 | 227 | - (void)danmakuDispatchQueueDidDrop:(LNDanmakuAbstractAttributes *)attributes 228 | { 229 | //满了 230 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:drop:)]) { 231 | [self.delegate dispatcher:self drop:attributes]; 232 | } 233 | } 234 | 235 | @end 236 | 237 | @implementation LNDanmakuDispatcher (Recover) 238 | 239 | - (void)recoverInsertAttributes:(NSArray *)attributesArray toTrackControllers:(NSArray *)trackControllerArray 240 | { 241 | for (LNDanmakuAbstractAttributes *attributes in attributesArray) { 242 | [self _recoverInsertOneAttributes:attributes to:trackControllerArray]; 243 | } 244 | } 245 | 246 | - (void)_recoverInsertOneAttributes:(LNDanmakuAbstractAttributes *)attributes to:(NSArray *)trackControllerArray 247 | { 248 | trackControllerArray = [self getFreeTrackControllers:trackControllerArray]; 249 | LNDanmakuAbstractTrackController *targetTrackController = [trackControllerArray firstObject]; 250 | switch (self.recoverDispatcherStrategy) { 251 | case LNDanmakuRecoverDispatchStrategyMostFastDisplay: { 252 | LNDanmakuAbstractAttributes *targetAttributes = attributes; 253 | NSTimeInterval maxAliveTime = -1.f; 254 | if (targetAttributes) { 255 | for (LNDanmakuAbstractTrackController * obj in trackControllerArray) { 256 | NSTimeInterval currentAliveTime = MIN([obj estimatedMaxAliveTimeFor:targetAttributes],targetAttributes.currentAliveTime); 257 | if (currentAliveTime > maxAliveTime) { 258 | maxAliveTime = currentAliveTime; 259 | targetTrackController = obj; 260 | } 261 | } 262 | } 263 | } break; 264 | case LNDanmakuRecoverDispatchStrategyLowDensity: { 265 | LNDanmakuAbstractAttributes *targetAttributes = attributes; 266 | NSTimeInterval minFinishTime = MAXFLOAT; 267 | if (targetAttributes) { 268 | for (LNDanmakuAbstractTrackController * obj in trackControllerArray) { 269 | NSTimeInterval currentFinishTime = [obj estimatedFinishTimeForCurrentAttributes]; 270 | if (currentFinishTime < minFinishTime) { 271 | minFinishTime = currentFinishTime; 272 | targetTrackController = obj; 273 | } 274 | } 275 | } 276 | } break; 277 | default: { 278 | 279 | }break; 280 | } 281 | 282 | if (targetTrackController) { 283 | NSTimeInterval maxAliveTime = [targetTrackController estimatedMaxAliveTimeFor:attributes]; 284 | if (maxAliveTime >= 0.f) { 285 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:willDispatch:to:)]) { 286 | [self.delegate dispatcher:self willDispatch:attributes to:targetTrackController]; 287 | } 288 | attributes.currentAliveTime = MIN(maxAliveTime, attributes.currentAliveTime); 289 | [targetTrackController recoverLoadAttributes:attributes]; 290 | if (self.delegate && [self.delegate respondsToSelector:@selector(dispatcher:didDispatch:to:)]) { 291 | [self.delegate dispatcher:self didDispatch:attributes to:targetTrackController]; 292 | } 293 | } else { 294 | [self.attributesQueue push:attributes]; 295 | } 296 | } else { 297 | [self.attributesQueue push:attributes]; 298 | } 299 | } 300 | 301 | @end 302 | 303 | --------------------------------------------------------------------------------