• Android弹幕实现原理


    一、首先给不愿看博客的同学附上Demo源码的链接:

    点击此处下载安卓弹幕Demo

    二、弹幕原理的简单解析
    1.我们先来做些准备工作。
    (1)我们可能会需要一个视频(我在这里找了一个mp4格式的视频,并放在了res/raw目录下面,因为音频和视频文件放在其它目录(例如assets资源目录)下会导致无法使用,对这一部分有兴趣的同学可以去查资料了解一下)。

    (2)需要将界面设置为横屏,并对它进行一些配置。下面是清单文件的代码。

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.mythmayor.a1805danmudemo">
    
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".MainActivity"
                android:configChanges="orientation|keyboardHidden|screenLayout|screenSize"
                android:screenOrientation="landscape">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    (3)我在这里用到了哔哩哔哩开源的弹幕效果库DanmakuFlameMaster(这个库的功能也比较强大,但本篇文章中可能只用到其基本功能,有兴趣的同学可以到其GitHub上进行学习)。需要配置到build.gradle的dependencies中。DanmakuFlameMaster库的项目主页地址是:https://github.com/Bilibili/DanmakuFlameMaster

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
        testCompile 'junit:junit:4.12'
        compile 'com.github.ctiao:DanmakuFlameMaster:0.9.25'
    }

    2.在观看直播或视频的时候,我们经常能看到弹幕的效果。首先我们从布局上讲一下,其实非常简单,布局最下层是播放器视图,中间那层一般则是弹幕视图层,最上层是操作界面的视图层。这样一说大家的心里是不是就有一个很清晰的结构了。那么下面就直接上布局的代码了。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000000"
        tools:context="com.mythmayor.a1805danmudemo.MainActivity">
    
        <VideoView
            android:id="@+id/video_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true" />
    
        <master.flame.danmaku.ui.widget.DanmakuView
            android:id="@+id/danmaku_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <LinearLayout
            android:id="@+id/operation_layout"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:background="#fff"
            android:visibility="gone">
    
            <EditText
                android:id="@+id/edit_text"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1" />
    
            <Button
                android:id="@+id/send"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="Send" />
        </LinearLayout>
    </RelativeLayout>

    3.核心代码就要来了。在这里有几点是需要说明的。
    (1)首先播放视频的话这里用到的是VideoView,使用起来也非常简单,先要设置一个视频文件的路径:String uri = “android.resource://” + getPackageName() + “/” + R.raw.danmu;然后调用start方法即可播放视频了。
    (2)关于弹幕库的使用,可参考下面代码进行理解。我们需要创建一个DanmakuContext的实例和一个弹幕的解析器(这里直接创建了一个全局的BaseDanmakuParser),创建完成后就可以调用DanmakuView的prepare()方法了,调用这一方法后会自动调用回调函数中的prepared()方法,这个方法中调用了start方法,弹幕就此开始工作了。
    (3)需要在onPause()、onResume()、onDestroy()方法中执行一些操作,以保证DanmakuView的资源可以得到释放。
    (4)下面附上完整代码。

    package com.mythmayor.a1805danmudemo;
    
    import android.app.Activity;
    import android.graphics.Color;
    import android.os.Build;
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.LinearLayout;
    import android.widget.VideoView;
    
    import java.util.Random;
    
    import master.flame.danmaku.controller.DrawHandler;
    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.android.DanmakuContext;
    import master.flame.danmaku.danmaku.model.android.Danmakus;
    import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
    import master.flame.danmaku.ui.widget.DanmakuView;
    
    public class MainActivity extends Activity {
    
        private boolean showDanmaku;
        private DanmakuView danmakuView;
        private DanmakuContext danmakuContext;
        private BaseDanmakuParser parser = new BaseDanmakuParser() {
            @Override
            protected IDanmakus parse() {
                return new Danmakus();
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            VideoView videoview = (VideoView) findViewById(R.id.video_view);
            String uri = "android.resource://" + getPackageName() + "/" + R.raw.danmu;
            videoview.setVideoPath(uri);
            //videoview.setVideoURI(Uri.parse(uri));
            videoview.start();
            danmakuView = (DanmakuView) findViewById(R.id.danmaku_view);
            danmakuView.enableDanmakuDrawingCache(true);
            danmakuView.setCallback(new DrawHandler.Callback() {
                @Override
                public void prepared() {
                    showDanmaku = true;
                    danmakuView.start();
                    generateSomeDanmaku();
                }
    
                @Override
                public void updateTimer(DanmakuTimer timer) {
    
                }
    
                @Override
                public void danmakuShown(BaseDanmaku danmaku) {
    
                }
    
                @Override
                public void drawingFinished() {
    
                }
            });
            danmakuContext = DanmakuContext.create();
            danmakuView.prepare(parser, danmakuContext);
    
            final LinearLayout operationLayout = (LinearLayout) findViewById(R.id.operation_layout);
            Button send = (Button) findViewById(R.id.send);
            final EditText editText = (EditText) findViewById(R.id.edit_text);
            danmakuView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (operationLayout.getVisibility() == View.GONE) {
                        operationLayout.setVisibility(View.VISIBLE);
                    } else {
                        operationLayout.setVisibility(View.GONE);
                    }
                }
            });
            send.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String content = editText.getText().toString();
                    if (!TextUtils.isEmpty(content)) {
                        addDanmaku(content, true, Color.GREEN);
                        editText.setText("");
                    }
                }
            });
            getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
                @Override
                public void onSystemUiVisibilityChange(int visibility) {
                    if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) {
                        onWindowFocusChanged(true);
                    }
                }
            });
        }
    
        /**
         * 向弹幕View中添加一条弹幕
         *
         * @param content    弹幕的具体内容
         * @param withBorder 弹幕是否有边框
         */
        private void addDanmaku(String content, boolean withBorder) {
            BaseDanmaku danmaku = danmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
            danmaku.text = content;
            danmaku.padding = 5;
            danmaku.textSize = sp2px(20);
            danmaku.textColor = Color.WHITE;
            danmaku.setTime(danmakuView.getCurrentTime());
            if (withBorder) {
                danmaku.borderColor = Color.GREEN;
            }
            danmakuView.addDanmaku(danmaku);
        }
    
        /**
         * 弹幕View中添加一条弹幕
         *
         * @param content    弹幕的具体内容
         * @param withBorder 弹幕是否有边框
         * @param textColor  弹幕字体颜色
         */
        private void addDanmaku(String content, boolean withBorder, int textColor) {
            BaseDanmaku danmaku = danmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
            danmaku.text = content;
            danmaku.padding = 5;
            danmaku.textSize = sp2px(20);
            danmaku.textColor = textColor;
            danmaku.setTime(danmakuView.getCurrentTime());
            if (withBorder) {
                danmaku.borderColor = Color.GREEN;
            }
            danmakuView.addDanmaku(danmaku);
        }
    
        /**
         * 随机生成一些弹幕内容以供测试
         */
        private void generateSomeDanmaku() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (showDanmaku) {
                        int time = new Random().nextInt(300);
                        String content = "" + time + time;
                        addDanmaku(content, false);
                        try {
                            Thread.sleep(time);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    
        /**
         * sp转px的方法。
         */
        public int sp2px(float spValue) {
            final float fontScale = getResources().getDisplayMetrics().scaledDensity;
            return (int) (spValue * fontScale + 0.5f);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            if (danmakuView != null && danmakuView.isPrepared()) {
                danmakuView.pause();
            }
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if (danmakuView != null && danmakuView.isPrepared() && danmakuView.isPaused()) {
                danmakuView.resume();
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            showDanmaku = false;
            if (danmakuView != null) {
                danmakuView.release();
                danmakuView = null;
            }
        }
    
        /**
         * 沉浸式状态栏效果
         */
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            if (hasFocus && Build.VERSION.SDK_INT >= 19) {
                View decorView = getWindow().getDecorView();
                decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
            }
        }
    }


    原文链接:https://blog.csdn.net/mythmayor/article/details/80510449

  • 相关阅读:
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    Linux的压缩和解压命令
    RabbitMQ消费消息的两种模式:推和拉
    没有开启keepalive,接收消息会超时
    不止背锅!互联网大厂的运维都在干什么?30K的总监来告诉你
    tcp 开启keepalive
  • 原文地址:https://www.cnblogs.com/wangdayang/p/14913347.html
Copyright © 2020-2023  润新知