Android 列表视频滑动自动播放—滑动过程自动播放(实现思路)

embedded/2024/9/23 3:19:16/

本文基于Exoplayer +  PlayerView 实现列表视频显示一定比例后自动播放

首先引入google media3包

implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-exoplayer-dash:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.1'
implementation "androidx.media3:media3-common:1.1.1"

列表自动播放,我们需要监听recyclerView.addOnScrollListener(this),并实现对应方法

onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) 中判断滑动过程中,展示视频itemVideoView比例

1、首先我们需要拿到recyclerView显示出来的itemPosition

int firstItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
int lastItemPosition = linearLayoutManager.findLastVisibleItemPosition();

2、当判断第一个(firstItemPosition)和最后一个(lastItemPosition)都大于0时候,开始遍历recyclerView数据,判断显示视频itemVideoView显示比例(因为我项目,视频播放数据是二维数组。RecyclerView1+RecyclerView2结构,所以视频播放时在里层RecyclerView2,如果不是二维,那么就RecyclerView2其实就是视频播放容器,dataItem.getVideoPosition()其实就是告诉我们RecyclerView2中第几条数据播放视频,默认是-1,表示没有视频,需要我们在网络请求后,遍历数据生成对应可以展示视频position)

 public void recyclerViewScrollVideo() {if (recyclerView == null || linearLayoutManager == null || communityPostListAdapter == null) {return;}int firstItemPosition = linearLayoutManager.findFirstVisibleItemPosition();int lastItemPosition = linearLayoutManager.findLastVisibleItemPosition();if (firstItemPosition >= 0 && lastItemPosition >= 0) {DataItem dataItem = communityPostListAdapter.getData(firstItemPosition);CommonViewHolder viewHolder = (CommonViewHolder) recyclerView.findViewHolderForAdapterPosition(firstItemPosition);if (viewHolder != null) {//当第一个显示数据,不支持播放视频时候,需要直接遍历后面,看是否有支持视频的//获取里层 RecyclerView ImageRecyclerView recyclerImage = viewHolder.getView(R.id.communal_post_header_recycler_image);if (dataItem != null && dataItem.getVideoPosition() >= 0 && recyclerImage != null) {//获取里层视频那个itemView positionint videoPosition = dataItem.getVideoPosition();CommonViewHolder itemViewHolder = (CommonViewHolder) recyclerImage.findViewHolderForAdapterPosition(videoPosition);//获取当前视频itemBenVideoAdapter videoAdapter = (CommunalImageAdapter) recyclerImage.getAdapter();//获取视频数据对象VideoDataBean videoDataBean = null;if (videoDataBean != null) {videoDataBean = videoAdapter.getData(videoPosition);}boolean isPlaying = ExoPlayerManager.getInstance().recyclerViewScrollVideo(recyclerImage, itemViewHolder, firstItemPosition, videoDataBean);if (!isPlaying) {//firstItemPosition不支持播放,则遍历RecyclerView数据判断下一个是否支持播放int newFirstItemPosition = firstItemPosition + 1;traversal(recyclerView, newFirstItemPosition, lastItemPosition);}} else {//有可能是多布局,则第一条数据,找不到视频播放view,那么我们需要遍历数据,从第二条数据开始找int newFirstItemPosition = firstItemPosition + 1;traversal(recyclerView, newFirstItemPosition, lastItemPosition);}}}}

3、当第一条不满足,开始遍历

 /*** 遍历寻找,屏幕显示可以播放视频的item** @param recycler* @param newFirstItemPosition* @param lastItemPosition*/private void traversal(RecyclerView recycler, int newFirstItemPosition, int lastItemPosition) {//标记是否找到视频-1,表示未找到视频int playPosition = -1;for (int i = newFirstItemPosition; i <= lastItemPosition; i++) {DataItem dataItem = communityPostListAdapter.getData(i);CommonViewHolder viewHolder = (CommonViewHolder) recycler.findViewHolderForAdapterPosition(i);if (viewHolder != null && dataItem != null && dataItem.getVideoPosition() >= 0) {RecyclerView recyclerImage = viewHolder.getView(R.id.communal_post_header_recycler_image);if (recyclerImage != null) {CommonViewHolder itemViewHolder = (CommonViewHolder) recyclerImage.findViewHolderForAdapterPosition(dataItem.getVideoPosition());//获取当前视频itemBenVideoAdapter videoAdapter = (CommunalImageAdapter) recyclerImage.getAdapter();VideoDataBean videoDataBean = null;if (videoAdapter != null) {videoDataBean = videoAdapter.getData(dataItem.getVideoPosition());}if (ExoPlayerManager.getInstance().recyclerViewScrollVideo(recyclerImage, itemViewHolder, i, communalImageBean)) {playPosition = i;break;}}}}if (playPosition == -1) {//都没有找到视频ExoPlayerManager.getInstance().stopVideo();}}

4、视频播放器单例(Exoplayer +  PlayerView)


public class ExoPlayerManager {private static volatile ExoPlayerManager inStance = null;ExoPlayer mExoPlayer;public ExoPlayerListener mExoPlayerListener;private UserPlayerView mUserPlayerView;//视频容器private FrameLayout mLayout;//loading viewprivate ProgressBar mLoading;//视频logoprivate ImageView mLogo;//点击播放按钮private ImageView mPlay;//计算RecyclerView显示位置private Rect mItemRect;//RecyclerView显示高度float visibleHeight;//RecyclerView内容高度float totalHeight;//RecyclerView 显示高度和高度比例float visibleRatio;int mItemPlayPosition;//视频播放数据对象VideoDataBean mVideoDataBean;//视频最先显示比例private float minRatio;/*** 监听*/private AnalyticsListener mAnalyticsListener = new AnalyticsListener() {@Overridepublic void onPlaybackStateChanged(EventTime eventTime, int state) {AnalyticsListener.super.onPlaybackStateChanged(eventTime, state);switch (state) {case Player.STATE_IDLE:  //未加载到资源,停止播放,包含无网也会执行if (mExoPlayerListener != null) {mExoPlayerListener.onVideoPlayIdle();} else {stopShowView();}showController();break;case Player.STATE_BUFFERING: //缓冲中if (mExoPlayerListener != null) {mExoPlayerListener.onVideoPlayBuffering(eventTime != null ? eventTime.currentPlaybackPositionMs : -1L);} else {loadingShowView();}break;case Player.STATE_READY:  //开始播放if (mExoPlayerListener != null) {mExoPlayerListener.onVideoPlay(eventTime != null ? eventTime.currentPlaybackPositionMs : -1L);} else {startShowView();}break;case Player.STATE_ENDED:  //播放结束,当循环播放时候,改方法不会被触发  onPositionDiscontinuity  DISCONTINUITY_REASON_AUTO_TRANSITIONif (mExoPlayerListener != null) {mExoPlayerListener.onVideoPlayerCompletion();}break;}}@Overridepublic void onPositionDiscontinuity(EventTime eventTime, Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {AnalyticsListener.super.onPositionDiscontinuity(eventTime, oldPosition, newPosition, reason);if (mExoPlayerListener != null) {mExoPlayerListener.onVideoPlayerCompletion();} else {//循环播放了,显示时间if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {showController();}}}@Overridepublic void onPlayerError(EventTime eventTime, PlaybackException error) {AnalyticsListener.super.onPlayerError(eventTime, error);if (mExoPlayerListener != null) {mExoPlayerListener.onVideoPlayerError(eventTime != null ? eventTime.currentPlaybackPositionMs : -1l);} else {Toast.makeText(CommonParam.getInstance().getApplication(), "网络异常", Toast.LENGTH_LONG).show();if (eventTime != null) {long currentPlaybackPositionMs = eventTime.currentPlaybackPositionMs;if (mVideoDataBean != null && currentPlaybackPositionMs > 0) {mVideoDataBean.setSeekToPositionMs(currentPlaybackPositionMs);}}//网络异常会执行--onPlayerError---onPlaybackStateChanged(1)----onIsPlayingChanged()stopShowView();}}};public static ExoPlayerManager getInstance() {if (inStance == null) {synchronized (ExoPlayerManager.class) {if (inStance == null) {inStance = new ExoPlayerManager();}}}return inStance;}private ExoPlayerManager() {init();}private void init() {//初始化exoPlayerif (mExoPlayer == null) {mExoPlayer = new ExoPlayer.Builder(CommonParam.getInstance().getApplication()).build();mExoPlayer.addAnalyticsListener(mAnalyticsListener);}// 设置重复模式// Player.REPEAT_MODE_ALL 无限重复// Player.REPEAT_MODE_ONE 重复一次// Player.REPEAT_MODE_OFF 不重复mExoPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);setVolume(CommonParam.getInstance().isVideoVolumeChange());if (mItemRect == null) {mItemRect = new Rect();}if (mUserPlayerView == null) {mUserPlayerView = new UserPlayerView(CommonParam.getInstance().getApplication());ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);mUserPlayerView.setLayoutParams(layoutParams);}//设置视频填充模式  RESIZE_MODE_FILL 拉伸视频达到没有黑边效果,RESIZE_MODE_ZOOM:这个是居中填充效果mUserPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH);minRatio = 9f / 16f;}/*** 获取视频View** @return*/public UserPlayerView getUserPlayerView() {return mUserPlayerView;}/*** 添加监听*/public void addAnalyticsListener() {if (mExoPlayer != null) {mExoPlayer.removeAnalyticsListener(mAnalyticsListener);mExoPlayer.addAnalyticsListener(mAnalyticsListener);}}/*** 移除监听*/public void removeAnalyticsListener() {if (mExoPlayer != null) {mExoPlayer.removeAnalyticsListener(mAnalyticsListener);}}/*** 视频显示尺寸修改** @param params* @param playerView* @param maxWidth   屏幕显示最大宽* @param width      视频真实宽* @param height     视频真实高*/public void disposeVideoSize(ViewGroup.LayoutParams params, View playerView, int maxWidth, float width, float height) {if (params != null && playerView != null) {if (width > 0 && height > 0) {if (height >= width) {int showWidth = (int) (maxWidth > 0 ? maxWidth : width);params.width = showWidth;params.height = showWidth;} else {params.width = maxWidth;float ratio = height / width;params.height = (int) ((ratio >= minRatio) ? ratio * maxWidth : minRatio * maxWidth);}} else if (maxWidth > 0) {params.width = maxWidth;params.height = maxWidth;} else {params.width = ViewGroup.LayoutParams.MATCH_PARENT;params.height = ViewGroup.LayoutParams.MATCH_PARENT;}playerView.setLayoutParams(params);}}/*** 获取当前列表布局控件,一会计算时候需要用** @param recyclerImage* @param itemViewHolder* @param communalImageBean*/public boolean recyclerViewScrollVideo(RecyclerView recyclerImage, CommonViewHolder itemViewHolder, int itemPlayPosition, CommunalImageBean communalImageBean) {if (recyclerImage != null && itemViewHolder != null && communalImageBean != null) {FrameLayout frameLayout = itemViewHolder.getView(R.id.video);ImageView logo = itemViewHolder.getView(R.id.logo);ImageView play = itemViewHolder.getView(R.id.play);ProgressBar loading = itemViewHolder.getView(R.id.loading);ImageView volume = itemViewHolder.getView(R.id.volume);setVolumeViewChange(volume);setVolume(CommonParam.getInstance().isVideoVolumeChange());return calculateVideoPlay(recyclerImage, frameLayout, play, logo, loading, itemPlayPosition, communalImageBean);}return false;}/*** 计算是否滑动自动播放视频** @param view* @param frameLayout* @param play* @param logo* @param loading* @param videoDataBean*/public boolean calculateVideoPlay(View view, FrameLayout frameLayout, ImageView play, ImageView logo, ProgressBar loading, int itemPlayPosition,VideoDataBean videoDataBean ) {view.getLocalVisibleRect(mItemRect);visibleHeight = mItemRect.bottom - mItemRect.top;totalHeight = view.getHeight();visibleRatio = visibleHeight / totalHeight;if (mItemRect.top >= 0 && visibleRatio > BaseConstantValue.VIDEO_START_VISIBLE_RATIO) {playVideo(frameLayout, play, logo, loading, itemPlayPosition, videoDataBean);return true;} else {//不满足播放if (play != null) {play.setVisibility(View.VISIBLE);}if (loading != null) {loading.setVisibility(View.GONE);}if (logo != null) {logo.setVisibility(View.VISIBLE);}if (frameLayout != null) frameLayout.removeAllViews();}return false;}/*** 满足播放视频,需要再判断,是不是已经还在播放当前视频** @param layout* @param play* @param logo* @param loading* @param itemPlayPosition* @param videoDataBean*/public void playVideo(FrameLayout layout, ImageView play, ImageView logo, ProgressBar loading, int itemPlayPosition, VideoDataBean videoDataBean) {if (mItemPlayPosition != itemPlayPosition) {//不满足当前播放那么需要停止播放,做部分资源保存if (videoDataBean != null && mExoPlayer.isPlaying()) {//视频已经播放位置videoDataBean.setSeekToPositionMs(mExoPlayer.getContentPosition());}//显示暂停不播布局stopShowView();//暂停播放stopVideo();}//重新复制相关控件mLayout = layout;mPlay = play;mLogo = logo;mLoading = loading;//获取当前播放状态,如果未播放,则直接设置资源播放,如果已经播放,则不做处理if (!mExoPlayer.isPlaying()) {//设置播放布局loadingShowView();//移除播放Viewif (mUserPlayerView != null) {ViewParent parent = mUserPlayerView.getParent();if (parent != null && parent instanceof FrameLayout) {((FrameLayout) parent).removeAllViews();}}//将PlayerView 添加 FrameLayout上if (layout != null) {layout.removeAllViews();layout.addView(mUserPlayerView);}if (videoDataBean != null) {String videoUrl = videoDataBean.getVideoUrl();long seekToPositionMs = videoDataBean.getSeekToPositionMs();long durationMs = videoDataBean.getDurationMs();mUserPlayerView.setVideoPosition(seekToPositionMs, durationMs);MediaItem mediaItem = MediaItem.fromUri(videoUrl != null ? videoUrl : "");mExoPlayer.setMediaItem(mediaItem, seekToPositionMs);}mUserPlayerView.setPlayer(mExoPlayer);mExoPlayer.prepare();
//            mExoPlayer.play();mExoPlayer.setPlayWhenReady(true);}mItemPlayPosition = itemPlayPosition;mVideoDataBean = videoDataBean;}public ExoPlayer exoPlayer() {return mExoPlayer;}/*** 是否正在播放** @return*/public boolean isPlaying() {return mExoPlayer != null ? mExoPlayer.isPlaying() : false;}/*** 获取当前播放进度** @return*/public long getCurrentPositionMs() {if (mExoPlayer != null && mExoPlayer.isPlaying()) {return mExoPlayer.getCurrentPosition();}return -1;}/*** 获取总时长** @return*/public long getDurationMs() {if (mExoPlayer != null) {return mExoPlayer.getDuration();}return 0l;}/*** 设置进度** @param seekToCurrentPositionMs*/public void seekTo(long seekToCurrentPositionMs) {if (mExoPlayer != null) {mExoPlayer.seekTo(seekToCurrentPositionMs);if (!isPlaying()) {mExoPlayer.play();}}}/*** 设置是否有视频控制器** @param useController*/private void setUseController(boolean useController) {mUserPlayerView.setUseController(useController);}/*** 绑定全屏播放** @param videoUrl* @param currentPositionMs* @return*/private UserPlayerView bindVideoFullScreen(String videoUrl, long currentPositionMs) {mItemPlayPosition = -1;mExoPlayer.setMediaItem(MediaItem.fromUri(videoUrl == null ? "" : videoUrl), currentPositionMs);mUserPlayerView.setPlayer(mExoPlayer);mExoPlayer.prepare();mVideoDataBean = null;return mUserPlayerView;}/*** 开始播放*/public void startVideo() {if (mExoPlayer != null && !mExoPlayer.isPlaying()) {mExoPlayer.play();}}/*** 开始播放*/public void startVideo(long currentPositionMs) {if (mExoPlayer != null && !mExoPlayer.isPlaying()) {mExoPlayer.seekTo(currentPositionMs);mExoPlayer.play();}}/*** 开始播放*/public void startVideo(String videoUrl, long currentPositionMs) {if (mExoPlayer != null) {if (!mExoPlayer.isPlaying()) {mExoPlayer.stop();}mExoPlayer.setMediaItem(MediaItem.fromUri(videoUrl == null ? "" : videoUrl), currentPositionMs);mUserPlayerView.setPlayer(mExoPlayer);mExoPlayer.prepare();mExoPlayer.play();}}/*** 暂停播放*/public void pauseVideo() {if (mExoPlayer != null && mExoPlayer.isPlaying()) {mExoPlayer.pause();}}/*** 停止播放*/public void stopVideo() {mItemPlayPosition = -1;if (mExoPlayer != null && mExoPlayer.isPlaying()) {if (mVideoDataBean != null) {mVideoDataBean.setSeekToPositionMs(mExoPlayer.getContentPosition());}}mExoPlayer.stop();releaseView();}/*** 页面关闭,释放资源*/public void releaseVideo() {if (mExoPlayer != null) {mExoPlayer.removeAnalyticsListener(mAnalyticsListener);mExoPlayer.release();mExoPlayer = null;}releaseView();}private void releaseView() {if (mUserPlayerView != null) {ViewParent parent = mUserPlayerView.getParent();if (parent != null && parent instanceof FrameLayout) {((FrameLayout) parent).removeAllViews();}}//将PlayerView 添加 FrameLayout上if (mLayout != null) {mLayout.removeAllViews();mLayout = null;}if (mPlay != null) {mPlay = null;}if (mLogo != null) {mLogo = null;}if (mLoading != null) {mLoading = null;}mVideoDataBean = null;}/*** 设置播放器是否开启声音** @param volumeChange*/public void setVolume(boolean volumeChange) {if (volumeChange) {//设置声音if (mExoPlayer.getVolume() == 0f) {AudioManager audioManager = (AudioManager) CommonParam.getInstance().getApplication().getSystemService(Context.AUDIO_SERVICE);float streamVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);float streamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);float volume = (streamMaxVolume != 0 && streamVolume != 0) ? streamVolume / streamMaxVolume : 0.3f;mExoPlayer.setVolume(volume);}} else {//设置声音为0mExoPlayer.setVolume(0f);}CommonParam.getInstance().setVideoVolumeChange(volumeChange);}/*** 设置 音量开关状态** @param volumeView*/public void setVolumeViewChange(View volumeView) {if (volumeView != null) {volumeView.setSelected(CommonParam.getInstance().isVideoVolumeChange());}}/*** 视频正在播放,设置相关布局show or hide*/public void startShowView() {if (mLoading != null) {mLoading.setVisibility(View.GONE);}if (mLogo != null) {mLogo.setVisibility(View.GONE);}if (mPlay != null) {mPlay.setVisibility(View.GONE);}}/*** 视频正在加载中,设置相关布局show or hide*/public void loadingShowView() {if (mLoading != null) {mLoading.setVisibility(View.VISIBLE);}if (mLogo != null) {mLogo.setVisibility(View.VISIBLE);}if (mPlay != null) {mPlay.setVisibility(View.GONE);}}/*** 视频停止播放,设置相关布局show or hide*/public void stopShowView() {if (mLoading != null) {mLoading.setVisibility(View.GONE);}if (mLogo != null) {mLogo.setVisibility(View.VISIBLE);}if (mPlay != null) {mPlay.setVisibility(View.VISIBLE);}}/*** 显示视频控制器*/public void showController() {if (mUserPlayerView != null) {UserPlayerControlView controller = mUserPlayerView.getController();if (controller != null) {controller.show();}}}/*** 设置监听** @param exoPlayerListener*/public void setExoPlayerListener(ExoPlayerListener exoPlayerListener) {mExoPlayerListener = exoPlayerListener;}
}

5、建议我们在开发视频时自定义PlayerView,只需要把源码中,这三个文件拷贝一份,然后自己根据具体需求,实现对应功能。

①、PlayerView

②、PlayerControlView

③、PlayerControlViewLayoutManager

6、如果项目是appBarLayout+RecyclerView的话。我们需要对appBarLayout滑动事件进行监听,从而实现appBarLayout滑动,也能播放视频

  mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.BaseOnOffsetChangedListener() {@Overridepublic void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {//appBarLayout 滑动监听int newOffset = Math.abs(verticalOffset);if (mVerticalOffset != newOffset && adapter != null) {Fragment fragment = adapter .getFragment(viewPagerShowType);if (fragment != null && fragment.isOnResume()) {fragment.startVideo();}mVerticalOffset = newOffset;}}});


http://www.ppmy.cn/embedded/58261.html

相关文章

连接与隔离:Facebook在全球化背景下的影响力

在当今全球化的背景下&#xff0c;Facebook作为全球最大的社交网络平台&#xff0c;不仅连接了世界各地的人们&#xff0c;还在全球社会、经济和文化中发挥着深远的影响。本文将深入探讨Facebook在全球化进程中的作用&#xff0c;以及其对个体和社会之间连接与隔离的双重影响。…

如何在uniapp中使用websocket?

websocket是我们经常使用到的接口,通常用于即时通讯以及K线图这种需要实时更新数据的业务需求上,传统的restful接口虽然可以满足,但是你需要轮询,这就要额外写一堆代码,不是很方便,用websocket就简单很多,我们来看代码 第一步定义全局常量、变量 const config = {host…

机器学习——随机森林

随机森林 1、集成学习方法 通过构造多个模型组合来解决单一的问题。它的原理是生成多个分类器/模型&#xff0c;各自独立的学习和做出预测。这些预测最后会结合成组合预测&#xff0c;因此优于任何一个单分类得到的预测。 2、什么是随机森林&#xff1f; 随机森林是一个包含…

秋招突击——7/5——设计模式知识点补充——适配器模式、代理模式和装饰器模式

文章目录 引言正文适配器模式学习篮球翻译适配器 面试题 代理模式学习面试题 装饰器模式学习装饰模式总结 面试题 总结 引言 为了一雪前耻&#xff0c;之前腾讯面试的极其差&#xff0c;设计模式一点都不会&#xff0c;这里找了一点设计模式的面试题&#xff0c;就针对几个常考…

【LLM】三、open-webui+ollama搭建自己的聊天机器人

系列文章目录 往期文章回顾&#xff1a; 【LLM】二、python调用本地的ollama部署的大模型 【LLM】一、利用ollama本地部署大模型 目录 前言 一、open-webui是什么 二、安装 1.docker安装 2.源码安装 三、使用 四、问题汇总 总结 前言 前面的文章&#xff0c;我们已经…

【续集】Java之父的退休之旅:从软件殿堂到多彩人生的探索

Java之父的退休之旅&#xff1a;从软件殿堂到多彩人生的探索-CSDN博客 四、科技领袖退休后的行业影响 4.1 传承与启迪 Gosling等科技领袖的退休&#xff0c;为行业内部年轻一代提供了更多的发展机会和成长空间。他们的退休不仅意味着权力和责任的交接&#xff0c;更是一种精…

Springboot项目实训--day2

今天学习的是idea和MySQL的连接&#xff0c;以及一些基本的增删改查的功能实现。 一、软件下载 昨天下载了idea&#xff0c;今天要是西安它们的连接&#xff0c;就需要再下载MySQL&#xff0c;我的MySQL是前面几个学期别人帮忙下载的&#xff0c;所以具体的操作步骤我也不清楚…

java SpringCloud版本b2b2c鸿鹄云商平台全套解决方案

使用技术&#xff1a; Spring CloudSpring BootMybatis微服务服务监控可视化运营 B2B2C平台&#xff1a; 平台管理端(包含自营) 商家平台端(多商户入驻) PC买家端、手机wap/公众号买家端 微服务&#xff08;30个通用微服务如&#xff1a;商品、订单、购物车、个人中心、支…