Android开发类似直播APP的弹幕和悬浮窗播放功能
闲来无事,最近自己查网上资料开发可以发送弹幕和悬浮窗播放功能的APP,写的不好,轻喷。
一、弹幕功能主要使用哔哩哔哩的弹幕库进行开发的,可以发送自己输入的弹幕文字,还做了弹幕是否显示的开关。上代码:
package com.barrage.barragetest.activity;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Environment;
import android.support.design.button.MaterialButton;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.VideoView;
import com.barrage.barragetest.R;
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 PlayVideoActivity extends Activity{
private boolean showDanmaku;
private DanmakuView danmakuView;
private DanmakuContext danmakuContext;
private VideoView videoView;
private LinearLayout ll_linearlayout;
private EditText et_write;
private MaterialButton mb_send,mb_close;
//视频地址
// private String file_path = Environment.getExternalStorageDirectory() + "/DCIM/Camera/shipin.mp4";
private String file_path = "android.resource://com.barrage.barragetest/" + R.raw.shipin;
private BaseDanmakuParser parser = new BaseDanmakuParser() {
@Override
protected IDanmakus parse() {
return new Danmakus();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//隐去标题栏(应用程序的名字)
requestWindowFeature(Window.FEATURE_NO_TITLE);
//隐去状态栏部分(电池等图标和一起修饰部分)
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);// 设置全屏
setContentView(R.layout.activity_play_video);
initView();
}
//初始化控件
private void initView(){
ll_linearlayout = (LinearLayout) findViewById(R.id.ll_linearlayout); //弹幕输入框
et_write = (EditText) findViewById(R.id.et_write); //弹幕输入框
mb_send = (MaterialButton) findViewById(R.id.mb_send); //发送弹幕
mb_close = (MaterialButton) findViewById(R.id.mb_close); //关闭或打开弹幕
videoView = (VideoView) findViewById(R.id.video_view);
videoView.setVideoPath(file_path); //加载视频
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);
initEvent();
}
//点击事件
private void initEvent(){
//发送弹幕
mb_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!"".equals(et_write.getText().toString())) {
addDanmaku(et_write.getText().toString(),true);
et_write.setText("");
}
}
});
//控制弹幕开关
mb_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//关闭弹幕
if ("关闭".equals(mb_close.getText().toString())) {
showDanmaku = false;
mb_close.setText("打开");
}else{//打开弹幕
showDanmaku = true;
mb_close.setText("关闭");
generateSomeDanmaku();
}
}
});
danmakuView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ll_linearlayout.getVisibility()== View.VISIBLE) { //已显示
ll_linearlayout.setVisibility(View.GONE);
}else{ //隐藏
ll_linearlayout.setVisibility(View.VISIBLE);
}
}
});
}
/**
* 向弹幕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;
danmaku.textColor = Color.BLUE;
}
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 (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 (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;
}
}
}
页面布局activity_play_video.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"/>
<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/ll_linearlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_margin="8dp"
android:background="#ffffff"
android:visibility="gone">
<EditText
android:id="@+id/et_write"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<android.support.design.button.MaterialButton
android:id="@+id/mb_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"
android:theme="@style/Theme.MaterialComponents.Light"/>
<android.support.design.button.MaterialButton
android:id="@+id/mb_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="关闭"
android:theme="@style/Theme.MaterialComponents.Light"/>
</LinearLayout>
</RelativeLayout>
注意:1、不要忘了权限申请;2、这里我把视频文件放在项目里,所以视频路径是项目资源文件路径。
效果图如下:
二、悬浮窗播放功能。该功能需要悬浮窗权限SYSTEM_ALERT_WINDOW,这是两大特殊权限之一,需要手动设置。
写个按钮直接调用showWindow()方法就行,完整的页面代码我就不写出来了
private WindowManager mWindowManager;
private WindowManager.LayoutParams mLayout;
// 窗口宽高值
private float x, y;
//悬浮窗口布局
private View mWindowsView;
//显示悬浮窗口
public void showWindow() {
//先检查是否具有悬浮窗权限
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);
} else {
// 取得系统窗体
mWindowManager = (WindowManager) getApplicationContext()
.getSystemService(WINDOW_SERVICE);
// 窗体的布局样式
mLayout = new WindowManager.LayoutParams();
// 设置窗体显示类型——TYPE_SYSTEM_ALERT(系统提示)
mLayout.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
// 设置窗体焦点及触摸:
// FLAG_NOT_FOCUSABLE(不能获得按键输入焦点)
mLayout.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 设置显示的模式
mLayout.format = PixelFormat.RGBA_8888;
// 设置对齐的方法
mLayout.gravity = Gravity.TOP | Gravity.LEFT;
// 设置窗体宽度和高度
// 设置视频的播放窗口大小
mLayout.width = 700;
mLayout.height = 400;
mLayout.x = 300;
mLayout.y = 300;
//将指定View解析后添加到窗口管理器里面
mWindowsView = View.inflate(this, R.layout.layout_window, null);
VideoView vv_float_video = (VideoView) mWindowsView.findViewById(R.id.vv_float_video);
mWindowsView.findViewById(R.id.iv_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
closeWindow();
}
});
playVideo(vv_float_video);
mWindowManager.addView(mWindowsView, mLayout);
mWindowsView.setOnTouchListener(new View.OnTouchListener() {
float mTouchStartX;
float mTouchStartY;
@Override
public boolean onTouch(View view, MotionEvent event) {
x = event.getRawX();
y = event.getRawY() - 25;//25状态栏大小
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTouchStartX = event.getX();
mTouchStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//原始坐标减去移动坐标
mLayout.x = (int) (x - mTouchStartX);
mLayout.y = (int) (y - mTouchStartY);
mWindowManager.updateViewLayout(mWindowsView, mLayout);
Log.i("main", "x值=" + x + "\ny值=" + y + "\nmTouchX" + mTouchStartX + "\nmTouchY=" + mTouchStartY);
break;
}
return true;
}
});
}
}
//播放视频
private void playVideo(VideoView videoView) {
//获取本地视频文件进行播放
ContentResolver resolver = getContentResolver();
Cursor c = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
if (c.moveToNext()) {
String path = c.getString(c.getColumnIndex(MediaStore.Video.Media.DATA));
videoView.setVideoPath(path);
videoView.requestFocus();
videoView.start();
}
}
//关闭窗口点击事件
public void closeWindow() {
mWindowManager.removeView(mWindowsView);
}
窗口页面布局layout_window.xml
<?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">
<VideoView
android:id="@+id/vv_float_video"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="@mipmap/ic_close" />
</RelativeLayout>
效果图如下: