Android DanmakuFlameMaster的踩坑方式
这里根据我用到过的功能填一些坑,但目前也只用到了部分功能,弹幕数据动态衔接的问题还没有遇到,以后遇到添加进来1. 基本使用基本使用看官方demo就行, 官方demo里注释非常详细,或者看这篇文章: 开源弹幕引擎·烈焰弹幕使(DanmakuFlameMaster)使用解析private void initDamakuView(String s) {danmakuContext = DanmakuCo
这里根据我用到过的功能填一些坑,但目前也只用到了部分功能,弹幕数据动态衔接的问题还没有遇到,以后遇到添加进来
1. 基本使用
基本使用看官方demo就行, 官方demo 里注释非常详细,或者看这篇文章: 开源弹幕引擎·烈焰弹幕使(DanmakuFlameMaster)使用解析
private void initDamakuView(String s) {
danmakuContext = DanmakuContext.create();
HashMap<Integer, Integer> maxLinesPair = new HashMap<Integer, Integer>();
maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_LR, 3); // 滚动弹幕最大显示3行
HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<Integer, Boolean>();
overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_LR, true);
overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_BOTTOM, true);
danmakuContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3) //设置描边样式
.setDuplicateMergingEnabled(false)
.setScrollSpeedFactor(1.2f) //是否启用合并重复弹幕
.setScaleTextSize(1.2f) //设置弹幕滚动速度系数,只对滚动弹幕有效
.setCacheStuffer(new BackgroundCatchSpanner(this), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer 设置缓存绘制填充器,默认使用{@link SimpleTextCacheStuffer}只支持纯文字显示, 如果需要图文混排请设置{@link SpannedCacheStuffer}如果需要定制其他样式请扩展{@link SimpleTextCacheStuffer}|{@link SpannedCacheStuffer}
.setMaximumLines(maxLinesPair) //设置最大显示行数
.preventOverlapping(overlappingEnablePair); //设置防弹幕重叠,null为允许重叠
if (danmakuView != null) {
try {
parser = (MyDanmakuParaser) createParser(this.getAssets().open(s
)); //创建解析器对象,从raw资源目录下解析comments.xml文本
} catch (IOException e) {
e.printStackTrace();
}
danmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() {
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void drawingFinished() {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
}
@Override
public void prepared() {
danmakuView.start();
}
});
danmakuView.prepare(parser, danmakuContext);
danmakuView.showFPS(true); //是否显示FPS
}
}
这样就初始化完成了,弹幕滚动了起来。但是在这一步很可能会遇到一个问题,设置不生效
设置不生效其实跟BaseDanmaku
中一个属性priority
有关,注释也注明了:
danmaku.priority = 1; //0 表示可能会被各种过滤器过滤并隐藏显示 //1 表示一定会显示, 一般用于本机发送的弹幕
出现设置不生效/弹幕丢失等等情况,都是这个参数为1导致的。弹幕的显示是与时间同步的,有时候会出现同一时间(这个时间是很短的一个时间段(ms级),可以翻翻源码看具体是多少)内有大量弹幕,但是弹幕的速度(这个速度指的是一条弹幕显示完整所用的时长,字数不同会导致速率不同)、屏幕大小、显示大小是固定的,因此能显示的弹幕条数是有限的,多出的就被过滤了。
这里假设极端情况在01s内有200条弹幕,但是12秒内没有弹幕,他是不会将弹幕时间后移显示的,可能因为实现困难和弹幕时间的同步
优先级为1的时候一定会显示,因此会出现设置了3行,但是显示了很多行,因为弹幕太多,3行它放不下啊!只有优先级0的时候过滤条件才会生效。
2. 自定义弹幕样式
BaseDanmaku
支持自定义padding
、textSize
、textColor
、borderColor
等等,但是需求肯定不会如此轻松,一般有两种,加背景或加图标
添加图标比较简单,根据需求使用SpannableStringBuilder
做图文混排就行:
public static SpannableStringBuilder createSpannable(Context context, String s) {
try {
if (s.contains("\n")) {
s = s.replaceAll("\n", " ");
//这里处理了换行符,否则存在换行符时每行都会有图标
}
} catch (Exception e) {
e.printStackTrace();
}
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(s);
Drawable drawable = context.getResources().getDrawable(R.drawable.vip_ico);
drawable.setBounds(0, 0, UIUtils.dip2px(context, 15),
UIUtils.dip2px(context, 15)); //算好padding做个居中处理
ImageSpan span = new CenterAlignImageSpan(drawable);
spannableStringBuilder.setSpan(span, 0, s.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
spannableStringBuilder.append(" ");
spannableStringBuilder.append(s);
spannableStringBuilder.setSpan(new BackgroundColorSpan(Color.TRANSPARENT),
0, spannableStringBuilder.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
return spannableStringBuilder;
}
使用:
danmaku.text = createSpannable(getContext(), text);
自定义样式需要继承SpannedCacheStuffer
重写drawText()
方法可以重写text的样式:
//修改弹幕字体
@Override
public void drawText(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, TextPaint paint, boolean fromWorkerThread) {
Typeface mTypeFace = Typeface.createFromAsset(context.getAssets(), "fonts/ukij_tor.ttf"); //字体文件
paint.setTypeface(mTypeFace);
super.drawText(danmaku, lineText, canvas, left, top, paint, fromWorkerThread);
}
重写drawStroke()
画边框
@Override
public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) {
}
这里有个坑,就是我们其实看到的弹幕上下间距是靠padding实现的,加上边框就很明显能看出来了。但实际上我们的需求一般都会要求边框上线有个间距,也就是margin
,因此想要实现这种效果只能从背景下手, 画一个上下不撑满的圆角矩形:
重写drawBackground()
画背景
@Override
protected void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) {
super.drawBackground(danmaku, canvas, left, top);
if ((boolean) danmaku.tag) { //tag接受Object对象,可以用它来区分需要重绘的弹幕类型
RectF rectF = new RectF(left + 2, top + dp2px(context, 5),
left + danmaku.paintWidth - 2,
top + danmaku.paintHeight - dp2px(context, 5));
canvas.drawRoundRect(rectF, dp2px(context, 13), dp2px(context, 13), paint);
}
}
使用:
danmakuContext.setCacheStuffer(new BackgroundCatchSpanner(this), mCacheStufferAdapter);
其中的mChacheStufferAdapter
中可以进行图片等资源的异步加载:
private BaseCacheStuffer.Proxy mCacheStufferAdapter = new BaseCacheStuffer.Proxy() {
@Override
public void prepareDrawing(final BaseDanmaku danmaku, boolean fromWorkerThread) {
//这个回调会在BaseDanmaku.prepare的时候调用
//弹幕会按时间顺序依次准备,准备完毕之后很快就会进行显示
}
@Override
public void releaseResource(BaseDanmaku danmaku) {
//在弹幕显示完毕后会启动回收流程,这时会调用这个方法,可以进行资源释放
}
};
3. 弹幕控制
基本的start()
,stop()
,pause()
,resume()
,seekTo()
使用都很简单不多讲了
这里要注意的是start(long position)
和seekTo(long position)
的区别,start(position)
移动后position之前的弹幕不会被清除,而seekTo
会有一个清屏的效果,翻源码可以看到seekTo
最终调用了一个清屏的方法,可以分情况使用这两种方式。
@Override
public void reset() {
if (danmakus != null)
danmakus = new Danmakus();
if (mRenderer != null)
mRenderer.clear(); //这里清屏了
}
@Override
public void seek(long mills) {
reset(); //reset
mContext.mGlobalFlagValues.updateVisibleFlag();
mContext.mGlobalFlagValues.updateFirstShownFlag();
mContext.mGlobalFlagValues.updateSyncOffsetTimeFlag();
mContext.mGlobalFlagValues.updatePrepareFlag();
mRunningDanmakus = new Danmakus(Danmakus.ST_BY_LIST);
mStartRenderTime = mills < 1000 ? 0 : mills;
mRenderingState.reset();
mRenderingState.endTime = mStartRenderTime;
mLastBeginMills = mLastEndMills = 0;
if (danmakuList != null) {
BaseDanmaku last = danmakuList.last();
if (last != null && !last.isTimeOut()) {
mLastDanmaku = last;
}
}
}
4. 数据解析
官方给的demo里是解析xml
格式的文件,看的我一脸懵逼,当默默搞懂了官方数据类型及其含义时,反应过来我们的格式其实并不相同…
官方提供了json格式的解析器,将数据处理成InputStream
,在初始化ILoader
的时候使用A站格式即可:
/**
* 创建解析器对象,解析输入流
*
* @param stream
* @return
*/
private BaseDanmakuParser createParser(InputStream stream) {
if (stream == null) {
return new BaseDanmakuParser() {
@Override
protected Danmakus parse() {
return new Danmakus();
}
};
}
//A站是Json格式
ILoader loader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_ACFUN);
try {
loader.load(stream);
} catch (IllegalDataException e) {
e.printStackTrace();
}
MyDanmakuParaser parser = new MyDanmakuParaser(this);
IDataSource<?> dataSource = loader.getDataSource();
parser.load(dataSource);
return parser;
}
重写BaseDanmakuParser
public class MyDanmakuParaser extends BaseDanmakuParser {
protected float mDispScaleX;
protected float mDispScaleY;
private Context context;
public MyDanmakuParaser(Context context){
this.context=context;
}
@Override
protected IDanmakus parse() {
if (mDataSource != null) {
JSONSource source = (JSONSource) mDataSource; //jsonSource
JSONArray jsonArray = source.data();
IDanmakus result = new Danmakus(ST_BY_TIME, false, mContext.getBaseComparator());
for (int i = 0; i < jsonArray.length(); i++) { //在这里将数据取出来设置进去
BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_LR, mContext);
danmaku.padding = MainActivity.dp2px(context,10);
danmaku.priority = 0;
danmaku.textSize = MainActivity.dp2px(context,15);
danmaku.textColor = Color.WHITE;
danmaku.setTimer(mTimer);
// danmaku.borderColor=Color.RED;
danmaku.index=i;
try {
JSONObject object = jsonArray.getJSONObject(i);
danmaku.text = object.optString("text", "");
danmaku.setTime(object.optLong("time", 1000));
danmaku.flags=mContext.mGlobalFlagValues;
} catch (JSONException e) {
e.printStackTrace();
}
result.addItem(danmaku);
}
return result;
}
return null;
}
//从time到最后的弹幕总数 加这个方法是因为我们有个需求是需要开屏显示XX条弹幕来袭
public int getCount(long time){
return getDanmakus().sub(time,Integer.MAX_VALUE).size();
}
public BaseDanmaku getFirst(long time){ //获取第一条弹幕
return getDanmakus().sub(time,Integer.MAX_VALUE).first();
}
@Override
public BaseDanmakuParser setDisplayer(IDisplayer disp) {
super.setDisplayer(disp);
mDispScaleX = mDispWidth / DanmakuFactory.BILI_PLAYER_WIDTH;
mDispScaleY = mDispHeight / DanmakuFactory.BILI_PLAYER_HEIGHT;
return this;
}
}
可以看到弹幕解析主要是parase()
这个方法,看源码可以发现,这个方法是在getDanmakus()
中调用的,最终它是在danmakuView.prepare()
方法中调用
也就是说,可以通过这两个方法更新整体弹幕数据,例如切换视频时可以重新设置弹幕数据并再次调用danmakuView.prepare()
5. 属性调整
以行数为例,原来显示3行,动态改为显示2行:
if (danmakuView != null && danmakuContext != null) {
HashMap<Integer, Integer> maxLinesPair = new HashMap<Integer, Integer>();
maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_LR, 2); // 滚动弹幕最大显示3行
danmakuContext.setMaximumLines(maxLinesPair);
danmakuView.invalidate(); //调用重绘
}
6. 弹幕倍速
danmakuView.setCallback(new DrawHandler.Callback() {
@Override
public void updateTimer(DanmakuTimer timer) {
//倍速
if (danmakuSpeed != 1) {
timer.add((long) (timer.lastInterval() * (danmakuSpeed - 1)));
}
}
...
});
这个倍速就很顺滑
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)