iOS弹幕组件LNDanmakuMaster

LNDanmakuMaster是一个轻量的弹幕播放器,通过:创建播放器->创建轨道->添加弹幕的方式进行弹幕播放,提供丰富轨道样式的同时也支持自定义轨道;对传入的弹幕的视图层没有要求(任意的View/Layer);提供多种(目前是3)弹幕分布策略;支持使用轨道组播放特殊弹幕;提供与分布策略的对应的弹幕seek策略。
Github链接:LNDanmakuMaster
  • 你可以直接下载这个链接并运行上面丰富的Demo,或参考Demo代码实现自己的弹幕播放器,也可以直接使用Cocoapods👇
Cocoapods
pod 'LNDanmakuMaster'
子文档

0.iOS端弹幕相关调研
1.从一个弹幕开始:LNDanmakuAttributes
2.动画的核心:LNDanmakuTrackController
3.暂存和分发:LNDanmakuDispatcher
4.弹幕复用:LNDanmakuPool
5.弹幕容器和手势:LNDanmakuContainerView
6.处理条件弹幕:LNDanmakuTrackGroup
7.封装组件:LNDanmakuPlayer

弹幕机制
  1. 驱动机制
    视频播放器的刷新率通常为29frame/s,弹幕播放器采用同样的刷新频率会有卡顿感,因此弹幕播放器通常使用自己的刷新驱动,要么是UIView animation,要么是CADisplayLink;CADisplaylink支持更多的细节、进度控制,因此这里我们选用的是CADisplayLink。
    LNDanmakuClock是这样一个驱动,封装了CADisplayLink,增加了一些暂停、销毁的控制方法,这个时钟每次的输出就是从上一次DisplayLink回调到这次displayLink回调的一小段时间。
  2. 进度控制机制
    LNDanmakuMaster整体采用时间定义进度,这也是它与其他弹幕框架的主要区别之一,我们的所有进度控制、追赶控制、繁忙度控制,都是使用时间计算的。
    使用时间变量替代空间变量的优势包括:
  • 如果你的轨道路线并非直线:如果你希望一个弹幕既可以在水平轨道播放也可以在圆形轨道播放,既要给出角速度,也要给出线速度,如果使用时间单位,只需要给出运行的总时间。
  • 如果你先控制一个曲线轨道两条弹幕之间的间距,空间间距需要计算曲线的长度,而对时间间距来说处理起来就和直线一样。
  • 在做一些判断时,如果使用空间条件做判断,则需要进行速度、时间相关大量乘除法运算,使用时间可能只需要加减法,虽然我不知道具体是否有优势,但直观上感觉乘除法是没有加减法快的。
  • 如果是像B站那种中间出现一列的弹幕,它不需要速度,只需要显示的时间就够了。
    总之,使用时间体系替代速度体系,是能统一多种轨道进度控制的一个好方法。

3.刷新机制
根据Clock的输出,我们可以得到一个稳定的回调得知刚刚经过了多长一段时间,那么弹幕的刷新过程就成为:在弹幕的剩余存活时间中扣除刚刚经过的那段时间,并根据扣除后的剩余时间占总时间的百分比来刷新弹幕的位置、大小等信息。
经过以上三个主要机制的介绍,已经有了实现一个弹幕框架的所有主要逻辑,剩下的就是一些模块的细分和细节上的雕琢。

模块分工

LNDanmakuMaster将整个弹幕框架分成以下几个部分(Abstract代表支持重写定制):

模块名称 类名 备注
播放器 LNDanmakuPlayer 播放器相当于对整个弹幕框架其他组件的整合,对外提供调用方法和时机代理
分发器 LNDanmakuAbstractDispatcher Dispatcher类似管理轨道的工头儿,根据轨道集合的状态决定弹幕放到哪里才是最好的
轨道控制器 LNDanmakuAbstractTrackController 轨道控制器类似一个工人,定期使用工具(Track)维护自己的弹幕,并向Dispatcher反馈自己的(繁忙/空闲)状态
轨道 LNDanmakuAbstractTrack Track的职责完全符合轨道的定义,它不维护弹幕,只维护任意一条弹幕弹幕在这个轨道上的刷新的位置、大小、仿射变换等属性与时间进度的映射,像是一个空间信息与时间信息的函数
样式 LNDanmakuAbstractAttributes 样式是弹幕的载体,包含了一条弹幕的所有信息,例如:存活时间、位置、携带的业务模型、展示时使用的View/Layer等等。对,没错,与CollectionViewAttributes十分类似,并根据播放器特性增加了时间戳信息

