Android 直播 彈幕

彈幕使用場景
  1. 直播(實時性)

彈幕是直播系統的核心功能之一。一段摘抄的直播彈幕描述(貌似美拍工程師寫的)

  • 美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線
  • 前中期使用 HTTP 輪詢方案,中後期替換爲長連接方案
  • 直播間消息,相對於 IM 的場景,有如下幾個特點:
  • 消息要求及時,過時的消息對於用戶來說不重要
  • 鬆散的羣聊,用戶隨時進羣,隨時退羣
  • 用戶進羣后,離線期間(接聽電話)的消息不需要重發
  1. 視頻(實時性 + 彈幕跟幀時間點關聯)
彈幕引擎(B站開源彈幕)

GitHub:https://github.com/bilibili/DanmakuFlameMaster

DanmakuFlameMaster 是 Android 上開源彈幕解析繪製引擎項目,貌似Android 上最好的開源彈幕引擎·烈焰彈幕

DanmakuFlameMaster 開發包已被包括優酷土豆、開迅視頻、MissEvan、echo回聲、鬥魚TV、天天動聽、被窩聲次元、ACFUN 等 APP 使用

DanmakuFlameMaster 特點
  1. 使用多種方式(View/SurfaceView/TextureView)實現高效繪製
  2. B站xml彈幕格式解析
  3. 基礎彈幕精確還原繪製
  4. 支持mode7特殊彈幕
  5. 多核機型優化,高效的預緩存機制
  6. 支持多種顯示效果選項實時切換
  7. 實時彈幕顯示支持
  8. 換行彈幕支持/運動彈幕支持
  9. 支持自定義字體
  10. 支持多種彈幕參數設置
  11. 支持多種方式的彈幕屏蔽
DanmakuFlameMaster 細節API
  1. 模式在BaseDanmaku裏有聲明,總結一下就是
  	public final static int TYPE_SCROLL_RL = 1; // 水平右往左

    public final static int TYPE_SCROLL_LR = 6; // 水平左往右

    public final static int TYPE_FIX_TOP = 5; // 固定在頂部

    public final static int TYPE_FIX_BOTTOM = 4; // 固定在底部

    public final static int TYPE_SPECIAL = 7; // 特殊彈幕
  1. 添加一條圖文混合彈幕
    請參考:DanmuKuDemo

  2. 展示隱藏彈幕

// 實現了IDanmakuView接口的彈幕View,調用下面2個方法展示或隱藏彈幕
void show();
void hide();
  1. 如何監聽每一個彈幕的移動的x,y位置

查看一下BaseDanmaku.layout方法的實現就明白了,彈幕庫每次繪製前都會調用這個方法

集成B站彈幕
  1. 集成配置
repositories {
    jcenter()
}

// 如果你不需要兼容x86和armv5就不用添加最下面兩行的內容了
dependencies {
    api 'com.github.ctiao:DanmakuFlameMaster:0.9.25'
    api 'com.github.ctiao:ndkbitmap-armv7a:0.9.21'

    // Other ABIs: optional 這個是適配多種架構的,如果你用虛擬機建議加上
    api 'com.github.ctiao:ndkbitmap-armv5:0.9.21'
    api 'com.github.ctiao:ndkbitmap-x86:0.9.21'
}
  1. DanmakuFlameMaster 使用多種方式(View / SurfaceView / TextureView)實現高效繪製!其中分別對應(DanmakuView/DanmakuSurfaceView/DanmakuTextureView)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

