[Android] (Android 视频悬浮窗)

自己最近再做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权限的应用无法再使用以下窗口类型来在其他应用和窗口上方显示提醒窗口。

  • TYPE_PHONE
  • TYPE_PRIORITY_PHONE
  • TYPE_SYSTEM_ALERT
  • TYPE_SYSTEM_OVERLAY
  • TYPE_SYSTEM_ERROR
    如果需要实现在其他应用和窗口上方显示提醒窗口,那么必须该为TYPE_APPLICATION_OVERLAY的新类型。
      如果在Android 8.0以上版本仍然使用TYPE_PHONE类型的悬浮窗口,则会出现如下异常信息
      android.view.WindowManager B a d T o k e n E x c e p t i o n : U n a b l e t o a d d w i n d o w a n d r o i d . v i e w . V i e w R o o t I m p l BadTokenException: Unable to add window android.view.ViewRootImpl BadTokenException:Unabletoaddwindowandroid.view.ViewRootImplW@f8ec928 – permission denied for window type 2006
    具体实现
    3.1 在MainActivity中启动悬浮窗并开启权限
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

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.INTERNET">
    </uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".FloatingWindowService"
            android:enabled="true"
            android:exported="true"></service>
    </application>

3.4 video_display.xml

 <FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/videoplayer_display"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</FrameLayout>

以上就是主要代码
完整代码,可直接执行 https://download.csdn.net/download/lisiwei1994/10751626
参考借鉴 https://blog.csdn.net/dongzhong1990/article/details/80512706

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要实现在 Android 悬浮窗播放视频,可以采用以下步骤: 1. 在 AndroidManifest.xml 添加 SYSTEM_ALERT_WINDOW 权限,以获取悬浮窗权限。 ```xml <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> ``` 2. 创建一个 Service,并在 onCreate() 方法创建一个悬浮窗口。可以使用 WindowManager 来创建悬浮窗口,并将其添加到 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 类型的窗口上。同时,为了能够拖动和调整悬浮窗口的大小,需要设置 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 和 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 标志。 ```java public class FloatingVideoService extends Service { private WindowManager windowManager; private View floatingView; @Override public void onCreate() { super.onCreate(); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); floatingView = LayoutInflater.from(this).inflate(R.layout.floating_video_layout, null); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT); windowManager.addView(floatingView, layoutParams); } ... } ``` 3. 在悬浮窗添加一个 VideoView,并设置要播放的视频路径。 ```java VideoView videoView = floatingView.findViewById(R.id.video_view); videoView.setVideoPath(videoPath); videoView.start(); ``` 4. 为悬浮窗口添加关闭按钮,并在按钮的 onClick() 方法关闭悬浮窗口。 ```java Button closeButton = floatingView.findViewById(R.id.close_button); closeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { windowManager.removeView(floatingView); stopSelf(); } }); ``` 5. 最后,在 AndroidManifest.xml 注册 Service。 ```xml <service android:name=".FloatingVideoService"/> ``` 需要注意的是,由于 Android 6.0 以上版本的权限机制的变化,需要在运行时获取悬浮窗口权限。可以在 onCreate() 方法请求权限,并在回调方法判断用户是否授权。 ```java if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, CODE_DRAW_OVER_OTHER_APP_PERMISSION); } else { // 权限已经被授予 startFloatingVideo(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == CODE_DRAW_OVER_OTHER_APP_PERMISSION) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) { // 权限已经被授予 startFloatingVideo(); } else { // 权限被拒绝 Toast.makeText(this, "悬浮窗权限被拒绝", Toast.LENGTH_SHORT).show(); } } else { super.onActivityResult(requestCode, resultCode, data); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值