bilibili / DanmakuFlameMaster

Android开源弹幕引擎·烈焰弹幕使 ~
http://app.bilibili.com/
Apache License 2.0
9.53k stars 2.1k forks source link

弹幕过多会发生卡顿 #243

Open li504799868 opened 7 years ago

li504799868 commented 7 years ago

我这里是在做把聊天消息当做弹幕的形式发送出去 ,是带有用户 头像的图文混排, 我把所有的弹幕都设置了danmaku.priority = 1; 但是当数量多一些的时候就会发生卡顿,看fps的话 会降到20多,我直接把demo的代码复制过来还是会卡,但是demo的fps却很稳定,都是在60左右。 我看了其他朋友提的问题,把添加弹幕放到子线程,但是还是会卡顿,求大神帮帮忙,解决这个蛋疼的问题

ctiao commented 7 years ago

加载图片是在哪个线程做的

li504799868 commented 7 years ago

加载图片是用的imageloader,但是这段代码我现在给注释掉了,现在用的就是包里的图片,另外我刚刚还发现一个蛋疼的问题,我开启fps显示,我的app的那个time,竟然不是一秒一秒累加的,可能是几秒

li504799868 commented 7 years ago

public void addDanmaku(final String url, final String text) { NDConfigs.executeTask(new Runnable() {

        @Override
        public void run() {
            BaseDanmaku danmaku = mContext.mDanmakuFactory
                    .createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
            if (danmaku == null) {
                return;
            }
            Drawable drawable = null;
            drawable = context.getResources().getDrawable(
                    R.drawable.default_picture_oval);
            drawable.setBounds(0, 0, imageSize, imageSize);
            danmaku.text = createSpannable(drawable, text);
            // 设置弹幕时间
            danmaku.duration = new Duration(5000);
            danmaku.padding = 5;
            danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕
            danmaku.isLive = false;
            danmaku.setTime(getCurrentTime() + 1000);
            danmaku.textSize = textSize;
            danmaku.textColor = Color.WHITE;
            danmaku.textShadowColor = 0; //
            // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低
            addDanmaku(danmaku);
        }
    });
}
li504799868 commented 7 years ago

我在详细的说一下我这里的情况吧 , 弹幕是浮在视频的上面, 视频的控件是使用的ijkplayer ,不知道是不是这两个在一起有互相影响了呢

ctiao commented 7 years ago

能否提供能重现的demo代码看看 另外不要对duration赋值

        danmaku.duration = new Duration(5000);
li504799868 commented 7 years ago

恩 我现在试试 稍等一会

li504799868 commented 7 years ago

我把demo怎么发给你呢 我现在把ijkplayer放进去播放视频,然后不停的点击图文 ,卡顿就很明显了

li504799868 commented 7 years ago

package com.sample;

import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.HashMap; import java.util.Timer; import java.util.TimerTask;

import master.flame.danmaku.controller.IDanmakuView; import master.flame.danmaku.danmaku.loader.ILoader; import master.flame.danmaku.danmaku.loader.IllegalDataException; import master.flame.danmaku.danmaku.loader.android.DanmakuLoaderFactory; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.DanmakuTimer; import master.flame.danmaku.danmaku.model.IDanmakus; import master.flame.danmaku.danmaku.model.IDisplayer; import master.flame.danmaku.danmaku.model.android.BaseCacheStuffer; import master.flame.danmaku.danmaku.model.android.DanmakuContext; import master.flame.danmaku.danmaku.model.android.Danmakus; import master.flame.danmaku.danmaku.model.android.SpannedCacheStuffer; import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.parser.IDataSource; import master.flame.danmaku.danmaku.parser.android.BiliDanmukuParser; import master.flame.danmaku.danmaku.util.IOUtils; import master.flame.danmaku.danmaku.util.SystemClock; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IMediaPlayer.OnPreparedListener; import tv.danmaku.ijk.media.player.IjkMediaPlayer; import android.app.Activity; import android.content.pm.ActivityInfo; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.style.BackgroundColorSpan; import android.text.style.ImageSpan; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.Button; import android.widget.PopupWindow;

