Android 实现仿网络直播弹幕功能详解及实例
android 网络直播弹幕
最近看好多网络电视,播放器及直播都有弹幕功能,自己周末捣鼓下并实现,以下是网上的资料,大家可以看下。
现在网络直播越来越火,网络主播也逐渐成为一种新兴职业,对于网络直播,弹幕功能是必须要有的,如下图:
首先来分析一下,这个弹幕功能是怎么实现的,首先在最下面肯定是一个游戏界面view,然后游戏界面上有弹幕view,弹幕的view必须要做成完全透明的,这样即使覆盖在游戏界面的上方也不会影响到游戏的正常观看,只有当有人发弹幕消息时,再将消息绘制到弹幕的view上面就可以了,下方肯定还有有操作界面view,可以让用户来发弹幕和送礼物的功能,原理示意图如下所示:
参照原理图,下面一步一步来实现这个功能。
实现视频的播放
activity_main.xml
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000"> <videoview android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerinparent="true"/> </relativelayout>
mainactivity.java
package com.jackie.bombscreen; import android.os.build; import android.os.bundle; import android.os.environment; import android.support.v7.app.appcompatactivity; import android.view.view; import android.widget.videoview; public class mainactivity extends appcompatactivity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); videoview videoview = (videoview) findviewbyid(r.id.video_view); videoview.setvideopath(environment.getexternalstoragedirectory() + "/xiaoxingyun.mp4"); videoview.start(); } @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); } } }
最后别忘了设置androidmainfest.xml
效果如下:
、
实现弹幕的效果
接下来我们开始实现弹幕效果。弹幕其实也就是一个自定义的view,它的上面可以显示类似于跑马灯的文字效果。观众们发表的评论都会在弹幕上显示出来,但又会很快地移出屏幕,既可以起到互动的作用,同时又不会影响视频的正常观看。
我们可以自己来编写这样的一个自定义view,当然也可以直接使用网上现成的开源项目。那么为了能够简单快速地实现弹幕效果,这里我就准备直接使用由哔哩哔哩开源的弹幕效果库danmakuflamemaster。
danmakuflamemaster库的项目主页地址是:http://xiazai.jb51.net/201611/yuanma/danmakuflamemaster-master(jb51.net).rar
添加build.gradle依赖
compile 'com.github.ctiao:danmakuflamemaster:0.5.3'
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000"> <videoview android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="wrap_content" 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" /> </relativelayout>
修改mainactivity.java
package com.jackie.bombscreen; import android.graphics.color; import android.os.build; import android.os.bundle; import android.os.environment; import android.support.v7.app.appcompatactivity; import android.view.view; 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 appcompatactivity { private boolean misshowdanmaku; private danmakuview mdanmakuview; private danmakucontext mdanmakucontext; 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); videoview.setvideopath(environment.getexternalstoragedirectory() + "/xiaoxingyun.mp4"); videoview.start(); mdanmakuview = (danmakuview) findviewbyid(r.id.danmaku_view); mdanmakuview.enabledanmakudrawingcache(true); mdanmakuview.setcallback(new drawhandler.callback() { @override public void prepared() { misshowdanmaku = true; mdanmakuview.start(); generatesomedanmaku(); } @override public void updatetimer(danmakutimer timer) { } @override public void danmakushown(basedanmaku danmaku) { } @override public void drawingfinished() { } }); mdanmakucontext = danmakucontext.create(); mdanmakuview.prepare(parser, mdanmakucontext); } /** * 向弹幕view中添加一条弹幕 * @param content 弹幕的具体内容 * @param withborder 弹幕是否有边框 */ private void adddanmaku(string content, boolean withborder) { basedanmaku danmaku = mdanmakucontext.mdanmakufactory.createdanmaku(basedanmaku.type_scroll_rl); danmaku.text = content; danmaku.padding = 5; danmaku.textsize = sp2px(20); danmaku.textcolor = color.white; danmaku.settime(mdanmakuview.getcurrenttime()); if (withborder) { danmaku.bordercolor = color.green; } mdanmakuview.adddanmaku(danmaku); } /** * 随机生成一些弹幕内容以供测试 */ private void generatesomedanmaku() { new thread(new runnable() { @override public void run() { while(misshowdanmaku) { int time = new random().nextint(300); string content = "" + time + time; adddanmaku(content, false); try { thread.sleep(time); } catch (interruptedexception 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 (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(); misshowdanmaku = false; if (mdanmakuview != null) { mdanmakuview.release(); mdanmakuview = 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); } } }
效果图如下:
加入操作界面
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000"> <videoview android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="wrap_content" 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>
package com.jackie.bombscreen; import android.graphics.color; import android.os.build; import android.os.bundle; import android.os.environment; import android.support.v7.app.appcompatactivity; 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 appcompatactivity { private boolean misshowdanmaku; private danmakuview mdanmakuview; private danmakucontext mdanmakucontext; 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); videoview.setvideopath(environment.getexternalstoragedirectory() + "/xiaoxingyun.mp4"); videoview.start(); mdanmakuview = (danmakuview) findviewbyid(r.id.danmaku_view); mdanmakuview.enabledanmakudrawingcache(true); mdanmakuview.setcallback(new drawhandler.callback() { @override public void prepared() { misshowdanmaku = true; mdanmakuview.start(); generatesomedanmaku(); } @override public void updatetimer(danmakutimer timer) { } @override public void danmakushown(basedanmaku danmaku) { } @override public void drawingfinished() { } }); mdanmakucontext = danmakucontext.create(); mdanmakuview.prepare(parser, mdanmakucontext); final linearlayout operationlayout = (linearlayout) findviewbyid(r.id.operation_layout); final button send = (button) findviewbyid(r.id.send); final edittext edittext = (edittext) findviewbyid(r.id.edit_text); mdanmakuview.setonclicklistener(new view.onclicklistener() { @override public void onclick(view view) { if (operationlayout.getvisibility() == view.gone) { operationlayout.setvisibility(view.visible); } else { operationlayout.setvisibility(view.gone); } } }); send.setonclicklistener(new view.onclicklistener() { @override public void onclick(view view) { string content = edittext.gettext().tostring(); if (!textutils.isempty(content)) { adddanmaku(content, true); 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 = mdanmakucontext.mdanmakufactory.createdanmaku(basedanmaku.type_scroll_rl); danmaku.text = content; danmaku.padding = 5; danmaku.textsize = sp2px(20); danmaku.textcolor = color.white; danmaku.settime(mdanmakuview.getcurrenttime()); if (withborder) { danmaku.bordercolor = color.green; } mdanmakuview.adddanmaku(danmaku); } /** * 随机生成一些弹幕内容以供测试 */ private void generatesomedanmaku() { new thread(new runnable() { @override public void run() { while(misshowdanmaku) { int time = new random().nextint(300); string content = "" + time + time; adddanmaku(content, false); try { thread.sleep(time); } catch (interruptedexception 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 (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(); misshowdanmaku = false; if (mdanmakuview != null) { mdanmakuview.release(); mdanmakuview = 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); } } }
效果图如下:
自己发的弹幕有绿色边框,很容易区分。
基本上实现了弹幕的功能,当然,里面的知识点还有很多,这只是最基本的功能。有时间的话,建议学学danmakuflamemaster,里面还有很多炫酷的功能。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!