弹幕,国内流行于视频网站A站和B站。网上关于弹幕的实现方法有很多,目前Android平台已经有比较成熟的解决方案DanmakuFlameMaster 。而iOS平台尚无比较成熟的开源库,在借鉴DanmakuFlameMaster的实现思想后,特分享iOS平台弹幕解决方案HJDanmakuDemo。本文将介绍弹幕的大致实现原理。
看过DanmakuFlameMaster源码的朋友都知道,弹幕实现主要需要解决以下几个问题
- 弹幕绘制方式
- 弹幕时间控制
- 弹幕碰撞检测原理
- 弹幕暂停及恢复
本文主要从以上4个方面介绍弹幕的详细实现原理。首先是弹幕绘制方式,在DanmakuFlameMaster库中,它主要通过view的自定义draw一帧一帧的绘制来完成弹幕的显示,这种方式最大的问题在于性能以及动画的不流畅。弹幕流畅的前提要求每秒绘制的帧数在30帧以上,而移动设备性能千差万别,当同一时刻需要绘制大量弹幕的时候,对于低端设备就会出现卡帧不流畅的情况,这会大大降低用户的体验。因此,在本项目中放弃采用自定义绘制帧的方式,而是采用系统动画的方式来实现弹幕文本的滚动。
1 2 3 |
[UIView animateWithDuration:danmaku.remainTime delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ danmaku.label.frame = CGRectMake(-danmaku.size.width, danmaku.py, danmaku.size.width, danmaku.size.height); } completion:nil]; |
其次,就是弹幕时间的控制。由于采用系统动画的方式,所以不需要时刻计算每一个弹幕的显示时间以及其X坐标(假设弹幕横向滚动),我们需要做的就是在弹幕需要出现的时候创建它,然后设定弹幕存活的时间,剩余滚动动画交给系统负责,当然,弹幕剩余时间需要我们来更新。本项目中,创建了一个0.5s间隔的定时器,主要负责创建新的弹幕并更新已显示弹幕的剩余时间,也就是说0.5s执行一次计算,如果需要,可以将刷新间隔设置成5s或者更长。
1 2 3 |
_timer = [NSTimer timerWithTimeInterval:_frameInterval target:self selector:@selector(onTimeCount) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; [_timer fire]; |
然后,就是弹幕碰撞检测的问题。碰撞检测主要难点在于检测横向滚动弹幕之间的碰撞,弹幕存活时间由其显示时间和存活长短决定,因此,弹幕之间是否碰撞只需检测开始和消失是否碰撞即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (BOOL)checkIsWillHitWithWidth:(float)width DanmakuL:(DanmakuBaseModel *)danmakuL DanmakuR:(DanmakuBaseModel *)danmakuR { if (danmakuL.remainTime<=0) { return NO; } if (danmakuL.px+danmakuL.size.width>danmakuR.px) { return YES; } float minRemainTime = MIN(danmakuL.remainTime, danmakuR.remainTime); float px1 = [danmakuL pxWithScreenWidth:width RemainTime:(danmakuL.remainTime-minRemainTime)]; float px2 = [danmakuR pxWithScreenWidth:width RemainTime:(danmakuR.remainTime-minRemainTime)]; if (px1+danmakuL.size.width>px2) { return YES; } return NO; } |
最后,弹幕的暂停及恢复。由于弹幕滚动采用系统动画,所以在解决弹幕暂停前需要先了解系统动画的实现原理,有兴趣的朋友可以参考动画解释这篇文章,在此就不做过多介绍。暂停的基本原理就是通过view的presentationLayer获取对象的当前坐标并赋给其frame,然后移除layer动画,考虑到缓冲,可以在当前坐标基础上-1
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (void)pauseRenderer { for (DanmakuBaseModel *danmaku in _drawArray.objectEnumerator) { CALayer *layer = danmaku.label.layer; CGRect rect = danmaku.label.frame; if (layer.presentationLayer) { rect = ((CALayer *)layer.presentationLayer).frame; rect.origin.x-=1; } danmaku.label.frame = rect; [danmaku.label.layer removeAllAnimations]; } } |
有兴趣的童鞋可以下载HJDanmakuDemo查看具体使用方法,有什么疑问可以在后面留言。 如果你喜欢,希望能在github上为本demo点上一赞,感谢你的来访!
你好
这个缓冲可以去掉,缓冲的原因是动画系统与 Runloop 的脱离,因此将每个弹幕的 layer 计算拆开到其他 runloop 中,就不会有位置上的偏移了
验证过吗?听起来不错!
尽管是有了另一番思路,但是自己知识储备量还是不够,具体实现还是不太明白,哎,惭愧
大哥 使用你的代码 其中有时候会随机 产生一个 —
感谢您的来访,最近我会去掉这个网址广告!
新代码已上传!
问下,我给每条弹幕信息加了一个点击手势,点击没反应
很遗憾,考虑到通用性,目前danmukuview是忽略任何手势响应的
你好, 首先非常感谢分享,好像不能动态改变文字大小, 屏蔽顶端或是底端弹幕
恩,这些需求目前都还不支持,以后会慢慢加入的,谢谢你的来访!
你好..谢谢你的分享.. 我也是按照这种思路写的弹幕.. 但是有些问题.. 可以交流下么..QQ.304977710.
关于界面有个人资料,谢谢来访!
你好,现在的代码通过send发出来的弹幕还是会带红色的下划线啊
带红色下划线是为了强调该条记录为刚才所发!
感谢分享,不过我这边项目也需要一个简单的弹幕,不过和ls一样需要动态改变字体大小,我感觉你吧中字体大字体那个参数直接改成字体大小就好啦。看来要自己写一个了T T
现在传入的就是字体大小哦
只能选择大字体中字体的大小吧,但是每条弹幕没法自己设定大小
动态设置字体大小的确还不支持,只能提前在DanmakuConfiguration对象里设置好两种字体大小,然后在DanmakuSource里指定该条弹幕是中字体还是大字体!
你好..我说说我的思路吧.
1. 进入的时候创建一个数组存放30个Label的用于弹幕显示.
2. 设定一个定时器 每隔一段时间取出一个弹幕数据与一个Label显示.
[UIView animateWithDuration:animateTime delay:0 options:UIViewAnimationOptionCurveLinear animations:^(){
lab.frame = CGRectMake(-self.view.frame.size.width, self.view.frame.origin.y, self.view.frame.size.width, DANMULABHEIGHT);
} completion:^(BOOL finished){
//进行弹幕Label的回收.
………
}];
在不与其它使用opengl模块的功能一起工作时跟你的CPU效率差不多.. 但是一起工作时比你的CPU占用高一些.
影响CPU性能除了定时器设定间隔,Label回收再利用还有过滤算法都有关系,至于你说的opengl,这个应该与动画渲染有关,不知道是不是由于定时器间隔过密导致的,还有过滤过程尽量在子线程完成!
你好,我也在做视频直接+弹幕,非常感谢你分享出你的想法来。等我看了代码,如果有想法跟一交流的话再回来跟你交流。再次感谢。
感谢来访,有什么问题都可以在下面跟我留言!
没有开源啊,打成库了。你的库代码是因为公司原因不能开源吗?
花名孤剑 。。。。。哈哈,加你旺旺。
孤剑 。http://www.topmobile.me 欢迎交流。
我想问一下,这个初始化的时候必须给一个xml的文件么?
你可以查看deme了解具体使用方法,初始化时是不需要使用xml文件的,你可以定义自己的格式,创建一个DanmakuSource数组传进来就可以了,当然也可以实时发送弹幕
prepareDanmakus这个方法是初始化时必须执行的么,我这个数组是每隔5分钟获取一次的,所以初始化的时候没有数据,注掉demo里初始化数组那的代码,发现isprepare这个属性一直都是no.如何解决呢,求教大神….
我要做的是实时弹幕,当socket接到一条的时候,就是显示出来,如果用sendDanmakuSource方法的话,就每条都有红线,怎么解决????谢谢.
在- (void)drawDanmakus:(NSArray *)danmakus time:(DanmakuTime *)time isBuffering:(BOOL)isBuffering 方法中,
danmaku.remainTime = danmaku.time-time.time+danmaku.duration;
创建弹幕的时候,为什么弹幕的剩余时间要设置成这样而不直接使用danmuku.duration ?
你好,新版本中的isShowLineWhenSelf属性,怎么不放到DanmakuSource里.这样就可以控制每条要发的是否要显示是自己发的.
这个可以为DanmakuSource增加一个新的属性,而isShowLineWhenSelf类似于全局开关,好建议,下个版本可以考虑为DanmakuSource增加是否自己的属性
如何定制弹幕的运动方式啊
比如 我就想制定 上方 向左 滚动 或者 下方悬浮
通过指定type类型即可
为什么是lib
请问您,字幕可以显示图片不
目前还不支持咧~
什么时候能支持啊
目前还没有时间表,为啥弹幕还要支持图片,这么复杂~
楼主,对于这样的好东西,还是分享分享,让大家都学习学习嘛
谢谢来访,公司有规定啊~
你好,谢谢你的分享,不过我发现在更新xcode7 之后,弹幕库会引起死机啊,请帮忙分析一下,谢谢。
行,我这边最近会使用xcode7重新编译,目前我这边使用xcode7编译的库不会出现卡死的问题,等更新后你再验证下
你好,改变DanmakuView的frame,弹幕出现的位置却没有变化,当需要改变frame时只能重新alloc一个吗?
你是怎么修改的,建议直接代码修改danmakuview的frame就可以了
你好,请问当需要改变DanmakuView的大小时只能重新alloc一个对象吗?
不需要的,修改view的frame会自适应的
楼主为什么不开源,这样项目可以长大,要不我们都不会选它
已开源
Pingback: iOS弹幕基本实现及原理介绍-IT大道
新建一个和弹幕Lbl一样大小的View然后在View上面给弹幕String绘制一个空心描边然后加到原来的弹幕Lbl上面,就简单的实现的弹幕渲染,测试了B站几个爆炸弹幕的视频都无压力~不知道博主是不是这种思路
没明白你的意思
赶上好时候了 开源不说 还增加了这么多功能 非常感谢楼主的慷慨
嘿嘿,多多点赞就行了
楼主建一个群 大家一起讨论下
感谢博主分享的弹幕代码,我后来又给加上了字体描黑边效果,感觉弹幕效果更好了些
恩,开源后效果可以自定义了,多多来拜访
你好,请问可以支持点击事件吗?有没有支持点击事件的方案?
During an animation, user interactions are temporarily disabled for all views involved in the animation, regardless of the value in this property. You can disable this behavior by specifying the UIViewAnimationOptionAllowUserInteraction option when configuring the animation.
设置UIViewAnimationOptionAllowUserInteraction后也不支持移动中点击弹幕,使用animation实现的弹幕是否可以支持点击事件?
你好,请问可以支持点击事件吗?有没有支持点击事件的方案?
During an animation, user interactions are temporarily disabled for all views involved in the animation, regardless of the value in this property. You can disable this behavior by specifying the UIViewAnimationOptionAllowUserInteraction option when configuring the animation.
设置UIViewAnimationOptionAllowUserInteraction后也不支持移动中点击弹幕,使用animation实现的弹幕是否可以支持点击事件?
我弹幕都出现两次,暂时找不到原因啊
你好 我弹幕都出现两次,暂时找不到原因啊
是不是设置了正在缓冲状态?其次,播放器进度正确吗?
博主你好,我用了你的弹幕代码.发现发弹幕的时候不是立即发送的,你设置了弹幕出现时间要比当前发送时间延迟1秒
就是这句代码 int time = ([self danmakuViewGetPlayTime:nil]+1)*1000;
我想立即发送,把+1删除了,但是弹幕不显示了.我看了源码,弹幕其实是加到了数组里面的.
然后 你的源码里 有这个判断
– (NSArray *)filterDanmakus:(NSArray *)danmakus Time:(DanmakuTime *)time
{
if (danmakus.count<1) {
return nil;
}
DanmakuBaseModel *lastDanmaku = danmakus.lastObject;
if (![lastDanmaku isDraw:time.time]) {
return nil;
}
DanmakuBaseModel *firstDanmaku = danmakus.firstObject;
if ([firstDanmaku isDraw:time.time]) {
return danmakus;
}
return [self cutWithDanmakus:danmakus Time:time];
}
那个isDraw方法里那个时间判断永远返回空了.
不知道怎么改了,求助….
原机制是基于每个弹幕时间决定是否需要显示的,如果你把时间设置的跟当前时间一直,由于计算时间有误差,计算的时候就会被当做已经显示过,最简单的就是对于新发的弹幕增加一个属性,如果该属性为YES,就忽略时间检测,直接显示,然后再设为NO,通过时间戳的方式显示就OK了
我现在在DanmakuBaseModel里加了一个属性immediatelyShow来控制是否立即显示
然后在
– (BOOL)isDraw:(float)curTime
{ if(self.immediatelyShow == YES)
{
return YES;
}
return self.time>=curTime;
}
这个方法里加了这个判断,如果是立即显示,就不判断时间
然后在
– (void)drawDanmakus:(NSArray *)danmakus Time:(DanmakuTime *)time IsBuffering:(BOOL)isBuffering
这个方法里将danmaku.immediatelyShow = NO; 设置为NO
字幕倒是立即显示了,但是又出现一个问题.字幕在屏幕右边停顿了一小会然后再往左边移动.这是什么情况呢?
调整发送弹幕时间
我发现另外一个问题,如果弹幕view加到另一个view上,如果把弹幕view remove了以后,隔0.5秒再加到原来的view上,弹幕就消失了.
我调试了下,发现render里面的UIView animateWithDuration是调用了的.
但是动画貌似不起作用了,弹幕lable的坐标直接变成负值了,所以看起来就像消失了一样.
这个问题怎么解决呢?
额,我发的评论消失了.
再发一次
====
我发现弹幕view从一个view上移除了后,上面的动画不会继续进行了.我想把弹幕view从一个view上加到另外一个view上,而且动画会继续进行,应该怎么做呢?
为啥需要从一个view移到另一个view了?如果加到另一个view,肯定需要重新加载动画的
感谢分享!已star
把大牛您的项目加到vvitamio的视频播放器里面 弹幕就是不显示 不知道为啥
是vitamio
– (NSArray *)cutWithDanmakus:(NSArray *)danmakus Time:(DanmakuTime *)time
{
NSUInteger count = danmakus.count;
NSUInteger index, minIndex=0, maxIndex = count-1;
DanmakuBaseModel *danmaku = nil;
while (maxIndex-minIndex>1) {
index = (maxIndex+minIndex)/2;
danmaku = danmakus[index];
if ([danmaku isDraw:time.time]) {
maxIndex = index;
} else {
minIndex = index;
}
}
return [danmakus subarrayWithRange:NSMakeRange(maxIndex, count-maxIndex)];
}
这段代码是干什么用的?原谅我小白!
二分法找到当前播放进度之后即将被渲染的弹幕
希望越滚越大,越来越好
对了,可以多加一点注释,也让人更快熟悉,参与进来这样利于发展。
写的非常棒, 接口写的也很好。。。
pod 更新不成, 只能直接拉进来了 。。。。
非常感谢 作者, 为弹幕做出的贡献
谢谢 鞠躬
感谢来访
希望大神可以多加点注释,要不对我等小菜,理解起来好吃力的说~
感谢来访
现在我创建了一个定时器,5秒接收一次服务器传过来的数据,数据虽然可以显示了,但是当我点击暂停,5秒弹幕自动的开始滚动!请问这是什么情况?
什么意思?描述不是很清楚
怎样改变字体类型,字体太粗了!
源码是开源的,太粗不是字体的问题,而是加了描边所致
嗯,解决了,请问怎样并行显示呢?
怎样实现并行显示呢? 比如一行显示两个弹幕,三个弹幕
求教, 怎样实现一行显示多个弹幕??
一行显示多个弹幕什么意思?现在不都是一行一行显示的
就像B站一样, 一行显示多个弹幕,而不是等这一行的弹幕消失了再显示另一个. 项目有这个需求,怎样改呢?
现在这个弹幕就是一行显示多个
已实现一行多个弹幕, 我想问自己发的弹幕怎样加下划线, 我看到你代码有这方面的打算,现在实现了吗?
自己发的弹幕就有下划线
configuration.isShowLineWhenSelf = YES; 是这样吗? 设置为YES 之后, 自己发的弹幕和之前一样,没有下划线,怎么整?谢谢
你好,我把你的弹幕控件,放在UITableView的cell中使用,滚动界面后,弹幕又会重新加载,怎样可以让它不重新加载
你好,我把你的弹幕控件,放在UITableView的cell中使用,滚动界面后,弹幕又会重新加载,怎样可以让它不重新加载字幕
你好,我把你的弹幕控件,放在UITableView的cell中使用,滚动界面后,弹幕又会重新加载,怎样可以让它不重新加载字幕
视图重用
博主,
在- (void)drawDanmakus:(NSArray *)danmakus time:(DanmakuTime *)time isBuffering:(BOOL)isBuffering 方法中,
danmaku.remainTime = danmaku.time-time.time+danmaku.duration;
创建弹幕的时候,为什么弹幕的剩余时间要设置成这样(弹幕进入显示的时间 – 当前时间轴时间 + 弹幕的持续时间) 而不直接使用danmuku.duration ?
duration是持续时间,这个是不变的
您好 我发一条弹幕结果重复显示 在嘛?
你是在demo里面还是自己工程里?看看是不是真的重复发送了~
大神,请问如何让弹幕密集起来,每一行的弹幕之间的间隔短一点 10个像素就可以
你好 cocoa pods 搜索的时候 搜索不到HJDanmaku是什么情况呢
你好 cocoa pods 搜索HJDanmaku的时候 搜不到是什么情况呢
因为HJDanmaku还没有加入到官方cocoapods仓库里面,等2.0最终Release发布再上传到cocoapods里面,你可以直接在podfile里面直接依赖该pod. pod ‘HJDanmaku’, :git => ‘https://github.com/panghaijiao/HJDanmakuDemo.git’
嗯嗯 谢谢
你好 HJDanmakuModel里面没有关于文字的属性呢 如果发弹幕 应该怎么发呢
参考demo,创建HJDanmakuModel的子类,自定义你自己的业务属性即可!