Android悬浮窗视频

类似微信视频通话需求。
思路:
1.申请悬浮窗权限
2.windowManager实现悬浮窗;
3.moveToback退出全屏,显示悬浮窗;

当用户正在NewsActivity看文字,视频通话来了,接听(VideoActivity),然后缩至悬浮窗,此时应回到NewsActivity,悬浮窗出现时该如何回到接听(VideoActivity)前的页面?finish掉VideoActivity吗?finish后自然回退到栈内上一个Activity,页面逻辑上符合需求,但是VideoActivity中写了很多通话逻辑,销毁不太好,怎么办?(有人说,为何会在页面写逻辑,明显是没抽象好嘛,这都是后话,不解决当前版本问题)

于是想到干脆起两个任务栈,一个单独放VideoActivity,另一个放剩余的Activity,然后点击要悬浮时,直接后置VideoActivity所在任务栈即可。
实现两个任务栈也很简单,只要给VideoActivity设置android:launchMode="singleInstance"即可。
想要退后该任务栈直接调用 moveTaskToBack(true);

然而singleInstance的坑不是一般的多。

一 . 首先 singleInstance会导致onRequestPermissionsResult( ),onActivityResult( )不回调。可是视频必须要申请RECORD_AUDIO,CAMERA,WRITE_EXTERNAL_STORAGE权限,以及悬浮窗ACTION_MANAGE_OVERLAY_PERMISSION。
无奈写个视频的前置页面标准启动模式,专门用来申请相机,录音,读写等权限,全部授权后 再真正进入singleInstance页面。
问题一算是迎刃而解。(躲过去了)

二. 长按home或者菜单键 查看Android最近任务列表时,一个APP竟然两个最近任务,解决方案:需要添加一个属性 保证 不在最近任务列表中显示当前activity所在的应用

android:launchMode= "singleInstance"  //开启新的应用任务栈
android:excludeFromRecents= "true"   //不在最近任务列表中显示当前activity所在的应用

三 选择TYPE_TOAST,如果期间有toast弹出,在android7.1.1会崩溃,加上版本判断吧。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
    wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}

下面是悬浮窗的实现


/**
 * 视频面试悬浮窗
 */
public class FloatVideoWindowService extends Service {
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams wmParams;

    private View mFloatingLayout;
    private RelativeLayout mPreviewLayout;
    private TutorialsManager mInstance = null;
    private SurfaceView remoteView = null;

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public class MyBinder extends Binder {
//        public FloatVideoWindowService getService() {
//            return FloatVideoWindowService.this;
//        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        initWindow();//设置悬浮窗基本参数(位置、宽高等)
        initFloating();//悬浮框点击事件的处理
        mInstance = TutorialsManager.getInstance(this);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int uid = intent.getIntExtra(VideoChatViewActivity.KEY_C_USER_ID, -1);
        if (uid != -1) {
            remoteView = mInstance.getRemoteVideo(uid);
            ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            remoteView.setLayoutParams(lp);
            mPreviewLayout.addView(remoteView);
        } else {
            stopSelf();
        }
        return super.onStartCommand(intent, flags, startId);
    }


    @Override
    public void onDestroy() {
        if (null != mPreviewLayout) {
            mPreviewLayout.removeAllViews();
        }
        if (mFloatingLayout != null) {
            // 移除悬浮窗口
            mWindowManager.removeView(mFloatingLayout);
        }
    }

    /**
     * 设置悬浮框基本参数(位置、宽高等)
     */
    private void initWindow() {
        mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        wmParams = getParams();
        // 悬浮窗默认显示以左上角为起始坐标
        wmParams.gravity = Gravity.RIGHT | Gravity.TOP;
        //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
        wmParams.x = DensityUtil.dip2px(10);
        wmParams.y = 110;
        // 获取浮动窗口视图所在布局
        mFloatingLayout = LayoutInflater.from(getApplicationContext()).inflate(R.layout.view_videochat_services_float_layout, null);
        // 添加悬浮窗的视图
        mWindowManager.addView(mFloatingLayout, wmParams);
    }


    private WindowManager.LayoutParams getParams() {
        wmParams = new WindowManager.LayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        //设置可以显示在状态栏上
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams
                .FLAG_NOT_TOUCH_MODAL |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;

        int fullCropWidth = DeviceUtils.getScreenWidth();
        int cropHeight = fullCropWidth * 16 / 9;

        //设置悬浮窗口长宽数据
        wmParams.width = (int) (fullCropWidth * 0.26);
        wmParams.height = (int) (cropHeight * 0.26);
        return wmParams;
    }

