-
滚动时不播放,但是要亮起,当前屏幕内,item view显示百分比最大的一个。
-
停止滚动且手指抬起时自动播放。
-
播放完当前的视频,自动滚动到下一个并自动播放。
-
正在播放的当前视频,快要播放完毕时,弹出TextView提示播放下一个,点击TextView自动滚动到下一个。
-
Activity 在前台播放时,进入后台暂停播放,再进入前台时 自动播放视频。
-
Activity 在前台暂停播放时,进入后台,再进入前台时 还是暂停播放之前的视频状态。
-
Activity finish 的时候,停止播放 销毁。
-
播放视频,当控制器隐藏时,播放器底边播放进度条显示。
-
播放列表到最后一个时的操作判断。
动态添加播放器:而不是每个item的布局都有一个播放器,视频列表页Activity 全局就初始化一个播放器view,动态添加到列表item中,也就是说当要播放当前的item时将播放器添加到item预留的ViewGroup容器中 。同时列表更加流畅,易方便于播放的处理,销毁和停止播放器。
全屏播放的处理: 在视频列表页的Activity 布局文件中预留一个ViewGroup容器,当点击全屏播放时,隐藏列表,并将列表的播放器移除列表,显示布局文件中预留的容器,将播放器添加到这个容器中,这样视频会继续从当前的进度播放,完全不用再去处理复杂的逻辑。这方法需要在Activity中预留一个放置播放器的宽高都match_parent的ViewGroup,大小切换就是把播放器添加到本来的小容器和添加到全屏的ViewGroup中来回切换,对于播放器的监听器也不用过多干预。
如看下图所示,全局只对一个播放器操作
是不是忽然之间貌似顿开,网易新闻或者腾讯新闻的视频列表也应该是这样实现的。
-
在视频列表Activity 全局初始化一个播放器的view
-
停止滚动手指抬起时 动态添加播放器,开始播放视频,并获取之前的播放进度
private void aoutPlayVideo(final RecyclerView recyclerView) { if (!lVideoView.isPlayer()) { VideoFeedHolder childViewHolder = (VideoFeedHolder) recyclerView.findViewHolderForAdapterPosition(itemPosition); if (childViewHolder != null) { // 注册监听以及隐藏蒙层 childViewHolder.registerVideoPlayerListener(this); childViewHolder.goneMasked(); childViewHolder.playerWifi(); // int netType = NetChangeManager.getInstance().getNetType(); // if (netType == 1 || Constants.VIDEO_FEED_WIFI) { // WiFi的情况下,或者允许不是WiFi情况下继续播放 // 动态添加播放器 View itemView = childViewHolder.itemView; FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video); frameLayout.removeAllViews(); ViewGroup last = (ViewGroup) lVideoView.getParent();//找到videoitemview的父类,然后remove if (last != null && last.getChildCount() > 0) { last.removeAllViews(); } frameLayout.addView(lVideoView); // 获取播放进度 TabFragMainBeanItemBean itemBean = itemBeens.get(itemPosition); long videoProgress = itemBean.videoProgress; long duration = itemBean.mDuration; if (videoProgress != 0 && videoProgress != duration) { // 跳转到之前的进度,继续播放 lVideoView.startLive(itemBean.video_url); lVideoView.setSeekTo(videoProgress); } else {//从头播放 lVideoView.startLive(itemBean.video_url); } // } } } }
从上面代码中我们可以看出,拿到当前正要播放视频的item中的容器,并将播放器添加到容器中,如果之前有播放过,拿取播放进度,并跳转到之前的进度继续播放。
-
滑动播放下一个视频时,停止播放上一个视频,并将播放器从item中移除记下当前item的播放进度,添加到下一个item的容器中,播放视频。
private void stopPlayer(int position) { VideoFeedHolder childViewHolder = (VideoFeedHolder) rl_video.findViewHolderForAdapterPosition(position); if (childViewHolder != null) { if (lVideoView.isPlayer()) { // 如果正在播放,则停止并记录播放进度,否则不调用这个方法 lVideoView.stopVideoPlay(); TabFragMainBeanItemBean itemBean = itemBeens.get(position); itemBean.videoProgress = currentPosition; itemBean.mDuration = mDuration; itemBeens.set(position, itemBean); } childViewHolder.visMasked();//显示蒙层 View itemView = childViewHolder.itemView; FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video); frameLayout.removeAllViews(); childViewHolder.unRegisterVideoPlayerListener();// 注意我们需要解除上一个item的监听,不然会注册多个监听 } }
横竖屏切换时的处理,按照上面的实现思路,看下面的代码
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); lVideoView.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {// 竖屏 orientation = false; full_screen.setVisibility(View.GONE); full_screen.removeAllViews(); rl_video_feed.setVisibility(View.VISIBLE); addPlayer(itemPosition); int mShowFlags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; full_screen.setSystemUiVisibility(mShowFlags); } else { // 横屏 orientation = true; rl_video_feed.setVisibility(View.GONE); ViewGroup viewGroup = (ViewGroup) lVideoView.getParent(); if (viewGroup == null) return; viewGroup.removeAllViews(); full_screen.addView(lVideoView); full_screen.setVisibility(View.VISIBLE); int mHideFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; full_screen.setSystemUiVisibility(mHideFlags); } } /** * 添加播放器 * * @param position */ private void addPlayer(int position) { VideoFeedHolder childViewHolder = (VideoFeedHolder) rl_video.findViewHolderForAdapterPosition(position); if (childViewHolder != null) { View itemView = childViewHolder.itemView; FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video); frameLayout.removeAllViews(); ViewGroup last = (ViewGroup) lVideoView.getParent();//找到videoitemview的父类,然后remove if (last != null && last.getChildCount() > 0) { last.removeAllViews(); } frameLayout.addView(lVideoView); } }
在Activity中预留一个放置播放器的宽高都match_parent的ViewGroup,大小切换就是把播放器添加到本来的小容器和添加到全屏的ViewGroup中来回切换,对于播放器的监听器也不用过多干预。
注意改变播放器view的大小.
/** * 大小屏切换播放器的处理 * * @param newConfig */ public void onConfigurationChanged(Configuration newConfig) { ViewGroup.LayoutParams layoutParams = fraVideoContainer.getLayoutParams(); if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { //竖屏 layoutParams.height = (int) getResources().getDimension(R.dimen.live_video_height); fraVideoContainer.setLayoutParams(layoutParams); } else {// 横屏 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; fraVideoContainer.setLayoutParams(layoutParams); } }
Activity/Fragment 的生命周期中处理,整个全局我们就一个播放器,生命周期中就可以很好的处理这个播放器
/** * Activity 不在前台时 暂停播放 */ @Override protected void onPause() { super.onPause(); if (!isThrumePause) {//若不是手动暂停,Activity进入后台自动暂停 lVideoView.onPause(); } } /** * Activity 重新进入前台 播放逻辑 */ @Override protected void onResume() { super.onResume(); Log.e("linksu", "onResume(VideoFeedDetailAct.java:558) isThrumePause" + isThrumePause); if (!isThrumePause) { //不是手动暂停且从后台进入前台 lVideoView.currentPlayer(); } else { //进入后台之前是暂停的状态,再次进入还是暂停的状态 lVideoView.startThumb(); } } /** * Activity 退出时 停止播放 */ @Override public void finish() { super.finish(); lVideoView.stopVideoPlay(); } /** * Activity 销毁时 销毁播放器 */ @Override protected void onDestroy() { super.onDestroy(); lVideoView.removeAllViews(); }
播放器的状态监听就可以在Activity/Fragment 中去处理,处理起来更加方便
@Override public void onVideoPrepared() { //准备播放 } @Override public void onVideoCompletion() {// 播放完成 if (itemPosition != lastItemPosition) { //若播放的不是最后一个,播放完成自动播放下一个 VideoFeedHolder childViewHolder = (VideoFeedHolder) rl_video.findViewHolderForAdapterPosition(itemPosition); if (childViewHolder != null) { // 播放完成将之前的播放进度清空 TabFragMainBeanItemBean itemBean = itemBeens.get(itemPosition); itemBean.videoProgress = 0; itemBean.mDuration = 0; itemBeens.set(itemPosition, itemBean); // 移除播放器 childViewHolder.visMasked(); View itemView = childViewHolder.itemView; FrameLayout frameLayout = (FrameLayout) itemView.findViewById(R.id.ll_video); frameLayout.removeAllViews(); childViewHolder.unRegisterVideoPlayerListener();// 注意我们需要解除上一个item的监听,不然会注册多个监听 } itemPosition = itemPosition + 1; playerPosition = itemPosition; ((LinearLayoutManager) rl_video.getLayoutManager()).scrollToPositionWithOffset(playerPosition, 20); aoutPlayVideo(rl_video); } } @Override public void onVideoError(int i, String error) { } @Override public void onBufferingUpdate() { } @Override public void onVideoStopped() { // 停止视频播放时,记录视频的播放位置 } @Override public void onVideoPause() { //暂停视频播放 } @Override public void onVideoThumbPause() { // 手动暂停视频播放 isThrumePause = true; } @Override public void onVideoThumbStart() { // 手动开始视频播放 isThrumePause = false; } @Override public void onVideoPlayingPro(long currentPosition, long mDuration, int mPlayStatus) {//获取播放进度 this.currentPosition = currentPosition; this.mDuration = mDuration; if (itemPosition != lastItemPosition) { //若播放的不是最后一个,弹出播放下一个的提示 float percent = (float) ((double) currentPosition / (double) mDuration); DecimalFormat fnum = new DecimalFormat("##0.0"); float c_percent = 0; c_percent = Float.parseFloat(fnum.format(percent)); if (0.8 <= c_percent) { videoTips(); } else { missVideoTips(); } } }
这样我们就完成了整个优化过程,其实就是一个带图的列表,动态的添加播放器,这样处理不仅内存消耗占的很少而且,没有任何复杂的逻辑。 最后的最后,点击【阅读原文】,请不要客气,尽情的砸issue或者pull request过来吧!
第一时间获得【不止个人原创 android/音视频技术干货,问题深度总结,FrameWork源码解析,插件化研究,FFmpeg研究,直播技术,最新开源项目推荐,还有更多职场思考】,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码