Android列表页播视频看这一篇就足够了

作者 |  DK_BurNIng
地址 |  juejin.im/post/5e5f4a5be51d4526cb162775

阅读本文解决什么问题?


解决android 滑动列表页自动播视频中的一些技术难点。助力更好的实现类似需求。不涉及到播放器的具体编解码技术,因为各家用的播放器可能都不一样(其实是我不会~)

何时播视频最合适?

建议在滑动停止的时候播视频,还在滑动的时候建议不播。目前大厂的app基本上都是基于这个思路来做。这么做的主要原因有两点:
  • 就如同我们平时做listview或者RecyclerView列表页优化的时候,考虑到图片的加载涉及到io,还有少部分的decode操作,所以我们都会在滑动停止或者是滑动速度变低的时候才加载图片,降低cpu的负载和内存抖动来达到提高页面滑动流畅性的目的,所以播视频也是一样的,在列表页停止滑动的时候才播放视频是可以大大提高页面流畅性的
  • 即使你的播放器是自研的,但是依旧不建议在列表页滑动的时候进行视频播放,考虑如下场景:A视频正在播放, 此时进行滑动,B视频到了播放区域开始播B视频,此时A从播视频的状态切到停止播放视频的视频封面图状态,B 视频从视频封面图状态切到播放状态(甚至还有黑底的loading的状态),可以想象滑动速度一旦变快,视觉上的效果 就会非常难受,那种一顿顿的效果,因为你列表页中的item几乎全在同一时刻切换图片状态。

所以要在哪个地方开始播视频?

以RecyclerView 为例:
Android列表页播视频看这一篇就足够了
所以我们其实只要在这个回调中进行
Android列表页播视频看这一篇就足够了
这个case 的条件 就代表列表页滑动停止。这就是我们播视频的时机了

如何合理规划播视频的区域?

我们以最流行的做法:符合人类视觉感知的,选择 从上到下列表页中第一个完整展示视频区域的 item 进行播放 有人说我们直接用findFirstCompletelyVisibleItemPosition方法不就行了?其实这样是不完美的,因为我们列表页中的item 并不只是一个单一的视频播放区域,他还有标题,描述,阅读数等等**。你的视频播放区域其实只占你整个item的一部分**
所以我们就要计算出来 视频区域完整显示 这个条件。代码如下:

case SCROLL_STATE_IDLE:
                //取第一个展示出来的item
                int firstPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
                //最后一个展示出来的item
                int lastPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();
                //开始遍历
                for (int i = firstPosition; i <= lastPosition; i++) {
                    //取出这个列表页中的item
                    View itemView = recyclerView.getLayoutManager().findViewByPosition(i);
                    //一定要先判空 因为有时上面这个函数真的会返回null
                    //然后接着判断 是不是有视频播放组件 比如有的列表页除了有视频item还有纯图片的item
                    //所以要判定这个条件
                    if (itemView != null && itemView.findViewById(R.id.video_player) != null) {
                        VideoPlayer videoPlayer = itemView.findViewById(R.id.video_player);
                        if (isCanBePlayedByRect(videoPlayer)) {
                            safePlayVideoMethod(videoPlayer);
                            break;
                        }
                    }
                }

private boolean isCanBePlayedByRect(VideoPlayer videoPlayer) {
        Rect rect = new Rect();
        //这里就可以取出来视频播放区域的坐标轴了
        videoPlayer.getLocalVisibleRect(rect);
        int videoHeight = videoPlayer.getHeight();
        //符合这个条件就意味着 整个视频播放区域 都是完整的呈现在视频中的
        final boolean playFlag = (rect.top == 0 && rect.bottom == videoHeight)
        return playFlag;
    }

达成这个条件 我们基本的算法就写好了,但是这样仍旧不完美。

还有其他条件需要判定?
考虑另外一种复杂的情况,比如说看下面这张图:

Android列表页播视频看这一篇就足够了

我们可以看到列表页中的最后2个item其实 是已经完全露出屏幕的,他是符合我们上一小节的判定条件的。但是视觉上我们仍然是觉得 这2个视频播放区域是不完整的,因为被底部的tab 遮盖住了。
甚至有的用户会把虚拟键给调出来。如下图

Android列表页播视频看这一篇就足够了

这样我们的item展示的区域被遮盖的就更多了。用前面一小节的算法已经不够了。
此时我们需要更进一步,针对前面的算法做一定的修改。有人会说,我们直接用底部tab的高度减一下不就行了么?这样其实也可以,但是这样做 不太优雅,原因有2:
  • 如果这个tab以后高度修改了,你能保证每次别人的修改你都知道么?
  • 除了有底部tab还有虚拟键的高度啊,这样一算 不是太复杂了么?
我们这里可以用另外一种比较好的方法规避上述的问题:
直接取底部tab的top坐标(针对整个坐标轴而言)。
我们可以在activity的第一帧绘制完毕以后 取一下这个坐标:

if (mTabWidget != null && MAIN_PAGE_BOTTOM_TAB_RECT_TOP == 0) {
            Rect rect = new Rect();
            //注意 我们这里 使用的是getGlobalVisibleRect 而不是getLocal了
            //区别就是 global取得就是 针对全屏幕的坐标轴
            mTabWidget.getGlobalVisibleRect(rect);
            MAIN_PAGE_BOTTOM_TAB_RECT_TOP = rect.top;
        }