    private void initFloating() {
        mPreviewLayout = mFloatingLayout.findViewById(R.id.small_size_preview);

        //悬浮框点击事件
        mPreviewLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FloatVideoWindowService.this, VideoChatViewActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
                mPreviewLayout.removeView(remoteView);
                stopSelf();
            }
        });

        //悬浮框触摸事件,设置悬浮框可拖动
        mPreviewLayout.setOnTouchListener(new FloatingListener());
    }

    /**
     * 开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
     */
    private int mTouchStartX;
    private int mTouchStartY;
    /**
     * 开始时的坐标和结束时的坐标(相对于自身控件的坐标)
     */
    private int mStartX;
    private int mStartY;
    /**
     * 判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
     */
    private boolean isMove;

    private class FloatingListener implements View.OnTouchListener {
        int slop = 1;//滑动距离,区分点击

        public FloatingListener() {
            slop = ViewConfiguration.get(getApplicationContext()).getScaledTouchSlop();
        }

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    isMove = false;
                    mTouchStartX = (int) event.getRawX();
                    mTouchStartY = (int) event.getRawY();
                    mStartX = (int) event.getX();
                    mStartY = (int) event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int mTouchCurrentX = (int) event.getRawX();
                    int mTouchCurrentY = (int) event.getRawY();
                    wmParams.x -= mTouchCurrentX - mTouchStartX;
                    wmParams.y += mTouchCurrentY - mTouchStartY;
                    mWindowManager.updateViewLayout(mFloatingLayout, wmParams);

                    mTouchStartX = mTouchCurrentX;
                    mTouchStartY = mTouchCurrentY;
                    break;
                case MotionEvent.ACTION_UP:
                    int mStopX = (int) event.getX();
                    int mStopY = (int) event.getY();
                    if (Math.abs(mStartX - mStopX) >= slop || Math.abs(mStartY - mStopY) >= slop) {
                        isMove = true;
                    }
                    break;
                default:
                    break;
            }
            //如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
            return isMove;
        }
    }
}

其中VideoActivity中代码:

    private int PermissionRequestCode = 10;

    private void onClickFloatBtn() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), PermissionRequestCode);
        } else {
            showFloatView();
        }
    }

    private void showFloatView() {
        if (mUid != -1) {
            moveTaskToBack(true);
            Intent intent = new Intent(this, FloatVideoWindowService.class);
            intent.putExtra(KEY_C_USER_ID, mUid);
            startService(intent);
        }
    }

    @Override
    protected void onActivityResult(final int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PermissionRequestCode) {
            if (mHandler == null) {
                mHandler = new Handler(Looper.getMainLooper());
            }
            //此处特意延时500ms,否则回调值不准确,   https://blog.csdn.net/qq_24179679/article/details/84139408
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        if (Settings.canDrawOverlays(mContext)) {//开启
                            showFloatView();
                        } else {//关闭
                            ToastUtil.show(mContext, "您未授权悬浮窗");
                        }
                    }
                }
            }, 500);
        }
    }

等等,你刚才坑一里不是说,onActivityResult不回调吗?咋还在singleInstance的页面重写这个方法。经测试,onRequestPermissionsResult的确不回调,但是onActivityResult还是有回调的,延时500ms,回调值更准确哦。

是不是很开森,万事大吉了。人生就像登山,往上走即使一小步也有新高度;然而编程就像玩俄罗斯套娃,打破一小层,还有更大层等着你。走不完的套路,爬不完的坑。

问题描述:当singleInstance页面在onResume时,按下home键,再次点击app的桌面icon,进入后,发现不是刚才的singleInstance页面。我太难啦!!!

有人说可以设置:alwaysRetainTaskState = true

咦~ 这是什么属性?来看看谷歌官方文档怎么说的:

android:alwaysRetainTaskState
系统是否始终保持 Activity 所在任务的状态 —“true”表示是,“false”表示允许系统在特定情况下将任务重置到其初始状态。
默认值为“false”。该属性只对任务的根Activity 有意义;所有其他 Activity 均可忽略该属性。
正常情况下,当用户从主屏幕重新选择某个任务时,系统会在特定情况下清除该任务(从根 Activity 上的堆栈中移除所有Activity)。通常,如果用户在一段时间(如 30 分钟)内未访问任务,系统会执行此操作。
不过,如果该属性的值是“true”,则无论用户如何返回任务,该任务始终会显示最后一次的状态。例如,该属性非常适用于网络浏览器这类应用,因为其中存在大量用户不愿丢失的状态(如多个打开的标签)。

看完最后一句,“该属性非常适用于网络浏览器这类应用,因为其中存在大量用户不愿丢失的状态(如多个打开的标签)”,好像有点那个意思哦,赶紧加上试试,RUN。。。。。满怀期待。。。。虔诚等待。。。。

等了那么久,然并卵。显然是singleInstance在作祟,烦屎了。。。。