</RelativeLayout>
  1. 代碼配置
 		// 設置最大顯示行數(滾動彈幕最大顯示行數)
        HashMap<Integer, Integer> maxLinesPair = new HashMap<>();
        maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 2);

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

        mContext = DanmakuContext.create();
        mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3)
                .setDuplicateMergingEnabled(false)
                .setScrollSpeedFactor(1.2f)
                // 設置文字的比例
                .setScaleTextSize(1.2f)
                // 圖文混排使用 SpannedCacheStuffer
                .setCacheStuffer(new SpannedCacheStuffer(), mCacheStufferAdapter) 
				// 繪製背景使用 BackgroundCacheStuffer
				.setCacheStuffer(new BackgroundCacheStuffer())  
                .setMaximumLines(maxLinesPair)
                .preventOverlapping(overlappingEnablePair).setDanmakuMargin(40);

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

                @Override
                public void drawingFinished() {
                    // 彈幕繪製完成時回掉(即彈幕展示完成移出屏幕時調用)
                    Log.i(TAG, "drawingFinished");
                }

                @Override
                public void danmakuShown(BaseDanmaku danmaku) {
                    Log.i(TAG, "danmakuShown " + danmaku.text);
                }

                @Override
                public void prepared() {
                    // 彈幕準備好的時候回掉,這裏啓動彈幕
                    Log.i(TAG, "prepared");
                    mDanmakuView.start();
                }
            });
            mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() {

                @Override
                public boolean onDanmakuClick(IDanmakus danmakus) {
                	Log.i(TAG, "onDanmakuClick danmakus size: " + danmakus.size());
                    BaseDanmaku latest = danmakus.last();
                    if (null != latest) {
                    Log.i(TAG, "onDanmakuClick text of latest danmaku: " + latest.text);
                        return true;
                    }
                    return false;
                }

                @Override
                public boolean onDanmakuLongClick(IDanmakus danmakus) {
                    return false;
                }

                @Override
                public boolean onViewClick(IDanmakuView view) {
                    return false;
                }
            });
            mDanmakuView.prepare(mParser, mContext);
            mDanmakuView.showFPS(true);
            mDanmakuView.enableDanmakuDrawingCache(true);
        }
  1. 直播頁面按Home鍵退後臺或重新回到前臺,相關的生命週期方法必須設置
// View 實現 彈幕
private IDanmakuView mDanmakuView;

@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;
        }
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (mDanmakuView != null) {
            // dont forget release!
            mDanmakuView.release();
            mDanmakuView = null;
        }
    }
  1. 添加一條普通文字彈幕
/**
* danmaku.isLive == true的情況下,請在非UI線程中使用此方法,避免可能卡住主線程
* @param item
*/
void addDanmaku(BaseDanmaku item);


private void addDanmaku(boolean isLive) {

        // 創建一個彈幕對象,這裏後面的屬性是設置滾動方向的!
        BaseDanmaku danmaku =
                mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
        if (danmaku == null || mDanmakuView == null) {
            return;
        }
        danmaku.text = "這是一條彈幕";

        // 設置相應的邊距,這個設置的是四周的邊距
        danmaku.padding = 5;

        // 可能會被各種過濾器過濾並隱藏顯示,若是本機發送的彈幕,建議設置成1;
        danmaku.priority = 0;

        // 是否是直播彈幕
        danmaku.isLive = isLive;

        danmaku.setTime(mDanmakuView.getCurrentTime() + 1200);

        danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f);

        // 設置文字顏色
        danmaku.textColor = Color.RED;

        // 設置陰影的顏色
        danmaku.textShadowColor = Color.WHITE;

        // danmaku.underlineColor = Color.GREEN;
        danmaku.borderColor = Color.RED;

        // 添加這條彈幕,也就相當於發送
        mDanmakuView.addDanmaku(danmaku);

    }
    

B站集成常見問題

參考
  1. 關於B站的彈幕集成 01
  2. 關於B站的彈幕集成 02
  3. DanmuKuDemo
  4. Android彈幕功能實現,模仿鬥魚直播的彈幕效果 (郭霖blog)
  5. 自己研究寫的一個彈幕庫的功能
  6. DanmuKuDemo
  7. 開源彈幕引擎·烈焰彈幕使(DanmakuFlameMaster)使用解析
  8. Android彈幕實現:基於B站彈幕開源系統(1) 重點關注一下
  9. B站彈幕集成常見問題
  10. DanmakuFlameMaster 使用小結
  11. 彈幕框架DanmakuFlameMaster簡單分析
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章