到这里我们就拿到了 底部tab 这个view的 top的坐标值了。所以剩下的条件我们只要增加一条
视频播放组件的 全屏bottom的值 要大于 我们这个tab的top的值。只有这样才能保证 我们视频的view 是没有被遮盖的
private boolean isCanBePlayedByRect(VideoPlayer videoPlayer) {
        Rect rect = new Rect();
        videoPlayer.getLocalVisibleRect(rect);
        Rect gRect = new Rect();
        //把视频view的全局 bottom坐标 也取出来 与tab的top坐标做对比
        videoPlayer.getGlobalVisibleRect(gRect);
        int videoHeight = videoPlayer.getHeight();
        final boolean playFlag = (rect.top == 0 && rect.bottom == videoHeight && gRect.bottom < MAIN_PAGE_BOTTOM_TAB_RECT_TOP);
        return playFlag;
    }

什么时候停止播放?

有人说 我们用
Android列表页播视频看这一篇就足够了

这个回调 会简单一些?
其实这样也是不完美的,还是前面那个问题,有些item 他就是有其他要素要展示, 就会出现 视频区域已经看不到了,被滑走了, 此时理应停止播放,但是因为还有比如视频阅读数等东西恰好在列表可是区域范围之内 导致这个item无法进入这个回调。
为了解决这个问题,我们使用如下方案:在列表页滑动的时候,判定视频区域是否全部在列表页之外,人已经看不到了,符合这个条件就停止播放视频
代码如下:

int position = 0;
            if (dy > 0) {
                //dy>0 代表 item 从下往上 被划出屏幕的 所以我们只需要判定最上面一个可视的item即可
                position = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
            } else if (dy < 0) {
                            //dy>0 代表 item 从上往下 被划出屏幕的 所以我们只需要判定最下面一个可视的item即可
                position = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
            }

            View itemView = mRecyclerView.getLayoutManager().findViewByPosition(position);
            if (itemView != null) {
                VideoPlayer videoPlayer = itemView.findViewById(R.id.video_player);
                if (videoPlayer != null) {
                    Rect rect = new Rect();
                    Rect gRect = new Rect();
                    vivoSpaceVideoPlayer.getLocalVisibleRect(rect);
                    vivoSpaceVideoPlayer.getGlobalVisibleRect(gRect);
                    //item 从下往上划出屏幕 的判定条件
                    final boolean stopFlagForDownToUp = (dy > 0 && rect.bottom - rect.top <= STOP_FLAG_PX_VALUE);
                    //item 从上往下划出屏幕的 判定条件 ----有底部tab
                    final boolean stopFlagForUptoDown1 = (dy < 0 && gRect.top > (MAIN_PAGE_BOTTOM_TAB_RECT_TOP - STOP_FLAG_PX_VALUE));
                    //item 从上往下划出屏幕的 判定条件 ----没有底部tab
                    final boolean stopFlagForUptoDown2 = (dy < 0 && rect.top != 0);
                    if (stopFlagForDownToUp || stopFlagForUptoDownHasParent || stopFlagForUptoDownNoParent) {
                        videoPlayer.release();
                    }
                }
            }

这里的算法比较简单,我就不一一解释了,唯一要说一下的就是这个

//定义个阈值 停止播放判定使用
    private static final int STOP_FLAG_PX_VALUE = 5;

为什么要加一个这个?因为针对全屏坐标而言,有时候滑动快了并不会经过某一个值,所以我们要有一定的余量。例如 我们条件是 当我们的视频区域的top坐标(滑动的时候会从例如 1900开始递增) 大于 tab的top坐标(假设这个值是2100)的时候  就判定被遮盖了 停止滑动, 并不代表视频区域的top坐标是 1900 1901 1902 这样1px 1px的递增到2100的。有时候他会忽略掉,比如某些手机经常出现1997 1998 1999 2102 等等 情况,所以这里我们要一定的余量。确保 视频一定会被停止播放。

怎么确保列表页第一次被渲染的时候就自动播放?

当我们网络数据回来以后 ,我们会把值set到RecyclerView 里面,然后notify,此时界面就有展示了,但是这个时候 如果用户没有滑动,又恰好第一屏的item有一个是视频,那我们怎么触发这种条件的自动播放呢?
其实RecyclerView有个特性就是 第一次渲染完毕以后 onScrolled会被执行一次,所以我们只要在这里做个标志位 然后去播放一次 就行了。

@Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            if (!mRvInitFlag) {
                playFirstVideoView();
                mRvInitFlag = true;
                return;
            }


private void playFirstVideoView() {
        VLog.d(TAG, "method in playFirstVideoView");
        if (mRecyclerView.getLayoutManager() != null) {
            //只有是 视频 才有意义,如果都不是视频 那没有任何意义

            int firstPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
            int lastPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
            for (int i = firstPosition; i <= lastPosition; i++) {
                // 如果 i<0 则直接跳过 因为毫无意义
                if (i < 0) {
                    continue;
                }
                View itemView = mRecyclerView.getLayoutManager().findViewByPosition(i);
                if (itemView != null && itemView.findViewById(R.id.video_player) != null) {
                    VideoPlayer videoPlayer = itemView.findViewById(R.id.video_player);
                    Rect rect = new Rect();
                    Rect gRect = new Rect();
                    videoPlayer.getLocalVisibleRect(rect);
                    videoPlayer.getGlobalVisibleRect(gRect);
                    int videoHeight = videoPlayer.getHeight();
                    //只要第一个元素 可视区域是完整的,那么就直接给他播放
                    if ((rect.bottom - rect.top) == videoHeight && gRect.bottom < MAIN_PAGE_BOTTOM_TAB_RECT_TOP && gRect.bottom > 0) {
                        safePlayVideoMethod(videoPlayer);
                        //既然找到了 那么就直接播放 不要再管其他的了
                        break;
                    }
                }
            }

    }

版权申明:内容来源网络,版权归原创者所有。除非无法确认,都会标明作者及出处,如有侵权,烦请告知,我们会立即删除并致歉!


原文始发于微信公众号(互联网程序员):Android列表页播视频看这一篇就足够了

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/24123.html

(1)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!