Android弹幕实现:基于B站弹幕开源系统(1)

简介: Android弹幕实现:基于B站弹幕开源系统(1)如今的视频播放,流行在视频上飘弹幕。这里面做的相对比较成熟、稳定、使用量较多的弹幕系统,当推B站的弹幕系统,B站的弹幕系统已经作为开源项目在github上,其项目地址:https://github.com/Bilibili/DanmakuFlameMaster 以B站开源的弹幕项目为基础,现给出一个简单的例子,实现发送简单的文本弹幕。


Android弹幕实现:基于B站弹幕开源系统(1)

如今的视频播放,流行在视频上飘弹幕。这里面做的相对比较成熟、稳定、使用量较多的弹幕系统,当推B站的弹幕系统,B站的弹幕系统已经作为开源项目在github上,其项目地址:https://github.com/Bilibili/DanmakuFlameMaster
以B站开源的弹幕项目为基础,现给出一个简单的例子,实现发送简单的文本弹幕。
第一步,首先要在Android的build.gradle文件中引入B站的项目:

repositories {
    	jcenter()
}


dependencies {

	compile 'com.github.ctiao:DanmakuFlameMaster:0.7.3'
	compile 'com.github.ctiao:ndkbitmap-armv7a:0.7.3'

}


第二步,写一个布局文件,引入B站的弹幕view:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示弹幕" />

    <Button
        android:id="@+id/hide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="隐藏弹幕" />

    <Button
        android:id="@+id/sendText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送文本弹幕" />

    <Button
        android:id="@+id/pause"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暂停弹幕" />

    <Button
        android:id="@+id/resume"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="重启弹幕" />

    <master.flame.danmaku.ui.widget.DanmakuView
        android:id="@+id/danmakuView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


第三步,写上层Java代码(该处java代码改造自B站弹幕github上的demo代码):

package zhangphil.danmaku;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.util.HashMap;

import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.model.DanmakuTimer;
import master.flame.danmaku.danmaku.model.IDisplayer;
import master.flame.danmaku.danmaku.model.android.DanmakuContext;
import master.flame.danmaku.ui.widget.DanmakuView;

public class MainActivity extends Activity {

    private DanmakuView mDanmakuView;
    private DanmakuContext mContext;

    private AcFunDanmakuParser mParser;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mDanmakuView = (DanmakuView) findViewById(R.id.danmakuView);

        Button show = (Button) findViewById(R.id.show);
        Button hide = (Button) findViewById(R.id.hide);
        Button sendText = (Button) findViewById(R.id.sendText);
        Button pause = (Button) findViewById(R.id.pause);
        Button resume = (Button) findViewById(R.id.resume);

        show.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.show();
            }
        });

        hide.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.hide();
            }
        });

        sendText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //每点击一次按钮发送一条弹幕
                sendTextMessage();
            }
        });

        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.pause();
            }
        });

        resume.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.resume();
            }
        });


        init();
    }

    private void init() {
        mContext = DanmakuContext.create();

        // 设置最大显示行数
        HashMap<Integer, Integer> maxLinesPair = new HashMap<>();
        maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 8); // 滚动弹幕最大显示5行

        // 设置是否禁止重叠
        HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<>();
        overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
        overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);

        mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 10) //描边的厚度
                .setDuplicateMergingEnabled(false)
                .setScrollSpeedFactor(1.2f) //弹幕的速度。注意!此值越小,速度越快!值越大,速度越慢。// by phil
                .setScaleTextSize(1.2f)  //缩放的值
                //.setCacheStuffer(new SpannedCacheStuffer(), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer
