重拾播放器
//依赖
api 'com.shuyu:GSYVideoPlayer:7.1.5'
对GSYVideo进行二次封装,方便自定义布局,修改样式
public class TyVideo extends StandardGSYVideoPlayer {
private ImageView mCoverImage;
public TyVideo(Context context, Boolean fullFlag) {
super(context, fullFlag);
}
public TyVideo(Context context) {
super(context);
}
public TyVideo(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void init(Context context) {
// 不调父类的 init
// super.init(context);
// GSYVideoView int
if (getActivityContext() != null) {
this.mContext = getActivityContext();
} else {
this.mContext = context;
}
initInflate(mContext);
mTextureViewContainer = (ViewGroup) findViewById(R.id.surface_container);
if (isInEditMode())
return;
mScreenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
mScreenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
mAudioManager = (AudioManager) mContext.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
// GSYVideoControlView
mLoadingProgressBar = findViewById(R.id.loading); //载入中动画
mStartButton = findViewById(R.id.start); //播放按钮
mBackButton = (ImageView) findViewById(R.id.back); //返回按键
mLockScreen = (ImageView) findViewById(R.id.lock_screen); //锁定图标
mFullscreenButton = (ImageView) findViewById(R.id.fullscreen); //全屏按钮
mTitleTextView = (TextView) findViewById(R.id.title); //title
mTotalTimeTextView = (TextView) findViewById(R.id.total); //总的时长
mCurrentTimeTextView = (TextView) findViewById(R.id.current); //时间显示
mBottomContainer = (ViewGroup) findViewById(R.id.layout_bottom); //顶部和底部区域
mTopContainer = (ViewGroup) findViewById(R.id.layout_top); //顶部和底部区域
mProgressBar = (SeekBar) findViewById(R.id.progress); //进度条
mBottomProgressBar = (ProgressBar) findViewById(R.id.bottom_progressbar); //底部进度条
mThumbImageViewLayout = (RelativeLayout) findViewById(R.id.thumb); //用来装封面的容器
if (isInEditMode())
return;
if (mStartButton != null) {
mStartButton.setOnClickListener(this);
}
if (mFullscreenButton != null) {
mFullscreenButton.setOnClickListener(this);
mFullscreenButton.setOnTouchListener(this);
}
if (mProgressBar != null) {
mProgressBar.setOnSeekBarChangeListener(this);
}
if (mBottomContainer != null) {
mBottomContainer.setOnClickListener(this);
}
if (mTextureViewContainer != null) {
mTextureViewContainer.setOnClickListener(this);
mTextureViewContainer.setOnTouchListener(this);
}
if (mProgressBar != null) {
mProgressBar.setOnTouchListener(this);
}
if (mThumbImageViewLayout != null) {
mThumbImageViewLayout.setVisibility(GONE);
mThumbImageViewLayout.setOnClickListener(this);
}
if (mThumbImageView != null && !mIfCurrentIsFullscreen && mThumbImageViewLayout != null) {
mThumbImageViewLayout.removeAllViews();
resolveThumbImage(mThumbImageView);
}
if (mBackButton != null)
mBackButton.setOnClickListener(this);
if (mLockScreen != null) {
mLockScreen.setVisibility(GONE);
mLockScreen.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mCurrentState == CURRENT_STATE_AUTO_COMPLETE ||
mCurrentState == CURRENT_STATE_ERROR) {
return;
}
lockTouchLogic();
if (mLockClickListener != null) {
mLockClickListener.onClick(v, mLockCurScreen);
}
}
});
}
if (getActivityContext() != null) {
mSeekEndOffset = CommonUtil.dip2px(getActivityContext(), 50);
}
// GSYBaseVideoPlayer
mSmallClose = findViewById(R.id.small_close);
//StandardGSYVideoPlayer
if (mBottomProgressDrawable != null) {
mBottomProgressBar.setProgressDrawable(mBottomProgressDrawable);
}
if (mBottomShowProgressDrawable != null) {
mProgressBar.setProgressDrawable(mBottomProgressDrawable);
}
if (mBottomShowProgressThumbDrawable != null) {
mProgressBar.setThumb(mBottomShowProgressThumbDrawable);
}
mCoverImage = new ImageView(getContext());
mCoverImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
setThumbImageView(mCoverImage);
}
/**
* 设置封面图片
* <p>
* GlideApp.with(context).load(imageUrl).into(player.getCoverView());
*
* @return
*/
public ImageView getCoverView() {
return mCoverImage;
}
@Override
public int getLayoutId() {
return R.layout.lib_video_layout;
}
/**
* 开始播放
*/
@Override
public void startPrepare() {
super.startPrepare();
}
/**
* 自定义 开始/ 暂停/ 错误的图标
*/
@Override
protected void updateStartImage() {
if (mStartButton instanceof ENPlayView) {
ENPlayView enPlayView = (ENPlayView) mStartButton;
enPlayView.setDuration(500);
if (mCurrentState == CURRENT_STATE_PLAYING) {
enPlayView.play();
} else if (mCurrentState == CURRENT_STATE_ERROR) {
enPlayView.pause();
} else {
enPlayView.pause();
}
} else if (mStartButton instanceof ImageView) {
ImageView imageView = (ImageView) mStartButton;
if (mCurrentState == CURRENT_STATE_PLAYING) {
//暂停
imageView.setImageResource(R.drawable.video_click_pause_selector);
} else if (mCurrentState == CURRENT_STATE_ERROR) {
//错误
imageView.setImageResource(R.drawable.video_click_error_selector);
} else {
//播放
imageView.setImageResource(R.drawable.video_click_play_selector);
}
}
}
/**
* 是否处于暂停状态
*
* @return
*/
public boolean isInInPause() {
return (mCurrentState >= 0 && mCurrentState == CURRENT_STATE_PAUSE);
}
}
重写布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<!-- 这个用来装我们视频播放器的不要删除,可以改变大小。-->
<RelativeLayout
android:id="@+id/surface_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
</RelativeLayout>
<!-- 这个是用来装封面的容器 -->
<RelativeLayout
android:id="@+id/thumb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="#000000"
android:scaleType="fitCenter" />
<LinearLayout
android:id="@+id/layout_bottom"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:background="#99000000"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="invisible">
<TextView
android:id="@+id/current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="00:00"
android:textColor="#ffffff" />
<!--可以拖动快进的 进度条-->
<SeekBar
android:id="@+id/progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1.0"
android:background="@null"
android:max="100"
android:maxHeight="4dp"
android:minHeight="4dp"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:progressDrawable="@drawable/video_seek_progress"
android:thumb="@drawable/video_seek_thumb" />
<!-- 总的时长 -->
<TextView
android:id="@+id/total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:text="00:00"
android:textColor="#ffffff" />
<ImageView
android:id="@+id/fullscreen"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingRight="16dp"
android:scaleType="center"
android:src="@drawable/video_enlarge" />
</LinearLayout>
<!--视屏底部进度条,不可以拖动-->
<ProgressBar
android:id="@+id/bottom_progressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="1.5dp"
android:layout_alignParentBottom="true"
android:max="100"
android:progressDrawable="@drawable/video_progress" />
<ImageView
android:id="@+id/back_tiny"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="6dp"
android:layout_marginTop="6dp"
android:visibility="gone" />
<LinearLayout
android:id="@+id/layout_top"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/video_title_bg"
android:gravity="center_vertical">
<ImageView
android:id="@+id/back"
android:layout_width="48dp"
android:layout_height="48dp"
android:paddingLeft="10dp"
android:scaleType="centerInside"
android:src="@drawable/video_back" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:textColor="@android:color/white"
android:textSize="18sp" />
</LinearLayout>
<moe.codeest.enviews.ENDownloadView
android:id="@+id/loading"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:visibility="invisible" />
<ImageView
android:id="@+id/start"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/small_close"
android:layout_width="30dp"
android:layout_height="30dp"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:scaleType="centerInside"
android:src="@drawable/video_small_close"
android:visibility="gone" />
<ImageView
android:id="@+id/lock_screen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="50dp"
android:visibility="gone"
android:scaleType="centerInside"
android:src="@drawable/unlock" />
</RelativeLayout>
视频管理类 --->
对于封面的播放按钮点击事件和播放完成单独做了接口回调,为的是点击开始按钮时隐藏掉封面上展示的播放量之类的控件,视频播放完之后显示,并释放资源。
/**
* @describe:视频播放
* @author:TY
*/
class VideoPlayingManger(private val gsyVideoOptionBuilder: GSYVideoOptionBuilder) {
/**
* @param TyVideo Video控件
* @param videoUrl 视频 URL
* @param imageUrl 封面图 URL
* @param title 全屏下标题
* @param adapterPosition 防止错位传适配器绝对位置 getAbsoluteAdapterPosition()
* @param mPlayTag 防止tag错误 不能重复
*/
fun playVideo(videoView: TyVideo, videoUrl: String, imageUrl: String, title: String, adapterPosition: Int, mPlayTag: String, onVideoPlayingMangerlistener: VideoPlayingMangerlistener) {
gsyVideoOptionBuilder
.setIsTouchWiget(true) //是否可以滑动界面改变进度,声音等 默认true
.setUrl(videoUrl) //播放url
.setCacheWithPlay(true) //是否边缓存,m3u8等无效
.setRotateViewAuto(true) //是否开启自动旋转
.setLockLand(true) //一全屏就锁屏横屏,默认false竖屏,可配合setRotateViewAuto使用
.setPlayTag(mPlayTag) //播放tag防止错误,因为普通的url也可能重复 playTag - 保证不重复就好
.setShowFullAnimation(true) //是否使用全屏动画效果
.setNeedLockFull(false) //是否需要全屏锁定屏幕功能 如果单独使用请设置setIfCurrentIsFullscreen为true
.setNeedShowWifiTip(true) //是否需要显示流量提示,默认true
.setPlayPosition(adapterPosition) //设置播放位置防止错位 (获取绝对适配器位置)
.setVideoAllCallBack(object : GSYSampleCallBack() {
/**
* 在进入全屏模式
* @param url
* @param objects
*/
override fun onEnterFullscreen(url: String, vararg objects: Any) {
super.onEnterFullscreen(url, *objects)
//获取当前正在播放的播放控件,将title隐藏
videoView.currentPlayer.titleTextView.visibility = View.VISIBLE
videoView.currentPlayer.titleTextView.text = title
}
override fun onClickStartIcon(url: String?, vararg objects: Any?) {
super.onClickStartIcon(url, *objects)
onVideoPlayingMangerlistener.onClickStartIcon()
}
override fun onAutoComplete(url: String?, vararg objects: Any?) {
super.onAutoComplete(url, *objects)
onVideoPlayingMangerlistener.onAutoComplete()
//播放完之后释放资源
GSYVideoManager.releaseAllVideos()
}
}).build(videoView)
//增加title
videoView.titleTextView.visibility = View.GONE
//设置返回键
videoView.backButton.visibility = View.GONE
//设置全屏按键功能
videoView.fullscreenButton.setOnClickListener {
resolveFullBtn(videoView)
}
//设置封面图片
Glide.with(videoView.context).load(imageUrl).into(videoView.coverView)
}
/**
* 全屏幕按键处理
*/
private fun resolveFullBtn(standardGSYVideoPlayer: StandardGSYVideoPlayer) {
standardGSYVideoPlayer.startWindowFullscreen(standardGSYVideoPlayer.context, true, true)
}
interface VideoPlayingMangerlistener {
/**
* 点击了播放按钮
*/
fun onClickStartIcon()
/**
* 播放完了
*/
fun onAutoComplete()
}
}
具体使用:AndroidManifest.xml
<!-- 使用播放器更改配置并设置屏幕方向 有些类型视频无法播放 开启硬件加速 -->
<activity
android:name=".xx.xx.XXXActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:hardwareAccelerated="true"
android:screenOrientation="portrait" />
private val videoPlayingManger = VideoPlayingManger(GSYVideoOptionBuilder())
mBinding.apply {
videoPlayingManger.playVideo(
mBinding.video,
"https://media.w3.org/2010/05/sintel/trailer.mp4",
"https://t7.baidu.com/it/u=3601447414,1764260638&fm=193&f=GIF",
"测试横屏显示",
0,
"0",
object : VideoPlayingManger.VideoPlayingMangerlistener {
override fun onClickStartIcon() {
//点击封面的播放按钮
}
override fun onAutoComplete() {
//播放完
}
}
)
}
此外还有单独滚动自动播放,滑动不可见小屏播放辅助类
public interface TyAutoPlayVideoHolder {
/**
* 绑定播放器 TyVideo
*/
TyVideo getVideoView();
}
/**
* Video自动播放滚动 Helper
*/
public class TyVideoAutoPlayScrollHelper extends RecyclerView.OnScrollListener {
int firstVisibleItem;
int lastVisibleItem;
LinearLayoutManager linearLayoutManager;
/**
* 自动播放滚动
* <p>
* <p>
* mRecyclerView.addOnScrollListener(new TyVideoAutoPlayScrollHelper((LinearLayoutManager) mRecyclerView.getLayoutManager()));
*
* @param linearLayoutManager
*/
public TyVideoAutoPlayScrollHelper(LinearLayoutManager linearLayoutManager) {
this.linearLayoutManager = linearLayoutManager;
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
//大于0说明有播放
if (GSYVideoManager.instance().getPlayPosition() >= 0) {
//当前播放的位置
int position = GSYVideoManager.instance().getPlayPosition();
//对应的播放列表TAG
if ((position < firstVisibleItem || position > lastVisibleItem)) {
//如果滑出去了上面和下面就是否,和今日头条一样
//是否全屏
if (!GSYVideoManager.isFullState((Activity) recyclerView.getContext())) {
GSYVideoManager.releaseAllVideos();
recyclerView.getAdapter().notifyItemChanged(position);
}
}
}
}
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
play(recyclerView);
}
}
/**
* 从第一可见的position 到最后一个可见的position 挨个遍历
* 通过 position 找到 对应的item view
* 在通过 item view 找到 holder 对象
* 如果这个holder 是一个视频广告的holder
* 如果这个item 视频播放器全部可见 那么通过这个item view 找到 对应的播放器控件,然后调用start 播放
*/
public void play(RecyclerView recyclerView) {
// 遍历第一个可见 item 和 最后一个可见item 之间的 是否有 视频广告item
for (int i = firstVisibleItem; i <= lastVisibleItem; i++) {
View itemView = linearLayoutManager.findViewByPosition(i);// 根据 position 找到 item view;
if (itemView == null) {
return;
}
RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(itemView); // 更具 item view 找到对应 holder
if (holder instanceof TyAutoPlayVideoHolder) { // 如果这个holder 实现了这个接口,意味着这个holder 对应的item 里面的视频会自动播放
TyVideo video = ((TyAutoPlayVideoHolder) holder).getVideoView();
int itemViewTop = itemView.getTop(); // item 在 recycler view 中的 top
int videoHeight = video.getHeight();// 视频控件的高度
int videoTopYInRecyclerView = itemViewTop + video.getTop(); // video view 在 recycler view (祖宗容器) 的 top 值
if (videoTopYInRecyclerView < 0) { // 如果video view 顶部有一部分不在recycler view 里面
// 顶部不在 recycler view 里面的部分小于制定的值,那么播放
if (Math.abs(videoTopYInRecyclerView) <= getPlayOrStopThreshold(videoHeight)) {
if (video.isInInPause()) {
video.onVideoResume(false);
} else if (!video.isInPlayingState()) {
video.startPrepare();
}
break;
} else {
if (video.isInPlayingState()) {
video.onVideoPause();
}
}
} else {
int videoBottomYInRecyclerView = itemView.getTop() + video.getBottom(); // video view 在 recycler vie 中的 bot
int excess = videoBottomYInRecyclerView - recyclerView.getHeight(); // video view 在 recycler view 中超出部分
// 如果video view 整个都在recycler view 里面或者 video 有一部分已经超出了 recycler view 下面一部分,但是超出部分不足 video view 高度的三分之一
if (excess < getPlayOrStopThreshold(videoHeight)) {
if (video.isInInPause()) {
video.onVideoResume(false);
} else if (!video.isInPlayingState()) {
video.startPrepare();
}
break;
} else {
if (video.isInPlayingState()) {
video.onVideoPause();
}
}
}
}
}
}
/**
* 返回 video view 上边 或者 下边 垂直方向上在屏幕之外的距离,如果大于这个距离 就停止播放,小于这个距离就自动播放
* 默认是高度的 1/3, 返回 0 表示,只有整个Video View 都在屏幕上可见是才播放。
*
* @param videoHeight
* @return
*/
public int getPlayOrStopThreshold(int videoHeight) {
return 0;
}
/**
* 看列表(RecycleView)是否有满足自动播放的 video
* 如果有的话就将列表向下滑动 1 个像素
* 使得 广告Video 自动播放
* TyVideoAutoPlayScrollHelper.playIfNeed(mRecyclerView);
*/
public static void playIfNeed(final RecyclerView recyclerView) {
recyclerView.post(new Runnable() {
@Override
public void run() {
recyclerView.smoothScrollBy(0, 1);
}
});
}
}