Android DanmakuFlameMaster的踩坑方式

这里根据我用到过的功能填一些坑,但目前也只用到了部分功能,弹幕数据动态衔接的问题还没有遇到,以后遇到添加进来

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支持自定义paddingtextSizetextColorborderColor等等,但是需求肯定不会如此轻松,一般有两种,加背景加图标

添加图标比较简单,根据需求使用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)));
                        }
                    }
  ...
 });

这个倍速就很顺滑

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值