//        .setCacheStuffer(new BackgroundCacheStuffer())  // 绘制背景使用BackgroundCacheStuffer
                .setMaximumLines(maxLinesPair)
                .preventOverlapping(overlappingEnablePair);

        mParser = new AcFunDanmakuParser();
        mDanmakuView.prepare(mParser, mContext);

        //mDanmakuView.showFPS(true);
        mDanmakuView.enableDanmakuDrawingCache(true);

        if (mDanmakuView != null) {
            mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() {
                @Override
                public void updateTimer(DanmakuTimer timer) {
                }

                @Override
                public void drawingFinished() {

                }

                @Override
                public void danmakuShown(BaseDanmaku danmaku) {
                    Log.d("弹幕文本", "danmakuShown text=" + danmaku.text);
                }

                @Override
                public void prepared() {
                    mDanmakuView.start();
                }
            });
        }
    }

    private void sendTextMessage() {
        addDanmaku(true);
    }

    private void addDanmaku(boolean islive) {
        BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
        if (danmaku == null || mDanmakuView == null) {
            return;
        }

        danmaku.text = "zhangphil @ csdn :" + System.currentTimeMillis();
        danmaku.padding = 5;
        danmaku.priority = 0;  // 可能会被各种过滤器过滤并隐藏显示
        danmaku.isLive = islive;
        danmaku.setTime(mDanmakuView.getCurrentTime() + 1200);
        danmaku.textSize = 20f * (mParser.getDisplayer().getDensity() - 0.6f); //文本弹幕字体大小
        danmaku.textColor = getRandomColor(); //文本的颜色
        danmaku.textShadowColor = getRandomColor(); //文本弹幕描边的颜色
        //danmaku.underlineColor = Color.DKGRAY; //文本弹幕下划线的颜色
        danmaku.borderColor = getRandomColor(); //边框的颜色

        mDanmakuView.addDanmaku(danmaku);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mDanmakuView != null && mDanmakuView.isPrepared()) {
            mDanmakuView.pause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
            mDanmakuView.resume();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDanmakuView != null) {
            // dont forget release!
            mDanmakuView.release();
            mDanmakuView = null;
        }
    }

    /**
     * 从一系列颜色中随机选择一种颜色
     *
     * @return
     */
    private int getRandomColor() {
        int[] colors = {Color.RED, Color.YELLOW, Color.BLUE, Color.GREEN, Color.CYAN, Color.BLACK, Color.DKGRAY};
        int i = ((int) (Math.random() * 10)) % colors.length;
        return colors[i];
    }
}


代码运行结果如图:



需要特别注意的是本例使用了一个叫做AcFunDanmakuParser的弹幕parser,这个解析器得自己写,自己基于json数据格式实现。该类写好基本就可以拿来稳定使用,现给出AcFunDanmakuParser的全部源代码:

package zhangphil.danmaku;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.model.android.Danmakus;
import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
import master.flame.danmaku.danmaku.parser.android.JSONSource;
import master.flame.danmaku.danmaku.util.DanmakuUtils;

/**
 * Created by phil on 2017/3/29.
 */

public class AcFunDanmakuParser extends BaseDanmakuParser {

    public AcFunDanmakuParser() {

    }

    public Danmakus parse() {
        if (this.mDataSource != null && this.mDataSource instanceof JSONSource) {
            JSONSource jsonSource = (JSONSource) this.mDataSource;
            return this.doParse(jsonSource.data());
        } else {
            return new Danmakus();
        }
    }

    private Danmakus doParse(JSONArray danmakuListData) {
        Danmakus danmakus = new Danmakus();
        if (danmakuListData != null && danmakuListData.length() != 0) {
            for (int i = 0; i < danmakuListData.length(); ++i) {
                try {
                    JSONObject e = danmakuListData.getJSONObject(i);
                    if (e != null) {
                        danmakus = this._parse(e, danmakus);
                    }
                } catch (JSONException var5) {
                    var5.printStackTrace();
                }
            }

            return danmakus;
        } else {
            return danmakus;
        }
    }

