代码地址如下:
http://www.demodashi.com/demo/11201.html

一、准备工作

  • AndroidStudio 开发环境
  • 需要下载七牛的开源播放器SDK
  • 本例子实现了仿网易/QQ空间视频列表滚动连播炫酷效果

二、程序实现

实现的功能

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

实现思路

看下面这篇文章 从实现到优化的所有填坑的过程

视频列表滚动连播技术探究系列 http://www.jianshu.com/nb/15022458

存在哪些问题?

  1. 每个item中都有一个播放器,如何销毁呢?内存占用会很大?
  2. 全屏播放视频时,切换会有问题?
  3. 每个item的播放器都不容易控制?
  4. 全屏播放时,进度如何处理

如何解决问题?

思想上的转变

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

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

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



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

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

  • 在视频列表Activity 全局初始化一个播放器的view
  lVideoView = new LVideoView(this);//初始化播放器
  • 停止滚动手指抬起时 动态添加播放器,开始播放视频,并获取之前的播放进度
    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();
}
}
}

这样我们就完成了整个优化过程,其实就是一个带图的列表,动态的添加播放器,这样处理不仅内存消耗占的很少而且,没有任何复杂的逻辑。

License

  MIT License

  Copyright (c) 2017 苏福鹿

  Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

三、运行效果

仿网易/QQ空间视频列表滚动连播炫酷效果

代码地址如下:
http://www.demodashi.com/demo/11201.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

仿网易/QQ空间视频列表滚动连播炫酷效果的更多相关文章

  1. swiper3d横向滚动多张炫酷切换banner

    最近有了个新需求,swiper3d横向滚动多张炫酷切换banner要和elementUI里边走马灯的卡片化card 类似,但是还需要h5手机触摸滚动啊啊啊啊,昨天折腾了半个早上总算完成,今天乖乖跑来m ...

  2. 谷歌浏览器无法播放QQ空间视频动画的解决方案

    https://qzonestyle.gtimg.cn/qzone/photo/v7/js/module/flashDetector/flash_tutorial.pdf Chrome开启⽅法 1. ...

  3. 模仿手机qq空间头部向上滚动颜色加深

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. qq 空间视频地址 的 有效期 403

    大约 在24-48小时内 200 http://vwecam.gtimg.com/1006_aa21a85ef3d245f19535cf2ab941ccbb.f0.mp4?ptype=http& ...

  5. iOS 相似QQ空间表视图下拉头部视图放大效果实现

    UITableView 是 UIScrollView 的子类. 所以 UIScrollView 的代理方法.在UITableView 上相同可以得到适用. 既然如此那么我们就行知道.在表格下拉的过程中 ...

  6. 仿网易新闻 ViewPager 实现图片自动轮播

    新闻 App 首页最上方一般会循环播放热点图片,如下图所示. 本文主要介绍了利用 ViewPager 实现轮播图片,图片下方加上小圆点指示器标记当前位置,并利用 Timer+Handler 实现了自动 ...

  7. QQ空间HD(6)-实现自定义的选项卡切换效果

    DJTabbarButton.m #import "DJTabbarButton.h" @implementation DJTabbarButton - (instancetype ...

  8. Android项目实战(十六):QQ空间实现(一)—— 展示说说中的评论内容并有相应点击事件

    大家都玩QQ空间客户端,对于每一个说说,我们都可以评论,那么,对于某一条评论: 白雪公主 回复 小矮人 : 你们好啊~ 我们来分析一下: .QQ空间允许我们 点击 回复人和被回复人的名字就可以进入对于 ...

  9. JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果

    JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果 今天是2014年第一篇博客是关于类似于我们的qq空间长图片展示效果,因为一张很长的图片不可能全部把他展示出来,所以外层用了一个容器给他一个高度,超 ...

随机推荐

  1. SQLSEVER 中的那些键和约束

    SQL Server中有五种约束类型,分别是 PRIMARY KEY约束.FOREIGN KEY约束.UNIQUE约束.DEFAULT约束.和CHECK约束.查看或者创建约束都要使用到 Microso ...

  2. hdu 4825(Trie)

    Xor Sum Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Others)Total S ...

  3. BZOJ 1106 [POI2007]立方体大作战tet(树状数组)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1106 [题目大意] 给定玩家一个有2n个元素的栈,元素一个叠一个地放置. 这些元素拥有 ...

  4. [WikiOI "天梯"1281] Xn数列

    题目描述Description 给你6个数,m, a, c, x0, n, g Xn+1 = ( aXn + c ) mod m,求Xn m, a, c, x0, n, g<=10^18 输入描 ...

  5. POJ 2139 Six Degrees of Cowvin Bacon (弗洛伊德最短路)

    题意:奶牛拍电影,如果2个奶牛在同一场电影里演出,她们的合作度是1,如果ab合作,bc合作,ac的合作度为2,问哪一头牛到其他牛的合作度平均值最小再乘100 思路:floyd模板题 #include& ...

  6. Trie 图

    时间限制:20000ms 单点时限:1000ms 内存限制:512MB 描述 前情回顾 上回说到,小Hi和小Ho接受到了河蟹先生伟大而光荣的任务:河蟹先生将要给与他们一篇从互联网上收集来的文章,和一本 ...

  7. Linux通用KVM自动安装Shell脚本(兼容所有Linux发行版/CentOS/Ubuntu)

    官网: https://github.com/retspen/webvirtmgr/wiki/Setup-Host-Server 安装: wget -O - https://retspen.githu ...

  8. 激活Debian 7 的桌面(把图标放在桌面上)

    转:http://www.acyoo.com/archives/3017.html Debian 7的默认桌面是Gnome 3,  刚装好时无法在桌面进行任何操作,不过只需要设置一下就可以了: 左上角 ...

  9. JavaMail入门:创建纯文本、HTML格式的邮件

    转自:http://haolloyin.blog.51cto.com/1177454/353849/ 在 http://java.sun.com/products/javamail/ 下载了 Java ...

  10. 二十四种设计模式:策略模式(Strategy Pattern)

    策略模式(Strategy Pattern) 介绍定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换.本模式使得算法的变化可独立于使用它的客户. 示例有一个Message实体类,对它的操作有 ...