剩下我知识储量的最后一招了,纯属无奈之举!
监听app从后台切回前台时机,切回前台时,如果VideoActivity尚在且在通话中,二话不说,直接startActivity之。
怎么监听APP从后台切到前台了呢?注册Application.ActivityLifecycleCallbacks
记得在MyApplication的onCreate中调用注册方法registerActivityLifecycleCallback(new AppActivityLifecycleCallbacks());

看看AppActivityLifecycleCallbacks实现类的代码:

    @Override
    public void onActivityStarted(Activity activity) {
            //视频面试时APP切换至后台,然后由后台切换至前台,视频面试中,且不是悬浮窗显示时 要回到视频面试页面
            if (onActivityStoppedFlag && BackgroundUtils.isForeground(activity)
                    && ActivityStackHelper.isActivityRunning(VideoChatViewActivity.class)
                    && !SystemUtils.isServiceRunning(activity, FloatVideoWindowService.class.getName())) {
                activity.startActivity(new Intent(activity, VideoChatViewActivity.class));
            }
            onActivityStoppedFlag = !BackgroundUtils.isForeground(activity);
    }
    /**
     * 用于判断是否由后台切换至前台
     */
    private boolean onActivityStoppedFlag = false;

    @Override
    public void onActivityStopped(Activity activity) {
        onActivityStoppedFlag = !BackgroundUtils.isForeground(activity);
    }

功能算是“顺利”实现了,但是监听app后台切前台纯属临时方案;恳请有爱的大神帮忙支招,在我的知识盲区指津。

参考文献:
https://www.jianshu.com/p/3786653f9c9b
https://blog.csdn.net/qq_24179679/article/details/84139408