public class MainActivity extends Activity implements View.OnClickListener {

private IDanmakuView mDanmakuView;

private View mMediaController;

public PopupWindow mPopupWindow;

private Button mBtnRotate;

private Button mBtnHideDanmaku;

private Button mBtnShowDanmaku;

private BaseDanmakuParser mParser;

private Button mBtnPauseDanmaku;

private Button mBtnResumeDanmaku;

private Button mBtnSendDanmaku;

private Button mBtnSendDanmakuTextAndImage;

private Button mBtnSendDanmakus;
private DanmakuContext mContext;
private BaseCacheStuffer.Proxy mCacheStufferAdapter = new BaseCacheStuffer.Proxy() {

    private Drawable mDrawable;

    @Override
    public void prepareDrawing(final BaseDanmaku danmaku,
            boolean fromWorkerThread) {
        if (danmaku.text instanceof Spanned) { // 根据你的条件检查是否需要需要更新弹幕
            // FIXME 这里只是简单启个线程来加载远程url图片,请使用你自己的异步线程池,最好加上你的缓存池
            new Thread() {

                @Override
                public void run() {
                    String url = "http://www.bilibili.com/favicon.ico";
                    InputStream inputStream = null;
                    Drawable drawable = mDrawable;
                    if (drawable == null) {
                        try {
                            URLConnection urlConnection = new URL(url)
                                    .openConnection();
                            inputStream = urlConnection.getInputStream();
                            drawable = BitmapDrawable.createFromStream(
                                    inputStream, "bitmap");
                            mDrawable = drawable;
                        } catch (MalformedURLException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            IOUtils.closeQuietly(inputStream);
                        }
                    }
                    if (drawable != null) {
                        drawable.setBounds(0, 0, 100, 100);
                        SpannableStringBuilder spannable = createSpannable(drawable);
                        danmaku.text = spannable;
                        if (mDanmakuView != null) {
                            mDanmakuView.invalidateDanmaku(danmaku, false);
                        }
                        return;
                    }
                }
            }.start();
        }
    }

    @Override
    public void releaseResource(BaseDanmaku danmaku) {
        // TODO 重要:清理含有ImageSpan的text中的一些占用内存的资源 例如drawable
    }
};

/**
 * 绘制背景(自定义弹幕样式)
 */
private static class BackgroundCacheStuffer extends SpannedCacheStuffer {
    // 通过扩展SimpleTextCacheStuffer或SpannedCacheStuffer个性化你的弹幕样式
    final Paint paint = new Paint();

    @Override
    public void measure(BaseDanmaku danmaku, TextPaint paint,
            boolean fromWorkerThread) {
        danmaku.padding = 10; // 在背景绘制模式下增加padding
        super.measure(danmaku, paint, fromWorkerThread);
    }

    @Override
    public void drawBackground(BaseDanmaku danmaku, Canvas canvas,
            float left, float top) {
        paint.setColor(0x8125309b);
        canvas.drawRect(left + 2, top + 2, left + danmaku.paintWidth - 2,
                top + danmaku.paintHeight - 2, paint);
    }

