本文基于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 Image
RecyclerView recyclerImage = viewHolder.getView(R.id.communal_post_header_recycler_image);
if (dataItem != null && dataItem.getVideoPosition() >= 0 && recyclerImage != null) {
//获取里层视频那个itemView position
int videoPosition = dataItem.getVideoPosition();
CommonViewHolder itemViewHolder = (CommonViewHolder) recyclerImage.findViewHolderForAdapterPosition(videoPosition);
//获取当前视频itemBen
VideoAdapter 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());
//获取当前视频itemBen
VideoAdapter 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 view
private ProgressBar mLoading;
//视频logo
private 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() {
@Override
public 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_TRANSITION
if (mExoPlayerListener != null) {
mExoPlayerListener.onVideoPlayerCompletion();
}
break;
}
}
@Override
public 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();
}
}
}
@Override
public 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() {
//初始化exoPlayer
if (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();
//移除播放View
if (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 {
//设置声音为0
mExoPlayer.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() {
@Override
public 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;
}
}
});