1. 实现效果

效果图中,视频没有铺满 是因为使用了ExoPlayer的RESIZE_MODE_FIT模式, 虽然使用RESIZE_MODE_FILL模式可以填充整个父布局,但是本Demo中使用的视频源本身就不适合全屏,会把视频拉伸,效果不好。 抖音上的视频源应该都有严格的宽高尺寸,才能做到全屏有很好的效果。

2. 技术选型

1)翻页功能:网上有不少例子是使用RecyclerView +  PagerSnapHelper 来实现翻页功能,但是笔者认为使用ViewPager2更加简洁。

2)视频播放:选用ExoPlayer, 谷歌亲儿子ExoPlayer  |  Android 开发者  |  Android Developers

此外,Bilibili公司开源ijkPlayer也比较有名,但是和ExoPlayer相比,ExoPlayer导入项目之后APK体积增加小 ,可以按需导入不同的组件。

整个ExoPlayer框架包括5个组件

  • exoplayer-core:核心功能
  • exoplayer-dash:支持DASH内容
  • exoplayer-hls:支持HLS内容
  • exoplayer-smoothstreaming:支持SmoothStreaming内容
  • exoplayer-ui:用于ExoPlayer的UI组件和相关的资源。

参考:

ExoPlayer简单使用 - 简书

Android中视频播放器的选择,MediaPlayer、ExoPlayer、ijkplayer简单对比_Android格调小窝-CSDN博客_exo硬解和ijk硬解哪个好

3) 视频缓存: 选用github上的一个比较有名的开源框架GitHub - danikula/AndroidVideoCache: Cache support for any video player with help of single line

VideoCache的核心原理:

参考:AndroidVideoCache-视频边播放边缓存的代理策略 - 简书

核心原理描述: VideoCache框架在本地构建了一个代理服务器,把VideoView的网络请求拦截转换为代理服务器进行网络请求,请求回包的数据写入到本地的文件缓存,并且缓存到达一定值时候通知客户端进行读取。

3. 核心实现

1)自定义一个VideoPlayManager.java封装好ExoPlayer的调用