额外的组件

模块名称 类名 备注
轨道组 LNDanmakuTrackGroup 这个组件是用来做一些特殊弹幕播放的,是类似一个Player的更小单元(但没有自己的驱动),内部包含了一个Dispatcher和若干TrackController

这个组件的意义:
某些弹幕播放对轨道有一定的要求,又或是轨道对自己播放的弹幕有一定要求,例如:送礼物的轨道只能出现在屏幕顶端,而不是中央,来避免影响用户观看视频;或者是,圆形的轨道不能那些较长的文字,这样它看起来就不是那么圆了,等等。
一但从两个方面考虑这个问题,就会陷入:轨道挑选弹幕/弹幕挑选轨道的困境,而实际上两种情况是都存在的,这两个问题最后统一使用轨道组解决,用户指定一个轨道的分组,并可以跨过Player层,直接向这个轨道组抛弹幕,那么这个弹幕就只有可能出现在这个轨道组包含的轨道中;这个功能在Demo中一个彩虹样式的轨道中得以体现,我将七种颜色的弹幕分别抛入七个轨道组中(每个轨道组有三根轨道,两个相邻的轨道组公用中间那个重合的轨道),这样它们就呈现除了一种彩虹的效果。

使用示例

以上介绍完了这个框架的所有重要组件,这里举例介绍构建一个最简单弹幕播放器的过程:
1.懒加载一个danmakuPlayer:
这个是起码要做,不需要做任何配置它就可以正常工作

- (LNDanmakuPlayer *)danmakuPlayer
{
    if (!_danmakuPlayer) {
        _danmakuPlayer = [[LNDanmakuPlayer alloc] init];
    }
    return _danmakuPlayer;
}

2.把这个播放器的容器View加到屏幕上:

    [self.view addSubview:self.danmakuPlayer.containerView];

3.给这个Player加一些轨道:
Player支持自定义就是主要体现在自定义轨道和弹幕样式上,所以,所有的轨道都是你亲手加上去的,你可以在init/viewDidLoad等初始化的时机做这个时,也可以在Player懒加载时一并加好

- (void)addTrack
    for (int i = 0; i < 20; i++) {
        LNDanmakuHorizontalMoveTrackController *horizontalTrackController = [[LNDanmakuHorizontalMoveTrackController alloc] init];
        horizontalTrackController.horizontalTrack.startPosition = CGPointMake(0, 44.f + 30.f * i);
        horizontalTrackController.horizontalTrack.width = self.view.frame.size.width;
        horizontalTrackController.spaceTimeInterval = 0.f;
        [self.danmakuPlayer addTrack:horizontalTrackController];
    }
}

4.让播放器动起来!
现在这个播放器就可以从外界接口随便一个弹幕并播放在屏幕上了

- (void)startPlay {
    [self.danmakuPlayer start];
}

5.让我们尝试放一个简单的弹幕放上去:

  • (void)addRandomDanmaku
    {
    LNDanmakuAttributes *attributes = [[LNDanmakuAttributes alloc] init];
    UIView *colorView = [[UIView alloc] init];
    colorView.backgroundColor = [UIColor redColor];
    attributes.presentView = colorView;
    attributes.trackTime =4.f;
    attributes.size = CGSizeMake(88.f, 44.f);
    [_player insertAttributes:@[attributes]];
    }

这个框架尽量使用最符合正常逻辑的方法定义了每个组件的分工来保证它使用起来是最舒服的,并尽可能封装了那些看起来比较复杂的逻辑:分发、刷新、追赶等等;当然,我觉得一个合理的框架应该是下限很低,上限也很高的,而不是一成不变使用规则(正如掌控疾风的某位男子应该算得上是设计得比较成功的角色),所以,当使用者需要深入探讨这些逻辑的时候,也可以从外部轻易定制他们,玩出自己的特色。

后续会陆续更新一些使用上或是原理方面的文章介绍这个框架,虽然实现起来没有很多高超的技巧,但我认为代码优秀与否并非取决于使用了多么高深或是精妙的语言特性,而是写代码的逻辑和思路;而且,我从心底十分抵制那些没什么复杂逻辑却要制定很多使用规则的组件(更恶心的是还要业务线强推),所以这个组件一定会朝着尽量少的使用规则、尽量朴素的代码、更丰富的功能方向发展。

最后附上几个Demo中的效果图

  • 横向的轨道

  • pop动画轨道

  • 波浪轨道+轨道分组

  • 心形轨道

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容