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

这篇具有很好参考价值的文章主要介绍了Android 使用ViewPager2+ExoPlayer+VideoCache 实现仿抖音视频翻页播放。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 实现效果

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

   效果图中,视频没有铺满 是因为使用了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的核心原理:

Android 使用ViewPager2+ExoPlayer+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();

        //获取代理url
        String 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和playerView
        mCurVideoPlayTask.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,缓存文件20
        return 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
    @Override
    public 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);
    }

    @Override
    public 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);
    }

    @Override
    public 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();
    }

    @Override
    public 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() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
            }

            @Override
            public 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();
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
            }
        });
    }

    @Override
    public 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不可见 ");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        onFragmentResume = true;
        if(onFragmentVisible) {
            VideoPlayManager.getInstance(AppUtil.getApplicationContext()).resumePlay();
        }
        Log.d("Video_Play_TAG", " video fragment Resume ");
    }

    @Override
    public 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文章来源地址https://www.toymoban.com/news/detail-408005.html

  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 实现仿抖音视频翻页播放的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android进阶之路 - ViewPager2 比 ViewPager 强在哪?

    我记得前年(2022)面试的时候有被问到 ViewPager 和 ViewPager2 有什么区别?当时因为之前工作一直在开发售货机相关的项目,使用的技术要求并不高,所以一直没去了解过 ViewPager2~ 去年的时候正好有相关的功能需求,索性直接用 ViewPager2 进行了 Tip :很多人可能比较关注俩者区

    2024年02月20日
    浏览(51)
  • 【Android基础面试题】ViewPager与ViewPager2的区别

    ViewPager和ViewPager2是Android中用于实现滑动页面切换的控件。它们的主要区别如下: 实现方式 ViewPager2的内部实现是RecyclerView,而ViewPager是通过继承自ViewGroup实现的。因此,ViewPager2的性能更高。 滑动方向 ViewPager2可以实现横向和竖向滑动,而ViewPager只能横向滑动。 Adapter:View

    2024年02月11日
    浏览(45)
  • Android ViewPager2 + Fragment 联动

    本篇主要介绍一下 ViewPager2 + Fragment , 上篇中简单使用了ViewPager2 实现了一个图片的滑动效果, 那图片视图可以滑动, ViewPager2也可以滑动 Fragment 概述 ViewPager2 官方对它的描述就是 以可滑动的格式显示视图或 Fragment 也就说明提供了滑动Fragment的实现 并且还很简单, 下面来看看吧

    2023年04月08日
    浏览(44)
  • ViewPager2与TabLayout的简单使用

    ViewPager2与TabLayout的简单使用 MainActivity.java activity_main.xml ViewPagerAdapter.java ShowBigIdBean.java item_img.xml item_icon_layout.xml item_circle_shape.xml

    2024年02月12日
    浏览(38)
  • ViewPager2+TabLayout

    ViewPager2最显著的特点是基于RecyclerView实现,RecyclerView是目前Android端最成熟的AdapterView解决方案,这带来诸多好处: 1、抛弃传统的PagerAdapter,统一了Adapter的API/ 2、通过LinearLayoutManager可以实现类似抖音的纵向滑动 3、支持DiffUitl,可以通过diff实现局部刷新 4、支持RTL(right-to-

    2023年04月19日
    浏览(40)
  • android : 底部导航栏的实现(使用ViewPager和BottomNavigationView)

      本案例中需要用的控件ViewPager和BottomNavigationView ViewPager:主要是页面的切换 Fragment:碎片(也就是每个页面的内容) BottomNavigationView:底部导航栏 非常简单,主要就是一个Viewpager和BottomNavigationView 先来说一下思路:BottomNavigationView底部导航栏   ViewPager+Fragment页面        

    2024年02月03日
    浏览(41)
  • Android studio中使用ViewPager和BottomNavigationView实现底部导航栏和碎片的同步切换

    通过几次的踩雷和摸索,完成了以上的操作,本教程写的详细全面,包教包会,对新手有好,看了不会的联系我,我倒立洗头给你看。 所需控件: fragment 作为Android中最常用的控件,它有自己的声明周期,可以粗略地等比为能够分屏的activity,但是和activity有区别,fragment有自

    2024年02月08日
    浏览(48)
  • Android 之 ViewPager 的简单使用

    本节带来的是Android 3.0后引入的一个UI控件——ViewPager(视图滑动切换工具),实在想不到 如何来称呼这个控件,他的大概功能:通过手势滑动可以完成View的切换,一般是用来做APP 的引导页或者实现图片轮播,因为是3.0后引入的,如果想在低版本下使用,就需要引入v4 兼容包哦

    2024年02月06日
    浏览(49)
  • Android——禁止ViewPager的左右滑动功能实现

    Android——禁止ViewPager的左右滑动功能实现 在Android开发中,ViewPager是一种常用的滑动控件,用于实现页面的左右切换效果。然而,在某些场景中,我们可能需要禁止ViewPager的左右滑动功能,只允许通过其他方式进行页面切换。本文将介绍如何在Android中实现禁止ViewPager左右滑动

    2024年02月06日
    浏览(46)
  • 『Android基础入门』ViewPager+Fragment+BottomNavigationView实现底部导航

    👨‍🎓作者简介:一位喜欢写作,计科专业大三菜鸟 🏡个人主页:starry陆离 如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦 在ViewPager与Fragment结合实现多页面滑动的学习上再进一步,记录一下ViewPager+Fragment+BottomNavigationView实现底部导航 1.复习ViewPager的用法 2.复习F

    2023年04月08日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包