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; } } }
作者:zhangphil 发表于2017/3/29 17:07:42 原文链接
阅读:152 评论:0 查看评论