    @Override
    public void drawStroke(BaseDanmaku danmaku, String lineText,
            Canvas canvas, float left, float top, Paint paint) {
        // 禁用描边绘制
    }
}

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

private BaseDanmakuParser createParser(InputStream stream) {

    if (stream == null) {
        return new BaseDanmakuParser() {

            @Override
            protected Danmakus parse() {
                return new Danmakus();
            }
        };
    }

    ILoader loader = DanmakuLoaderFactory
            .create(DanmakuLoaderFactory.TAG_BILI);

    try {
        loader.load(stream);
    } catch (IllegalDataException e) {
        e.printStackTrace();
    }
    BaseDanmakuParser parser = new BiliDanmukuParser();
    IDataSource<?> dataSource = loader.getDataSource();
    parser.load(dataSource);
    return parser;

}

private void findViews() {

    mMediaController = findViewById(R.id.media_controller);
    mBtnRotate = (Button) findViewById(R.id.rotate);
    mBtnHideDanmaku = (Button) findViewById(R.id.btn_hide);
    mBtnShowDanmaku = (Button) findViewById(R.id.btn_show);
    mBtnPauseDanmaku = (Button) findViewById(R.id.btn_pause);
    mBtnResumeDanmaku = (Button) findViewById(R.id.btn_resume);
    mBtnSendDanmaku = (Button) findViewById(R.id.btn_send);
    mBtnSendDanmakuTextAndImage = (Button) findViewById(R.id.btn_send_image_text);
    mBtnSendDanmakus = (Button) findViewById(R.id.btn_send_danmakus);
    mBtnRotate.setOnClickListener(this);
    mBtnHideDanmaku.setOnClickListener(this);
    mMediaController.setOnClickListener(this);
    mBtnShowDanmaku.setOnClickListener(this);
    mBtnPauseDanmaku.setOnClickListener(this);
    mBtnResumeDanmaku.setOnClickListener(this);
    mBtnSendDanmaku.setOnClickListener(this);
    mBtnSendDanmakuTextAndImage.setOnClickListener(this);
    mBtnSendDanmakus.setOnClickListener(this);
    // init player
    IjkMediaPlayer.loadLibrariesOnce(null);
    IjkMediaPlayer.native_profileBegin("libijkplayer.so");
    // VideoView
    final IjkVideoView mVideoView = (IjkVideoView) findViewById(R.id.videoview);
    // DanmakuView

    // 设置最大显示行数
    HashMap<Integer, Integer> maxLinesPair = new HashMap<Integer, Integer>();
    maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 5); // 滚动弹幕最大显示5行
    // 设置是否禁止重叠
    HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<Integer, Boolean>();
    overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
    overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);

    mDanmakuView = (IDanmakuView) findViewById(R.id.sv_danmaku);
    mContext = DanmakuContext.create();
    mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3)
            .setDuplicateMergingEnabled(false)
            .setScrollSpeedFactor(1.2f)
            .setScaleTextSize(1.2f)
            .setCacheStuffer(new SpannedCacheStuffer(),
                    mCacheStufferAdapter)
            // 图文混排使用SpannedCacheStuffer
            // .setCacheStuffer(new BackgroundCacheStuffer()) //
            // 绘制背景使用BackgroundCacheStuffer
            .setMaximumLines(maxLinesPair)
            .preventOverlapping(overlappingEnablePair);
    if (mDanmakuView != null) {
        mParser = createParser(this.getResources().openRawResource(
                R.raw.comments));
        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("DFM", "danmakuShown(): text=" +
                        // danmaku.text);
                    }

                    @Override
                    public void prepared() {
                        mDanmakuView.start();
                    }
                });
        mDanmakuView
                .setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() {

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

                    @Override
                    public boolean onViewClick(IDanmakuView view) {
                        mMediaController.setVisibility(View.VISIBLE);
                        return false;
                    }
                });
        mDanmakuView.prepare(mParser, mContext);
        mDanmakuView.showFPS(true);
        mDanmakuView.enableDanmakuDrawingCache(true);
    }

    if (mVideoView != null) {
        mVideoView.setOnPreparedListener(new OnPreparedListener() {

            @Override
            public void onPrepared(IMediaPlayer mp) {
                mVideoView.start();
            }
        });
        mVideoView
                .setVideoPath("http://bj.video.baladiguo.com/video_480/4bd706ed230deebe5696dde3b592b851.mp4?OSSAccessKeyId=0EBRRPeWqt4QcQ7s&Expires=1477558360&Signature=6rtvkZFeVxuuCTPNXp4wKCHKXiQ%3D");
    }

}

