logo
当前位置:首 页 > 移动开发 >android > 查看文章

VideoFeed实现的功能

  1. 滚动时不播放,但是要亮起,当前屏幕内,item view显示百分比最大的一个。
  2. 停止滚动且手指抬起时自动播放。
  3. 播放完当前的视频,自动滚动到下一个并自动播放。
  4. 正在播放的当前视频,快要播放完毕时,弹出TextView提示播放下一个,点击TextView自动滚动到下一个。
  5. Activity 在前台播放时,进入后台暂停播放,再进入前台时 自动播放视频。
  6. Activity 在前台暂停播放时,进入后台,再进入前台时 还是暂停播放之前的视频状态。
  7. Activity finish 的时候,停止播放 销毁。
  8. 播放视频,当控制器隐藏时,播放器底边播放进度条显示。
  9. 播放列表到最后一个时的操作判断。

VideoFeed效果图

如何解决问题?

思想上的转变

动态添加播放器:而不是每个item的布局都有一个播放器,视频列表页Activity 全局就初始化一个播放器view,动态添加到列表item中,也就是说当要播放当前的item时将播放器添加到item预留的ViewGroup容器中 。同时列表更加流畅,易方便于播放的处理,销毁和停止播放器。

全屏播放的处理: 在视频列表页的Activity 布局文件中预留一个ViewGroup容器,当点击全屏播放时,隐藏列表,并将列表的播放器移除列表,显示布局文件中预留的容器,将播放器添加到这个容器中,这样视频会继续从当前的进度播放,完全不用再去处理复杂的逻辑。这方法需要在Activity中预留一个放置播放器的宽高都match_parent的ViewGroup,大小切换就是把播放器添加到本来的小容器和添加到全屏的ViewGroup中来回切换,对于播放器的监听器也不用过多干预。

如看下图所示,全局只对一个播放器操作

是不是忽然之间貌似顿开,网易新闻或者腾讯新闻的视频列表也应该是这样实现的。

1. 如何动态的给item添加播放器

  • 在视频列表Activity 全局初始化一个播放器的view
  • 停止滚动手指抬起时 动态添加播放器,开始播放视频,并获取之前的播放进度
  1.    private void aoutPlayVideo(final RecyclerView recyclerView) {
  2.        if (!lVideoView.isPlayer()) {
  3.            VideoFeedHolder childViewHolder = (VideoFeedHolder) recyclerView.findViewHolderForAdapterPosition(itemPosition);
  4.            if (childViewHolder != null) {
  5.                // 注册监听以及隐藏蒙层
  6.                childViewHolder.registerVideoPlayerListener(this);
  7.                childViewHolder.goneMasked();
  8.                childViewHolder.playerWifi();
  9. //                int netType = NetChangeManager.getInstance().getNetType();
  10. //                if (netType == 1 || Constants.VIDEO_FEED_WIFI) { // WiFi的情况下,或者允许不是WiFi情况下继续播放
  11.                // 动态添加播放器
  12.                View itemView = childViewHolder.itemView;
  13.                FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video);
  14.                frameLayout.removeAllViews();
  15.                ViewGroup last = (ViewGroup) lVideoView.getParent();//找到videoitemview的父类,然后remove
  16.                if (last != null && last.getChildCount() > 0) {
  17.                    last.removeAllViews();
  18.                }
  19.                frameLayout.addView(lVideoView);
  20.                // 获取播放进度
  21.                TabFragMainBeanItemBean itemBean = itemBeens.get(itemPosition);
  22.                long videoProgress = itemBean.videoProgress;
  23.                long duration = itemBean.mDuration;
  24.                if (videoProgress != 0 && videoProgress != duration) { // 跳转到之前的进度,继续播放
  25.                    lVideoView.startLive(itemBean.video_url);
  26.                    lVideoView.setSeekTo(videoProgress);
  27.                } else {//从头播放
  28.                    lVideoView.startLive(itemBean.video_url);
  29.                }
  30. //                }
  31.            }
  32.        }
  33.    }

从上面代码中我们可以看出,拿到当前正要播放视频的item中的容器,并将播放器添加到容器中,如果之前有播放过,拿取播放进度,并跳转到之前的进度继续播放。

  • 滑动播放下一个视频时,停止播放上一个视频,并将播放器从item中移除记下当前item的播放进度,添加到下一个item的容器中,播放视频。
  1.   private void stopPlayer(int position) {
  2.        VideoFeedHolder childViewHolder = (VideoFeedHolder) rl_video.findViewHolderForAdapterPosition(position);
  3.        if (childViewHolder != null) {
  4.            if (lVideoView.isPlayer()) { // 如果正在播放,则停止并记录播放进度,否则不调用这个方法
  5.                lVideoView.stopVideoPlay();
  6.                TabFragMainBeanItemBean itemBean = itemBeens.get(position);
  7.                itemBean.videoProgress = currentPosition;
  8.                itemBean.mDuration = mDuration;
  9.                itemBeens.set(position, itemBean);
  10.            }
  11.            childViewHolder.visMasked();//显示蒙层
  12.            View itemView = childViewHolder.itemView;
  13.            FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video);
  14.            frameLayout.removeAllViews();
  15.            childViewHolder.unRegisterVideoPlayerListener();// 注意我们需要解除上一个item的监听,不然会注册多个监听
  16.        }
  17.    }