public class VideoPlayManager {private volatile static VideoPlayManager mInstance = null;private Context mContext;private SimpleExoPlayer mSimpleExoPlayer;private VideoPlayTask mCurVideoPlayTask;/*** 双重检测* @return*/public static VideoPlayManager getInstance(Context context) {if (mInstance == null) {synchronized (VideoPlayManager.class) {if(mInstance == null) {mInstance = new VideoPlayManager(context);}}}return mInstance;}public VideoPlayManager(Context context) {this.mContext = context;}/*** 开始播放*/public void startPlay() {stopPlay();if(mCurVideoPlayTask == null) {Log.e("Video_Play_TAG", "start play task is null");return;}//创建带宽对象BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();//根据当前宽带来创建选择磁道工厂对象TrackSelection.Factory videoTrackSelectionFactory =new AdaptiveTrackSelection.Factory(bandwidthMeter);//传入工厂对象,以便创建选择磁道对象TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);LoadControl loadControl = new DefaultLoadControl();mSimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(mContext, trackSelector, loadControl);//设置是否循环播放mSimpleExoPlayer.setRepeatMode(Player.REPEAT_MODE_ONE);//配置数据源DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(mContext,Util.getUserAgent(mContext, "Exo_Video_Play"));DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();//获取代理urlString proxyUrl = getProxy().getProxyUrl(mCurVideoPlayTask.getVideoUrl());Log.d("Video_Play_TAG", "start play orginal url = " + mCurVideoPlayTask.getVideoUrl() + " , proxy url = " + proxyUrl);Uri proxyUri = Uri.parse(proxyUrl);//配置数据源MediaSource mediaSource = new ExtractorMediaSource(proxyUri, mediaDataSourceFactory, extractorsFactory, null, null);mSimpleExoPlayer.prepare(mediaSource);//隐藏播放工具mCurVideoPlayTask.getSimpleExoPlayerView().setUseController(false);//设置播放视频的宽高为Fit模式mCurVideoPlayTask.getSimpleExoPlayerView().setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);//绑定player和playerViewmCurVideoPlayTask.getSimpleExoPlayerView().setPlayer(mSimpleExoPlayer);mSimpleExoPlayer.setPlayWhenReady(true);}/*** 停止播放*/public void stopPlay() {if(mSimpleExoPlayer != null) {mSimpleExoPlayer.release();mSimpleExoPlayer = null;}}public void resumePlay() {if(mSimpleExoPlayer != null) {mSimpleExoPlayer.setPlayWhenReady(true);} else {startPlay();}}public void pausePlay() {if(mSimpleExoPlayer != null) {mSimpleExoPlayer.setPlayWhenReady(false);}}/********************************************* VideoCache start ***************************************/private HttpProxyCacheServer mHttpProxyCacheServer;public HttpProxyCacheServer getProxy() {if(mHttpProxyCacheServer == null) {mHttpProxyCacheServer = newProxy();}return mHttpProxyCacheServer;}private HttpProxyCacheServer newProxy() {//缓存大小512M,缓存文件20return new HttpProxyCacheServer.Builder(mContext.getApplicationContext()).maxCacheSize(512 * 1024 * 1024).maxCacheFilesCount(20).fileNameGenerator(new VideoFileNameGenerator()).cacheDirectory(new File(mContext.getFilesDir() + "/videoCache/")).build();}/********************************************* VideoCache end ***************************************/public VideoPlayTask getCurVideoPlayTask() {return mCurVideoPlayTask;}public void setCurVideoPlayTask(VideoPlayTask mCurVideoPlayTask) {this.mCurVideoPlayTask = mCurVideoPlayTask;}/*** 构建测试数据* @return*/public static List<String> buildTestVideoUrls() {List<String> urls = new ArrayList<>();urls.add("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");urls.add("https://vfx.mtime.cn/Video/2019/01/15/mp4/190115161611510728_480.mp4");urls.add("http://gslb.miaopai.com/stream/oxX3t3Vm5XPHKUeTS-zbXA__.mp4");urls.add("http://vjs.zencdn.net/v/oceans.mp4 ");return urls;}
}

2) 实现ViewPager2的adapter: VideoViewPagerAdapter.java

public class VideoViewPagerAdapter extends RecyclerView.Adapter<VideoViewPagerAdapter.VideoViewHolder> {private Context mContext;private List<String> mVieoUrls = new ArrayList<>();public VideoViewPagerAdapter(Context context) {super();this.mContext = context;}public void setDataList(List<String> videoUrls) {mVieoUrls.clear();mVieoUrls.addAll(videoUrls);notifyDataSetChanged();Log.d("Video_Play_TAG", "setDataList" );}public void addDataList(List<String> videoUrls) {mVieoUrls.addAll(videoUrls);notifyDataSetChanged();}@NonNull@Overridepublic VideoViewPagerAdapter.VideoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View itemView = LayoutInflater.from(mContext).inflate(R.layout.fragment_video_item, parent, false);return new VideoViewHolder(itemView);}@Overridepublic void onBindViewHolder(@NonNull VideoViewPagerAdapter.VideoViewHolder holder, int position) {holder.videoUrl = mVieoUrls.get(position);holder.itemView.setTag(position);Log.d("Video_Play_TAG", " on bind view holder pos = "+ position + " , url = " + holder.videoUrl);}@Overridepublic int getItemCount() {return mVieoUrls.size();}public class VideoViewHolder extends RecyclerView.ViewHolder {public SimpleExoPlayerView mVideoView;public String videoUrl;VideoViewHolder(View itemView) {super(itemView);mVideoView = itemView.findViewById(R.id.video_view);}}public String getUrlByPos(int pos) {return mVieoUrls.get(pos);}
}

3) 最后在fragment里调用ViewPager2