    private Danmakus _parse(JSONObject jsonObject, Danmakus danmakus) {
        if (danmakus == null) {
            danmakus = new Danmakus();
        }

        if (jsonObject != null && jsonObject.length() != 0) {
            for (int i = 0; i < jsonObject.length(); ++i) {
                try {
                    String c = jsonObject.getString("c");
                    String[] values = c.split(",");
                    if (values.length > 0) {
                        int type = Integer.parseInt(values[2]);
                        if (type != 7) {
                            long time = (long) (Float.parseFloat(values[0]) * 1000.0F);
                            int color = Integer.parseInt(values[1]) | -16777216;
                            float textSize = Float.parseFloat(values[3]);
                            BaseDanmaku item = this.mContext.mDanmakuFactory.createDanmaku(type, this.mContext);
                            if (item != null) {
                                item.setTime(time);
                                item.textSize = textSize * (this.mDispDensity - 0.6F);
                                item.textColor = color;
                                item.textShadowColor = color <= -16777216 ? -1 : -16777216;
                                DanmakuUtils.fillText(item, jsonObject.optString("m", "...."));
                                item.index = i;
                                item.setTimer(this.mTimer);
                                danmakus.addItem(item);
                            }
                        }
                    }
                } catch (JSONException var13) {

                } catch (NumberFormatException var14) {

                }
            }

            return danmakus;
        } else {
            return danmakus;
        }
    }
}

相关文章
|
23天前
|
搜索推荐 Android开发 iOS开发
安卓与iOS系统的用户界面设计对比分析
本文通过对安卓和iOS两大操作系统的用户界面设计进行对比分析,探讨它们在设计理念、交互方式、视觉风格等方面的差异及各自特点,旨在帮助读者更好地理解和评估不同系统的用户体验。
18 1
|
2月前
|
搜索推荐 Android开发 iOS开发
探析安卓与iOS系统的优劣
【2月更文挑战第7天】安卓与iOS是当今手机市场上最主流的两款操作系统,各有优劣。本文将从用户体验、开放程度、生态系统等方面对两者进行深入探析,以期帮助读者更好地了解它们的特点。
|
2月前
|
Android开发 数据安全/隐私保护 iOS开发
安卓与iOS系统的发展趋势与比较分析
【2月更文挑战第6天】 在移动互联网时代,安卓和iOS系统作为两大主流移动操作系统,各自呈现出不同的发展趋势。本文将从技术角度出发,对安卓和iOS系统的发展方向、特点及未来趋势进行比较分析,以期为读者提供更深入的了解和思考。
33 4
|
3月前
|
Linux 调度 Android开发
【系统启动】Kernel怎么跳转到Android:linux与安卓的交界
【系统启动】Kernel怎么跳转到Android:linux与安卓的交界
46 0
|
4月前
|
JSON 自然语言处理 Java
Android App开发语音处理之系统自带的语音引擎、文字转语音、语音识别的讲解及实战(超详细 附源码)
Android App开发语音处理之系统自带的语音引擎、文字转语音、语音识别的讲解及实战(超详细 附源码)
120 0
|
17天前
|
机器学习/深度学习 人工智能 搜索推荐
探索安卓应用中的新趋势:人工智能驱动的智能推荐系统
传统的应用推荐系统已经无法满足用户日益增长的个性化需求。本文将探讨如何通过引入人工智能技术,构建智能推荐系统,为用户提供更加精准、个性化的应用推荐体验,进而提升应用的用户满意度和留存率。
16 0
|
1月前
|
搜索推荐 测试技术 定位技术
基于Android的自助导游系统的设计与实现(论文+源码)_kaic
基于Android的自助导游系统的设计与实现(论文+源码)_kaic
|
1月前
|
搜索推荐 安全 Android开发
安卓与iOS系统的用户体验比较
【2月更文挑战第11天】 在当今移动设备市场上,安卓和iOS系统一直是两大主流操作系统。本文将从用户界面设计、应用生态、系统定制性等方面对安卓和iOS系统进行比较分析,旨在探讨两者的优势和劣势,为用户选择合适的操作系统提供参考。
|
2月前
|
人工智能 vr&ar Android开发
探索安卓与iOS系统的发展趋势
【2月更文挑战第9天】 过去,人们对于安卓和iOS系统的争论主要集中在性能、用户体验和生态系统的比较上。然而,随着移动互联网的快速发展,两大操作系统在人工智能、物联网、安全性等方面的发展趋势也备受关注。本文将探讨安卓与iOS系统在技术发展方面的差异以及未来的发展趋势。