由锐动天地公司开发的国产免费软件视频编辑专家现在已经完全免费发布啦,您可以自由免费使用该软件,再也不用担心付费了。经大眼仔旭测试视频转换专家是一款操作简单,功能强大的视频编辑软件。支持各种主流格式的转换,并可以生成 VCD/SVCD、DVD 映像文件。 视频编辑专家功能简介 1.编辑与转换:可以转换MPEG 1/2/4, AVI, ASF, SWF, DivX, Xvid, RM, RMVB, FLV, SWF, MOV, 3GP, WMV, PMP, VOB, MP3, MP2, AU, AAC, AC3, M4A, WAV, WMA, OGG, FLAC等各种音视频格式;而且支持音量调节,时间截取,视频裁剪,添加水印和字幕等功能。 2.视频分割:把一个视频文件分割成任意大小和数量的视频文件。 3.视频文件截取:从视频文件中提取出您感兴趣的部分,制作成视频文件。 4.视频合并:把多个不同或相同的音视频格式文件合并成一个音视频文件。 5.配音配乐:给视频添加背景音乐以及配音。 6.字幕制作:给视频添加字幕。 7.视频截图:从视频中截取精彩画面。 Bentley Maxsurf Enterprise V8i (SELECTSeries 3) 20.00.03.09 关键特色 支持iPod, iPhone, PSP, Zune, Mobile phone。 支持各种高清视频格式:iPod AVC, PSP AVC, Zune AVC。 支持Real Play Meida格式。 支持苹果电视(Apple TV)格式。 支持互联网视频格式:SWF, FLV。 支持3GP视频格式。 支持视频裁剪,时间剪切。 支持一键转换,批量转换。 自动记忆用户上次设置,也可以恢复默认设置。 为什么选用视频转换专家? 简单易用,初级用户能够通过简单的步骤就可以完成操作。 拥有最好的性价比。 官方主页 新增功能 最新升级!支持转换视频到iPod, iPhone, PSP, Zune, Mobile phone 支持苹果电视 H.264 & 苹果电视 MPEG-4。 支持 FLV 和SWF 格式。 支持 RMVB 格式。 核心算法全面优化,更快更稳定。
ckplayer是一款在网页上播放视频的免费的播放器,功能强大,体积小巧,跨平台,使用起来随心所欲。 播放器主要以adobe的flash(所使用的版本是CS5)平台开发,所以在支持flash插件的平台和浏览器上都可以使用,而无需下载其它插件,如果你需要修改完整版里提供的相关的flash源文件,请使用adobe的flash cs5以上版本打开源文件修改。 ckplayer同时也支持html5的视频播放,即同时可以在Ipad,Iphone等IOS平台上播放视频,并且只需简单代码就可以使用,无需了解html5插入视频的方法。 软件在对各平台的兼容性方面适应性比较强,比如你可以设置在Android平台或wp平台上在用户已安装flash插件的时候使用flash播放器,在没有装flash插件的情况下使用html5播放器。 ckplayer是一种前台使用的程序,不涉及到服务器程序(asp,php,jsp,.net),也不涉及到对服务器的操作(即不需要写入权限),所以不存在安全方面的问题。同时可以集成在任何程序中,包括asp,php,jsp,.net以及其它支持插入flash的环境中。 功能列表: 兼容SWF/HTML5跨平台播放 支持流行视频格式flv,f4v,mp4, 支持html5格式m3u8,webm,ogg theora,mp4 支持RTMP协议下的视频直播和回放 支持前置广告(swf,图片,视频) 支持前置广告多个随机/顺序播放 支持暂停广告(swf,图片)多个随机播放 支持缓冲广告,小窗口广告,滚动文字广告 支持多达6种形式的视频地址调用方式 支持多段视频无缝播放,支持多集连播 支持视频预览功能 自定义提示点功能,跳过片头片尾功能 bshare完美视频分享功能 调节视频尺寸,亮度,对比度,色相,饱和度功能 支持播放结束显示精彩视频推荐 支持自定义播放器界面,无需了解程序,即可自己制作出风格 支持js和播放器的互动操作,可以任意控制播放器的动作,比如暂停/播放 丰富的api接口,快速打造功能强大的插件
ckplayer是一款在网页上播放视频的免费的播放器,功能强大,体积小巧,跨平台,使用起来随心所欲。 <p>CKplayer播放器主要以adobe的flash(所使用的版本是CS5)平台开发,所以在支持flash插件的平台和浏览器上都可以使用,而无需下载其它插件,如果你需要修改完整版里提供的相关的flash源文件,请使用adobe的flash cs5以上版本打开源文件修改。</p><p>ckplayer同时也支持html5的视频播放,即同时可以在Ipad,Iphone等IOS平台上播放视频,并且只需简单代码就可以使用,无需了解html5插入视频的方法。</p><p>软件在对各平台的兼容性方面适应性比较强,比如你可以设置在Android平台或wp平台上在用户已安装flash插件的时候使用flash播放器,在没有装flash插件的情况下使用html5播放器。</p><p>ckplayer是一种前台使用的程序,不涉及到服务器程序(asp,php,jsp,.net),也不涉及到对服务器的操作(即不需要写入权限),所以不存在安全方面的问题。同时可以集成在任何程序中,包括asp,php,jsp,.net以及其它支持插入flash的环境中。</p><p>功能列表:</p><p>兼容SWF/HTML5跨平台播放</p><p>支持流行视频格式flv,f4v,mp4,</p><p>支持html5格式m3u8,webm,ogg theora,mp4</p><p>支持RTMP协议下的视频直播和回放</p><p>支持前置广告(swf,图片,视频)</p><p>支持前置广告多个随机/顺序播放</p><p>支持暂停广告(swf,图片)多个随机播放</p><p>支持缓冲广告,小窗口广告,滚动文字广告</p><p>支持多达6种形式的视频地址调用方式</p><p>支持多段视频无缝播放,支持多集连播</p><p>支持视频预览功能</p><p>自定义提示点功能,跳过片头片尾功能</p><p>bshare完美视频分享功能</p><p>调节视频尺寸,亮度,对比度,色相,饱和度功能</p><p>支持播放结束显示精彩视频推荐</p><p>支持自定义播放器界面,无需了解程序,即可自己制作出风格</p><p>支持js和播放器的互动操作,可以任意控制播放器的动作,比如暂停/播放</p><p>丰富的api接口,快速打造功能强大的插件</p><p> </p><p>CKplayer-超酷网页视频播放器 v6.8 更新日志: </p>一:修复Bug 1:修复了已知的bug 2:修正了html5中监听time的错误 3:新加了几个交互函数 4:风格设置里增加了提示文本底色及圆角的功能 二:增加功能 1:增加了全屏模式下启用键盘输入的功能,设置方法分二步:调用代码var flashvars={}里增加fs:'1',以及var params={}里增加allowFullScreenInteractive:true 2:增加了在播放器内显示文本元件的功能。 3:增加了新的调用视频地址的方式:json调用。 感谢各位朋友对ckplayer的支持,在本次升级中,许多朋友提供了好的建议,虽然因为考虑到大部分用户的需求后决定一些功能没有对其进行更改。 6.7升级至6.8的方法 1:替换ckplayer.swf 2:替换ckplayer.js中的315行((function() {)及以下的所有内容 3:ckplayer.js中setup属性的第21个值在正式使用时设置成0 4:language.xml里增加了控制提示文字的样式:{font color='#FFFFFF' face='Microsoft YaHei' size='16'}[$prompt]{/font} 5:languae.xml里增加了新版默认风格里的设置的提示文本样式:{font color='#FFFFFF' face='Microsoft YaHei,\u5fae\u8f6f\u96c5\u9ed1' size='14'}设置{/font} (也可以直接替换掉languae.xml) 6:如果要使用新的风格,需要替换掉目前的style.swf,并同时删除ckplayer.js中和ckplayer.xml中安装的插件代码。详细的可以对照新的ckplayer.js中的: function ckcpt() {   var cpt = '';   return cpt; } 以及ckplayer.xml中的的节点
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值