自己最近再做Android相关的项目,所以闲下来的时候摸索了一下悬浮窗播放视频。
# 1【准备工作】
## 1.1 MediaPlayer的使用
既然是悬浮窗播放视频,那么首先就要了解Android怎么播放视频。这里做简单的介绍。
视频播放的调用步骤:
(1). 装载播放资源 MediaPlayer.setDataSource() , 也何以使用 MediaPlay.oncreate(context, R.raw.video);
(2). 在layout.xml中创建surfaceView;
(3). 调用MediaPlayer.setDisplay()设置surfaceHolder, surfaceHolder可以通过surfaceView.getHolder()方法获得;
(4). 调用MediaPlayer.prepare()来准备;
(5). 调用MediaPlayer.start()来播放视频。
在第三步之前要确保surfaceHolder已经准备好。因此 需要给surfaceHolder设置一个callBack,调用addCallback(),调用addCallBack()方法. callBack有三个回调函数:
SurfaceHolder.Callback {
@Override
// 待surfaceCreated回调,做MediaPlayer.setDisplay()
public void surfaceCreated(SurfaceHolder holder) {
MediaPlayer.setDisplay();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
以上是准备工作,简介介绍,如果看不懂可以参考 https://www.cnblogs.com/yxx123/p/5720907.html, 也可以后面看源码。
【实现原理】
1.1 设计思想:
悬浮窗画面需要继承Service,而不是Activity,一旦Activity退出就onStop掉了,所以需要继承Service后台运行。
1.2 权限设置和申请:
在Android 6.0之后大部分权限都是需要自行申请的,不能随意获取。我们这里需要申请两个权限,一个是启动悬浮窗 ,另一个由于我们播放的是网络资源,需要权限 。
1.3 悬浮窗口设置:
这个是Android 8.0之后的又一个坑,需要对不同版本的Android系统进行适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
在Android 8.0之前,悬浮窗口设置可以为TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口。
而Android 8.0对系统和API行为做了修改,包括使用SYSTEM_ALERT_WINDOW权限的应用无法再使用以下窗口类型来在其他应用和窗口上方显示提醒窗口。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取权限。如果当前获取了权限,直接就启动悬浮窗画面FloatingWindowService.class
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 1111);
} else {
startService(new Intent(MainActivity.this, FloatingWindowService.class));
}
// 炮灰activity,启动悬浮窗之后就退掉
finish();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 判断获取权限是否成功
if (requestCode == 1111) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show();
startService(new Intent(MainActivity.this, FloatingService.class));
}
}
3.2 悬浮窗画面
public class FloatingWindowService extends Service {
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
private View display;
private SurfaceHolder surfaceHolder;
private SurfaceView surfaceView;
public FloatingWindowService() {
}
@Override
public void onCreate() {
super.onCreate();
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
layoutParams = new WindowManager.LayoutParams();
Log.d("悬浮窗", "Build.VERSION.SDK_INT" + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
// android 8.0及以后使用
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
// android 8.0以前使用
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
//该flags描述的是窗口的模式,是否可以触摸,可以聚焦等
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 设置视频的播放窗口大小
layoutParams.width = 800;
layoutParams.height = 450;
layoutParams.x = 300;
layoutParams.y = 300;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
showFloatingWindow();
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}
private void showFloatingWindow(){
if (Settings.canDrawOverlays(this)) {
LayoutInflater layoutInflater = LayoutInflater.from(this);
display = layoutInflater.inflate(R.layout.video_display, null);
surfaceView = display.findViewById(R.id.videoplayer_display);
// 获取surfaceView的sourfaceHolder
surfaceHolder = surfaceView.getHolder();
final MediaPlayer mediaPlayer = new MediaPlayer();
// 设置视频播放流类型
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 视频资源网址
Uri uri = Uri.parse("https://raw.githubusercontent.com/dongzhong/ImageAndVideoStore/master/Bruno%20Mars%20-%20Treasure.mp4");
try {
// 设置视频播放资源,这里如果前面调用了MediaPlayer.create(contex, R.raw.video),就不用再次调用了
mediaPlayer.setDataSource(this,uri);
} catch (IOException e) {
e.printStackTrace();
}
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 视频播放设置
mediaPlayer.setDisplay(surfaceHolder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
// 待视频资源准备好了,回调中播放视频资源,
mediaPlayer.start();
//循环播放
mediaPlayer.setLooping(true);
}
});
windowManager.addView(display, layoutParams);
display.setOnTouchListener(new FloatingOnTouchListener());
}
}
// touch移动视频窗口
private class FloatingOnTouchListener implements View.OnTouchListener {
private int x;
private int y;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
int nowY = (int) event.getRawY();
int movedX = nowX - x;
int movedY = nowY - y;
Log.d("悬浮窗", "movedX = " + movedX + ", movedY =" + movedY);
x = nowX;
y = nowY;
layoutParams.x = layoutParams.x + movedX;
layoutParams.y = layoutParams.y + movedY;
windowManager.updateViewLayout(view, layoutParams);
break;
default:
break;
}
return false;
}
}
}
3.3 AndroidManifest.xml
3.4 video_display.xml
以上就是主要代码
完整代码,可直接执行 https://download.csdn.net/download/lisiwei1994/10751626
参考借鉴 https://blog.csdn.net/dongzhong1990/article/details/80512706