├── 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 | [](https://imgchr.com/i/yVlmjO)
135 |
136 | * pop动画轨道
137 |
138 | [](https://imgchr.com/i/yVlegK)
139 |
140 | * 波浪轨道+轨道分组
141 |
142 | [](https://imgchr.com/i/yVluuD)
143 |
144 | * 心形轨道
145 |
146 | [](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 |
--------------------------------------------------------------------------------