@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
public void onBackPressed() {
    super.onBackPressed();
    if (mDanmakuView != null) {
        // dont forget release!
        mDanmakuView.release();
        mDanmakuView = null;
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public void onClick(View v) {
    if (v == mMediaController) {
        mMediaController.setVisibility(View.GONE);
    }
    if (mDanmakuView == null || !mDanmakuView.isPrepared())
        return;
    if (v == mBtnRotate) {
        setRequestedOrientation(getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
                : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    } else if (v == mBtnHideDanmaku) {
        mDanmakuView.hide();
        // mPausedPosition = mDanmakuView.hideAndPauseDrawTask();
    } else if (v == mBtnShowDanmaku) {
        mDanmakuView.show();
        // mDanmakuView.showAndResumeDrawTask(mPausedPosition); // sync to
        // the video time in your practice
    } else if (v == mBtnPauseDanmaku) {
        mDanmakuView.pause();
    } else if (v == mBtnResumeDanmaku) {
        mDanmakuView.resume();
    } else if (v == mBtnSendDanmaku) {
        addDanmaku(false);
    } else if (v == mBtnSendDanmakuTextAndImage) {
        addDanmaKuShowTextAndImage(false);
    } else if (v == mBtnSendDanmakus) {
        Boolean b = (Boolean) mBtnSendDanmakus.getTag();
        timer.cancel();
        if (b == null || !b) {
            mBtnSendDanmakus.setText(R.string.cancel_sending_danmakus);
            timer = new Timer();
            timer.schedule(new AsyncAddTask(), 0, 1000);
            mBtnSendDanmakus.setTag(true);
        } else {
            mBtnSendDanmakus.setText(R.string.send_danmakus);
            mBtnSendDanmakus.setTag(false);
        }
    }
}

Timer timer = new Timer();

class AsyncAddTask extends TimerTask {

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            addDanmaku(true);
            SystemClock.sleep(20);
        }
    }
};

private void addDanmaku(boolean islive) {
    BaseDanmaku danmaku = mContext.mDanmakuFactory
            .createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
    if (danmaku == null || mDanmakuView == null) {
        return;
    }
    // for(int i=0;i<100;i++){
    // }
    danmaku.text = "这是一条弹幕" + System.nanoTime();
    danmaku.padding = 5;
    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.GREEN;
    mDanmakuView.addDanmaku(danmaku);

}

private void addDanmaKuShowTextAndImage(boolean islive) {
    BaseDanmaku danmaku = mContext.mDanmakuFactory
            .createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
    Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher);
    drawable.setBounds(0, 0, 100, 100);
    SpannableStringBuilder spannable = createSpannable(drawable);
    danmaku.text = spannable;
    danmaku.padding = 5;
    danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕
    danmaku.isLive = islive;
    danmaku.setTime(mDanmakuView.getCurrentTime() + 1200);
    danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f);
    danmaku.textColor = Color.RED;
    danmaku.textShadowColor = 0; // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低
    danmaku.underlineColor = Color.GREEN;
    mDanmakuView.addDanmaku(danmaku);
}

private SpannableStringBuilder createSpannable(Drawable drawable) {
    String text = "bitmap";
    SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(
            text);
    ImageSpan span = new ImageSpan(drawable);// ImageSpan.ALIGN_BOTTOM);
    spannableStringBuilder.setSpan(span, 0, text.length(),
            Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    spannableStringBuilder.append("图文混排");
    spannableStringBuilder.setSpan(
            new BackgroundColorSpan(Color.parseColor("#8A2233B1")), 0,
            spannableStringBuilder.length(),
            Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    return spannableStringBuilder;
}

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

}

里面使用的ijkplayer就是官方的ijkplayer的demo里的文件 ,直接复制过来的,如果需要demo加我的qq就是我的名字的号码

li504799868 commented 7 years ago

我刚刚又仔细测试了一下 发现了一个问题 如果ijkplayer使用了 // init player IjkMediaPlayer.loadLibrariesOnce(null); IjkMediaPlayer.native_profileBegin("libijkplayer.so");

弹幕的卡顿就会明显很多 ,希望能够帮助你们排查问题

ctiao commented 7 years ago

ijk开启硬解码了吗

li504799868 commented 7 years ago

这个方法是开启硬解码的吗 ……我也不清楚 但是确实是调用了之后卡顿明显

ctiao commented 7 years ago

不是,我的意思是开启ijk硬解码,会减少弹幕卡顿。 你的情况很有可能是因为软解播放耗性能影响了弹幕

li504799868 commented 7 years ago

……低调的问一句怎么开启硬解码

li504799868 commented 7 years ago

ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);

是这个设置吗