播放器之 – 小屏/全屏 无缝切换

时间:2021-7-21 作者:qvyue

Activity 显示视图结构

DecorView是activity窗口的根视图,每个Activity在调用setContent(xxx)之后,会把对应的xml或者view添加到DecorView –> ContentParent 的ViewGroup中

播放器之 - 小屏/全屏 无缝切换
视图结构

APP在Activity中获取对应DecorView的方法

val decorView = (activity?.window?.decorView as ViewGroup?)

无缝切换原理

小屏 -> 全屏

按照正常的流程,咱们是这么干的:

  • 移出:把MXVideoView从默认的Layout中移出
  • 占位:复制一个相同的MXVideoView(相同的地址、配置、播放器等等),加入原Layout。这里是防止原Layout在MXVideoView移出之后,父容器高度/宽度 变化
  • 加入根DecorView:将被移出的MXVideoView,添加至根DecorView
  • 处理页面旋转,顶部状态栏的隐藏

代码流程:

// 移出
val parent = (parent as ViewGroup?) ?: return
// 这里存储移出前的Layout信息,如:index,LayoutParams,宽度,高度 等
val target = MXParentView(
      parent.indexOfChild(this),
      parent, layoutParams, width, height
 )
parent.removeView(this)

//占位
// 通过反射创建一个自己
val constructor = this::class.java.getConstructor(Context::class.java)
val selfClone = constructor.newInstance(context)
selfClone.id = this.id
selfClone.mxPlayerClass = mxPlayerClass
selfClone.dimensionRatio = dimensionRatio
selfClone.config.cloneBy(config)

selfClone.minimumWidth = target.width // 宽度占位
selfClone.minimumHeight = target.height // 高度占位
target.parentViewGroup.addView(selfClone, target.index, target.layoutParams)

// 加入根DecorView
val decorView = (activity?.window?.decorView as ViewGroup?)
val fullLayout = LayoutParams(
     ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
decorView.addView(this, fullLayout)

// 全屏处理
val activity = findActivity(context) ?: return
val currentActivityId = activity.toString()

activity.window?.setFlags(
    WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN
)

var uiOptions = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        or View.SYSTEM_UI_FLAG_FULLSCREEN
        or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    uiOptions = uiOptions or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}
val currentSystemVisibility = activity.window?.decorView?.systemUiVisibility
if (!activityFlagMap.containsKey(currentActivityId)) {
    activityFlagMap[currentActivityId] = currentSystemVisibility
}
activity.window?.decorView?.systemUiVisibility = uiOptions

// 屏幕方向处理
val activity = findActivity(context) ?: return
val currentActivityId = activity.toString()
if (!activityOrientationMap.containsKey(currentActivityId)) {
    activityOrientationMap[currentActivityId] = activity.requestedOrientation
}
activity.requestedOrientation = when (orientation) {
    MXOrientation.DEGREE_0 -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    MXOrientation.DEGREE_90 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
    MXOrientation.DEGREE_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
    MXOrientation.DEGREE_270 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}

如果你按照上面的操作来处理,会发现一个问题:切换全屏后,视频黑屏!

那么问题是什么呢?
MXVideo播放视频使用的显示容器是TextureView,当TextureView在被移出并原Layout,并添加到DecorView时,TextureView的生命周期回调如下:

onSurfaceTextureDestroyed
onSurfaceTextureAvailable

TextureView经过这一流程后,会重新创建所持有的SurfaceTexture,这时播放器所持有的的SurfaceTexture与TextureView所持有的已经不是同一个对象,所以播放器无法输出画面。
所以只需要将MXVideo所持有的SurfaceTexture在TextureView回调onSurfaceTextureAvailable后重新还给TextureView就可以了!

// IMXPlayer 中已定义
protected var mSurfaceTexture: SurfaceTexture? = null

    // TextureView 生命周期回调
    override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
        if (mSurfaceTexture == null) {
            // 初始化时,持有TextureView的SurfaceTexture对象
            mSurfaceTexture = surface
            prepare()
        } else {
            // 这里需要将已经存在的SurfaceTexture还给TextureView
            mTextureView?.surfaceTexture = mSurfaceTexture
        }
    }

详细代码可以查看
com.mx.video.player.MXSystemPlayer
com.mx.video.MXVideo

全屏 -> 小屏

这个就比较简单了,流程如下:

  • 移出:把占位MXVideoView从默认的Layout中移出
  • 脱离DecorView:将播放器从DecorView中移出
  • 复位:将播放器添加至原Layout中,还原LayoutParams等相关信息
  • 还原屏幕方向以及顶部状态栏的显示
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。