横竖屏切换时的处理,按照上面的实现思路,看下面的代码

  1.    @Override
  2.    public void onConfigurationChanged(Configuration newConfig) {
  3.        super.onConfigurationChanged(newConfig);
  4.        lVideoView.onConfigurationChanged(newConfig);
  5.        if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {// 竖屏
  6.            orientation = false;
  7.            full_screen.setVisibility(View.GONE);
  8.            full_screen.removeAllViews();
  9.            rl_video_feed.setVisibility(View.VISIBLE);
  10.            addPlayer(itemPosition);
  11.            int mShowFlags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  12.                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  13.                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
  14.            full_screen.setSystemUiVisibility(mShowFlags);
  15.        } else { // 横屏
  16.            orientation = true;
  17.            rl_video_feed.setVisibility(View.GONE);
  18.            ViewGroup viewGroup = (ViewGroup) lVideoView.getParent();
  19.            if (viewGroup == null)
  20.                return;
  21.            viewGroup.removeAllViews();
  22.            full_screen.addView(lVideoView);
  23.            full_screen.setVisibility(View.VISIBLE);
  24.            int mHideFlags =
  25.                    View.SYSTEM_UI_FLAG_LOW_PROFILE
  26.                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  27.                            | View.SYSTEM_UI_FLAG_FULLSCREEN
  28.                            | View.SYSTEM_UI_FLAG_IMMERSIVE
  29.                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  30.                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  31.            full_screen.setSystemUiVisibility(mHideFlags);
  32.        }
  33.    }
  34.  /**
  35.     * 添加播放器
  36.     *
  37.     * @param position
  38.     */
  39.    private void addPlayer(int position) {
  40.        VideoFeedHolder childViewHolder = (VideoFeedHolder) rl_video.findViewHolderForAdapterPosition(position);
  41.        if (childViewHolder != null) {
  42.            View itemView = childViewHolder.itemView;
  43.            FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video);
  44.            frameLayout.removeAllViews();
  45.            ViewGroup last = (ViewGroup) lVideoView.getParent();//找到videoitemview的父类,然后remove
  46.            if (last != null && last.getChildCount() > 0) {
  47.                last.removeAllViews();
  48.            }
  49.            frameLayout.addView(lVideoView);
  50.        }
  51.    }

在Activity中预留一个放置播放器的宽高都match_parent的ViewGroup,大小切换就是把播放器添加到本来的小容器和添加到全屏的ViewGroup中来回切换,对于播放器的监听器也不用过多干预。

注意改变播放器view的大小.

  1. /**
  2.     * 大小屏切换播放器的处理
  3.     *
  4.     * @param newConfig
  5.     */
  6.    public void onConfigurationChanged(Configuration newConfig) {
  7.        ViewGroup.LayoutParams layoutParams = fraVideoContainer.getLayoutParams();
  8.        if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { //竖屏
  9.            layoutParams.height = (int) getResources().getDimension(R.dimen.live_video_height);
  10.            fraVideoContainer.setLayoutParams(layoutParams);
  11.        } else {// 横屏
  12.            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
  13.            fraVideoContainer.setLayoutParams(layoutParams);
  14.        }
  15.    }

Activity/Fragment 的生命周期中处理,整个全局我们就一个播放器,生命周期中就可以很好的处理这个播放器

  1. /**
  2.     * Activity 不在前台时 暂停播放
  3.     */
  4.    @Override
  5.    protected void onPause() {
  6.        super.onPause();
  7.        if (!isThrumePause) {//若不是手动暂停,Activity进入后台自动暂停
  8.            lVideoView.onPause();
  9.        }
  10.    }
  11.    /**
  12.     * Activity 重新进入前台 播放逻辑
  13.     */
  14.    @Override
  15.    protected void onResume() {
  16.        super.onResume();
  17.        Log.e("linksu",
  18.                "onResume(VideoFeedDetailAct.java:558) isThrumePause" + isThrumePause);
  19.        if (!isThrumePause) { //不是手动暂停且从后台进入前台
  20.            lVideoView.currentPlayer();
  21.        } else { //进入后台之前是暂停的状态,再次进入还是暂停的状态
  22.            lVideoView.startThumb();
  23.        }
  24.    }
  25.    /**
  26.     * Activity 退出时 停止播放
  27.     */
  28.    @Override
  29.    public void finish() {
  30.        super.finish();
  31.        lVideoView.stopVideoPlay();
  32.    }
  33.    /**
  34.     * Activity 销毁时 销毁播放器
  35.     */
  36.    @Override
  37.    protected void onDestroy() {
  38.        super.onDestroy();
  39.        lVideoView.removeAllViews();
  40.    }