public class MediaFragment extends Fragment {private ViewPager2 mViewPager2;private VideoViewPagerAdapter mVideoViewPagerAdapter;private boolean onFragmentResume;private boolean onFragmentVisible;public static MediaFragment build() {return new MediaFragment();}@Overridepublic View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_media, null, true);initUI(rootView);return rootView;}private void initUI(View rootView) {mViewPager2 = rootView.findViewById(R.id.viewpager2);mVideoViewPagerAdapter = new VideoViewPagerAdapter(getActivity());mVideoViewPagerAdapter.setDataList(VideoPlayManager.buildTestVideoUrls());mViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);mViewPager2.setAdapter(mVideoViewPagerAdapter);mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {super.onPageScrolled(position, positionOffset, positionOffsetPixels);}@Overridepublic void onPageSelected(int position) {super.onPageSelected(position);Log.d("Video_Play_TAG", " on page selected = " + position);View itemView = mViewPager2.findViewWithTag(position);SimpleExoPlayerView simpleExoPlayerView = itemView.findViewById(R.id.video_view);VideoPlayManager.getInstance(AppUtil.getApplicationContext()).setCurVideoPlayTask(new VideoPlayTask(simpleExoPlayerView,mVideoViewPagerAdapter.getUrlByPos(position)));if(onFragmentResume && onFragmentVisible) {VideoPlayManager.getInstance(AppUtil.getApplicationContext()).startPlay();}}@Overridepublic void onPageScrollStateChanged(int state) {super.onPageScrollStateChanged(state);}});}@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);if(isVisibleToUser) {onFragmentVisible = true;VideoPlayManager.getInstance(AppUtil.getApplicationContext()).resumePlay();Log.d("Video_Play_TAG", " video fragment可见");}else {onFragmentVisible = false;VideoPlayManager.getInstance(AppUtil.getApplicationContext()).pausePlay();Log.d("Video_Play_TAG", " video fragment不可见 ");}}@Overridepublic void onResume() {super.onResume();onFragmentResume = true;if(onFragmentVisible) {VideoPlayManager.getInstance(AppUtil.getApplicationContext()).resumePlay();}Log.d("Video_Play_TAG", " video fragment Resume ");}@Overridepublic void onPause() {super.onPause();onFragmentResume = false;VideoPlayManager.getInstance(AppUtil.getApplicationContext()).pausePlay();Log.d("Video_Play_TAG", " video fragment Pause ");}
}

4. 后续todo工作:视频的预加载实现

5.参考链接

ExoPlayer简单使用 - 简书

Android中视频播放器的选择,MediaPlayer、ExoPlayer、ijkplayer简单对比_Android格调小窝-CSDN博客_exo硬解和ijk硬解哪个好

AndroidVideoCache-视频边播放边缓存的代理策略 - 简书

6. Demo地址

GitHub - mikelhm/MikelProjectDemo: Personal Android Demo

  1. MVVM: ViewModel+LiveData+DataBinding+Retrofit+Room+Paging+RxJava 总结与实践(Java实现)
    MVVM: ViewModel+LiveData+DataBinding+Retrofit+Room+Paging+RxJava 总结与实践(Java实现)_xiaobaaidaba123的专栏-CSDN博客
  2. android 嵌套ViewPager + Fragment实现仿头条UI框架Demo
    android 嵌套ViewPager + Fragment实现仿头条UI框架Demo_xiaobaaidaba123的专栏-CSDN博客
  3. Android 使用ViewPager2+ExoPlayer+VideoCache 实现仿抖音视频翻页播放
    https://blog.csdn.net/xiaobaaidaba123/article/details/120630087