播放器的状态监听就可以在Activity/Fragment 中去处理,处理起来更加方便

  1. @Override
  2.    public void onVideoPrepared() { //准备播放
  3.    }
  4.    @Override
  5.    public void onVideoCompletion() {// 播放完成
  6.        if (itemPosition != lastItemPosition) { //若播放的不是最后一个,播放完成自动播放下一个
  7.            VideoFeedHolder childViewHolder = (VideoFeedHolder) rl_video.findViewHolderForAdapterPosition(itemPosition);
  8.            if (childViewHolder != null) {
  9.                // 播放完成将之前的播放进度清空
  10.                TabFragMainBeanItemBean itemBean = itemBeens.get(itemPosition);
  11.                itemBean.videoProgress = 0;
  12.                itemBean.mDuration = 0;
  13.                itemBeens.set(itemPosition, itemBean);
  14.                // 移除播放器
  15.                childViewHolder.visMasked();
  16.                View itemView = childViewHolder.itemView;
  17.                FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video);
  18.                frameLayout.removeAllViews();
  19.                childViewHolder.unRegisterVideoPlayerListener();// 注意我们需要解除上一个item的监听,不然会注册多个监听
  20.            }
  21.            itemPosition = itemPosition + 1;
  22.            playerPosition = itemPosition;
  23.            ((LinearLayoutManager) rl_video.getLayoutManager()).scrollToPositionWithOffset(playerPosition, 20);
  24.            aoutPlayVideo(rl_video);
  25.        }
  26.    }
  27.    @Override
  28.    public void onVideoError(int i, String error) {
  29.    }
  30.    @Override
  31.    public void onBufferingUpdate() {
  32.    }
  33.    @Override
  34.    public void onVideoStopped() { // 停止视频播放时,记录视频的播放位置
  35.    }
  36.    @Override
  37.    public void onVideoPause() { //暂停视频播放
  38.    }
  39.    @Override
  40.    public void onVideoThumbPause() { // 手动暂停视频播放
  41.        isThrumePause = true;
  42.    }
  43.    @Override
  44.    public void onVideoThumbStart() { // 手动开始视频播放
  45.        isThrumePause = false;
  46.    }
  47.    @Override
  48.    public void onVideoPlayingPro(long currentPosition, long mDuration, int mPlayStatus) {//获取播放进度
  49.        this.currentPosition = currentPosition;
  50.        this.mDuration = mDuration;
  51.        if (itemPosition != lastItemPosition) { //若播放的不是最后一个,弹出播放下一个的提示
  52.            float percent = (float) ((double) currentPosition / (double) mDuration);
  53.            DecimalFormat fnum = new DecimalFormat("##0.0");
  54.            float c_percent = 0;
  55.            c_percent = Float.parseFloat(fnum.format(percent));
  56.            if (0.8 <= c_percent) {
  57.                videoTips();
  58.            } else {
  59.                missVideoTips();
  60.            }
  61.        }
  62.    }

这样我们就完成了整个优化过程,其实就是一个带图的列表,动态的添加播放器,这样处理不仅内存消耗占的很少而且,没有任何复杂的逻辑。 最后的最后,点击【阅读原文】,请不要客气,尽情的砸issue或者pull request过来吧!

更详细的讲解、源码及Demo参见:https://github.com/susussa/VideoFeed

一个有大大梦想但是没有支撑这梦想的技术的攻城狮,大家就当这个博客是你的技术工具箱吧,我会吧我工作中遇到的问题贴在这,大家需要的记得把我放到收藏夹哦! 百度输入“子傲代码”就可以了

—— 陈子傲

admin
你可能也喜欢Related Posts
众说纷纭Comments
大眼 可爱 大笑 坏笑 害羞 发怒 折磨 快哭了 大哭 白眼 晕 流汗 困 腼腆 惊讶 憨笑 色 得意 骷髅 囧 睡觉 眨眼 亲亲 疑问 闭嘴 难过 淡定 抗议 鄙视 猪头
小提示:直接粘贴图片到输入框试试
努力发送中...
footer logo
未经许可请勿自行使用、转载、修改、复制、发行、出售、发表或以其它方式利用本网站之内容
Copyright ©ziao design Studio All Rights Reserved.沪ICP备14052271号-3