Android 使用ViewPager2+ExoPlayer+VideoCache 实现仿抖音视频翻页播放相关推荐

  1. html5仿抖音全屏播放,仿抖音视频全屏播放滑动切换

    1 前言 随着移动技术的快速迭代,数据流量费用的快速下降,视频.直播正成为全民的媒体盛宴,我司必然也不会缺席此次盛宴,这里讲述的是通过h5实现仿抖音视频全屏播放&滑动切换的效果,供我司直播鉴定 ...

  2. 仿抖音视频详情页点赞红心动效

    GitHub地址: https://github.com/selfconzrr/LikeAnimator 可直接测试运行 效果预览 核心思路: 自定义 View 继承自 RelativeLayout ...

  3. 【Android 进阶】仿抖音系列之列表播放视频(二)

    上一篇中,我们实现了仿抖音上下翻页切换视频的效果,详见[Android 进阶]仿抖音系列之翻页上下滑切换视频(一),这一篇,我们来实现抖音列表播放视频. [Android 进阶]仿抖音系列之翻页上下滑 ...

  4. 【Android 进阶】仿抖音系列之列表播放视频(三)

    在上一篇[Android 进阶]仿抖音系列之列表播放视频(二)中,我们实现列表播放视频,这一篇我们来对其做些优化. [Android 进阶]仿抖音系列之翻页上下滑切换视频(一) [Android 进阶 ...

  5. vue仿抖音视频列表(兼容微信内置X5浏览器)

    vue 仿抖音视频列表(兼容微信内置X5浏览器)https://blog.csdn.net/superKM/article/details/87603255制作 仿抖音视频列表遇到很多坑,特别是安卓微 ...

  6. html5仿抖音切换效果,仿抖音视频滑动效果

    更新记录 1.6.2(2020-06-04) 优化css3动画效果 1.6.1(2020-05-23) 1.修复串音 2.新增进度条 3.新增弹幕 查看更多 scroll-video uniapp仿抖 ...

  7. 仿抖音视频自动播放html,vue 仿抖音视频列表(兼容微信内置X5浏览器)

    制作 仿抖音视频列表遇到很多坑,特别是安卓微信内置浏览器,让人脑壳疼,核心代码不多 便于理解 组件用到了vant 中的swiper滑动组件 h5 原生 video 属性 webkit-playsinl ...

  8. 微信小程序仿抖音视频

    微信小程序仿抖音视频 使用轮播图实现视频滑动效果. wxml 部分 <view class="video-contain"><!-- 自定义头部 -->&l ...

  9. 【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)

    需要全部代码请点赞关注收藏后评论区留言私信~~~ 与传统的影视行业相比,诞生于移动互联网时代的短视频是个全新行业,它制作方便又容易传播,一出现就成为大街小巷的时髦潮流. 各行各业的人们均可通过短视频展 ...

最新文章

  1. rhel6用centos163 yum源
  2. 多版本php共存 linux,linux下多版本php共存的原理、方法
  3. HDU-4278 Faulty Odometer 数学递推 || 八进制
  4. TCP三次握手和四次挥手过程
  5. 彻底搞懂阻塞、非阻塞、同步、异步
  6. Qt4_在表中显示数据
  7. 希望博客园可以开个邮件列表
  8. 人工智能 深度学习(Deep learning)开源框架
  9. 噪声的频谱分析的重要意义_一文带你了解频谱仪和示波器究竟有何区别(涨知识了)...
  10. 13、TCP Socket与UDP Socket
  11. 【单片机仿真】(四)寻址方式 — 寄存器寻址与直接寻址
  12. 从Oppo手机拍照无法展示谈图片压缩
  13. 画图工具轻松打印长图
  14. 他一年写了200篇原创笔记,帮助你快速入门Python与机器学习
  15. 对图像高通滤波matlab,高通巴特沃斯滤波器在MATLAB中对图像进行滤波
  16. 初识中间件Kafka
  17. 小学计算机教师集体备课计实,小学科学集体备课记录(年.doc
  18. 一米OA任意文件读取漏洞
  19. 计算机中丢失krpt怎么办,计算机中丢失krpt。dll怎么办
  20. 300万微信公众号迎来广告时代

热门文章

  1. 106个免费英文SEO工具,带你飞!
  2. 织梦dedecms模板文件在哪
  3. CAD如何使用多段线命令?
  4. 青灯教育 python录播课_9款线上少儿编程课测评合集:录播课、直播课,最后2家的老师难选到飙泪!...
  5. 马尔科夫链-转移概率
  6. 【LabVIEW懒人系列教程-小白入门】1.24LabVIEW文件IO之txt文本...
  7. python从入门到精通视频,python快速入门精讲
  8. 坚定看好核电设备产业
  9. ibmt60桌面怎么没有计算机,ibm t60
  10. 深度学习(五) 生成对抗网络入门与实践