1.概述
本人经常刷今日头条,里面的视频模块我觉得做的很好,做Android的嘛,看到好的东西就想怎么实现的。头条的那个视频播放器,只能同时播放同一个视频,点击下一个上一个就停止,觉得这功能不错,决定自己实现一下。
先看一下最后的效果图吧:
2.实现思路
通过mediaPlayer + TextureView实现视频播放,播放,暂停,进度条分别对应MediaPlayer 的start,pause,seekTo方法,这些很简单。为了实现今日头条那样只允许一个视频播放的效果。想的是让MediaPlayer实例只有一个,当点击下一个视频的时候先将MediaPlayer的实例release掉,再置为null,然后重新进行初始化,从而实现单一播放效果。
头条还有一个功能是当前播放的视频滑动出屏幕的时候自动停止播放的功能,这个功能实现的思路是,通过recyclerView的addOnChildAttachStateChangeListener监听,监听接口中会实现onChildViewAttachedToWindow(View view)方法跟onChildViewDetachedFromWindow(View view)这两个方法。第一个监听的是recyclerView的item出现在屏幕中的回调,第二个是item移出屏幕时的回调,我们在第二个方法中,通过传过来的参数view拿到当前item的videoPlayer,调用player.release();方法即可实现视频滑动出屏幕自动停止播放的功能。
3.核心代码
播放器的代码就不贴了,太多,你懂的。。可以去下载完整版的代码
1.管理器代码
public class NiceVideoPlayerManager {
private NiceVideoPlayer mVideoPlayer;
private NiceVideoPlayerManager() {
}
private static NiceVideoPlayerManager sInstance;
public static synchronized NiceVideoPlayerManager instance() {
if (sInstance == null) {
sInstance = new NiceVideoPlayerManager();
}
return sInstance;
}
public void setCurrentNiceVideoPlayer(NiceVideoPlayer videoPlayer) {
mVideoPlayer = videoPlayer;
}
public void releaseNiceVideoPlayer() {
if (mVideoPlayer != null) {
mVideoPlayer.release();
mVideoPlayer = null;
}
}
public boolean onBackPressd() {
if (mVideoPlayer != null) {
if (mVideoPlayer.isFullScreen()) {
return mVideoPlayer.exitFullScreen();
} else if (mVideoPlayer.isTinyWindow()) {
return mVideoPlayer.exitTinyWindow();
} else {
mVideoPlayer.release();
return false;
}
}
return false;
}
public class NiceVideoPlayerController extends FrameLayout
implements View.OnClickListener,
SeekBar.OnSeekBarChangeListener {
private Context mContext;
private NiceVideoPlayerControl mNiceVideoPlayer;
private ImageView mImage;
private ImageView mCenterStart;
private LinearLayout mTop;
private ImageView mBack;
private TextView mTitle;
private LinearLayout mBottom;
private ImageView mRestartPause;
private TextView mPosition;
private TextView mDuration;
private SeekBar mSeek;
private ImageView mFullScreen;
private LinearLayout mLoading;
private TextView mLoadText;
private LinearLayout mError;
private TextView mRetry;
private LinearLayout mCompleted;
private TextView mReplay;
private TextView mShare;
private Timer mUpdateProgressTimer;
private TimerTask mUpdateProgressTimerTask;
private boolean topBottomVisible;
private CountDownTimer mDismissTopBottomCountDownTimer;
public NiceVideoPlayerController(Context context) {
super(context);
mContext = context;
init();
}
private void init() {
LayoutInflater.from(mContext).inflate(R.layout.nice_video_palyer_controller, this, true);
mCenterStart = (ImageView) findViewById(R.id.center_start);
mImage = (ImageView) findViewById(R.id.image);
mTop = (LinearLayout) findViewById(R.id.top);
mBack = (ImageView) findViewById(R.id.back);
mTitle = (TextView) findViewById(R.id.title);
mBottom = (LinearLayout) findViewById(R.id.bottom);
mRestartPause = (ImageView) findViewById(R.id.restart_or_pause);
mPosition = (TextView) findViewById(R.id.position);
mDuration = (TextView) findViewById(R.id.duration);
mSeek = (SeekBar) findViewById(R.id.seek);
mFullScreen = (ImageView) findViewById(R.id.full_screen);
mLoading = (LinearLayout) findViewById(R.id.loading);
mLoadText = (TextView) findViewById(R.id.load_text);
mError = (LinearLayout) findViewById(R.id.error);
mRetry = (TextView) findViewById(R.id.retry);
mCompleted = (LinearLayout) findViewById(R.id.completed);
mReplay = (TextView) findViewById(R.id.replay);
mShare = (TextView) findViewById(R.id.share);
mCenterStart.setOnClickListener(this);
mBack.setOnClickListener(this);
mRestartPause.setOnClickListener(this);
mFullScreen.setOnClickListener(this);
mRetry.setOnClickListener(this);
mReplay.setOnClickListener(this);
mShare.setOnClickListener(this);
mSeek.setOnSeekBarChangeListener(this);
this.setOnClickListener(this);
}
public void setTitle(String title) {
mTitle.setText(title);
}
public void setImage(String imageUrl) {
Glide.with(mContext)
.load(imageUrl)
.placeholder(R.drawable.img_default)
.crossFade()
.into(mImage);
}
public void setImage(@DrawableRes int resId) {
mImage.setImageResource(resId);
}
public void setNiceVideoPlayer(NiceVideoPlayerControl niceVideoPlayer) {
mNiceVideoPlayer = niceVideoPlayer;
if (mNiceVideoPlayer.isIdle()) {
mBack.setVisibility(View.GONE);
mTop.setVisibility(View.VISIBLE);
mBottom.setVisibility(View.GONE);
}
}
@Override
public void onClick(View v) {
if (v == mCenterStart) {
if (mNiceVideoPlayer.isIdle()) {
mNiceVideoPlayer.start();
}
} else if (v == mBack) {
if (mNiceVideoPlayer.isFullScreen()) {
mNiceVideoPlayer.exitFullScreen();
} else if (mNiceVideoPlayer.isTinyWindow()) {
mNiceVideoPlayer.exitTinyWindow();
}
} else if (v == mRestartPause) {
if (mNiceVideoPlayer.isPlaying() || mNiceVideoPlayer.isBufferingPlaying()) {
mNiceVideoPlayer.pause();
} else if (mNiceVideoPlayer.isPaused() || mNiceVideoPlayer.isBufferingPaused()) {
mNiceVideoPlayer.restart();
}
} else if (v == mFullScreen) {
if (mNiceVideoPlayer.isNormal()) {
mNiceVideoPlayer.enterFullScreen();
} else if (mNiceVideoPlayer.isFullScreen()) {
mNiceVideoPlayer.exitFullScreen();
}
} else if (v == mRetry) {
mNiceVideoPlayer.release();
mNiceVideoPlayer.start();
} else if (v == mReplay) {
mRetry.performClick();
} else if (v == mShare) {
Toast.makeText(mContext, "分享", Toast.LENGTH_SHORT).show();
} else if (v == this) {
if (mNiceVideoPlayer.isPlaying()
|| mNiceVideoPlayer.isPaused()
|| mNiceVideoPlayer.isBufferingPlaying()
|| mNiceVideoPlayer.isBufferingPaused()) {
setTopBottomVisible(!topBottomVisible);
}
}
}
private void setTopBottomVisible(boolean visible) {
mTop.setVisibility(visible ? View.VISIBLE : View.GONE);
mBottom.setVisibility(visible ? View.VISIBLE : View.GONE);
topBottomVisible = visible;
if (visible) {
if (!mNiceVideoPlayer.isPaused() && !mNiceVideoPlayer.isBufferingPaused()) {
startDismissTopBottomTimer();
}
} else {
cancelDismissTopBottomTimer();
}
}
public void setControllerState(int playerState, int playState) {
switch (playerState) {
case NiceVideoPlayer.PLAYER_NORMAL:
mBack.setVisibility(View.GONE);
mFullScreen.setVisibility(View.VISIBLE);
mFullScreen.setImageResource(R.drawable.ic_player_enlarge);
break;
case NiceVideoPlayer.PLAYER_FULL_SCREEN:
mBack.setVisibility(View.VISIBLE);
mFullScreen.setVisibility(View.VISIBLE);
mFullScreen.setImageResource(R.drawable.ic_player_shrink);
break;
case NiceVideoPlayer.PLAYER_TINY_WINDOW:
mFullScreen.setVisibility(View.GONE);
break;
}
switch (playState) {
case NiceVideoPlayer.STATE_IDLE:
break;
case NiceVideoPlayer.STATE_PREPARING:
// 只显示准备中动画,其他不显示
mImage.setVisibility(View.GONE);
mLoading.setVisibility(View.VISIBLE);
mLoadText.setText("正在准备...");
mError.setVisibility(View.GONE);
mCompleted.setVisibility(View.GONE);
mTop.setVisibility(View.GONE);
mCenterStart.setVisibility(View.GONE);
break;
case NiceVideoPlayer.STATE_PREPARED:
startUpdateProgressTimer();
break;
case NiceVideoPlayer.STATE_PLAYING:
mLoading.setVisibility(View.GONE);
mRestartPause.setImageResource(R.drawable.ic_player_pause);
startDismissTopBottomTimer();
break;
case NiceVideoPlayer.STATE_PAUSED:
mLoading.setVisibility(View.GONE);
mRestartPause.setImageResource(R.drawable.ic_player_start);
cancelDismissTopBottomTimer();
break;
case NiceVideoPlayer.STATE_BUFFERING_PLAYING:
mLoading.setVisibility(View.VISIBLE);
mRestartPause.setImageResource(R.drawable.ic_player_pause);
mLoadText.setText("正在缓冲...");
startDismissTopBottomTimer();
break;
case NiceVideoPlayer.STATE_BUFFERING_PAUSED:
mLoading.setVisibility(View.VISIBLE);
mRestartPause.setImageResource(R.drawable.ic_player_start);
mLoadText.setText("正在缓冲...");
cancelDismissTopBottomTimer();
case NiceVideoPlayer.STATE_COMPLETED:
cancelUpdateProgressTimer();
setTopBottomVisible(false);
mImage.setVisibility(View.VISIBLE);
mCompleted.setVisibility(View.VISIBLE);
if (mNiceVideoPlayer.isFullScreen()) {
mNiceVideoPlayer.exitFullScreen();
}
if (mNiceVideoPlayer.isTinyWindow()) {
mNiceVideoPlayer.exitTinyWindow();
}
break;
case NiceVideoPlayer.STATE_ERROR:
cancelUpdateProgressTimer();
setTopBottomVisible(false);
mTop.setVisibility(View.VISIBLE);
mError.setVisibility(View.VISIBLE);
break;
}
}
private void startUpdateProgressTimer() {
cancelUpdateProgressTimer();
if (mUpdateProgressTimer == null) {
mUpdateProgressTimer = new Timer();
}
if (mUpdateProgressTimerTask == null) {
mUpdateProgressTimerTask = new TimerTask() {
@Override
public void run() {
NiceVideoPlayerController.this.post(new Runnable() {
@Override
public void run() {
updateProgress();
}
});
}
};
}
mUpdateProgressTimer.schedule(mUpdateProgressTimerTask, 0, 300);
}
private void updateProgress() {
int position = mNiceVideoPlayer.getCurrentPosition();
int duration = mNiceVideoPlayer.getDuration();
int bufferPercentage = mNiceVideoPlayer.getBufferPercentage();
mSeek.setSecondaryProgress(bufferPercentage);
int progress = (int) (100f * position / duration);
mSeek.setProgress(progress);
mPosition.setText(NiceUtil.formatTime(position));
mDuration.setText(NiceUtil.formatTime(duration));
}
private void cancelUpdateProgressTimer() {
if (mUpdateProgressTimer != null) {
mUpdateProgressTimer.cancel();
mUpdateProgressTimer = null;
}
if (mUpdateProgressTimerTask != null) {
mUpdateProgressTimerTask.cancel();
mUpdateProgressTimerTask = null;
}
}
private void startDismissTopBottomTimer() {
cancelDismissTopBottomTimer();
if (mDismissTopBottomCountDownTimer == null) {
mDismissTopBottomCountDownTimer = new CountDownTimer(8000, 8000) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
setTopBottomVisible(false);
}
};
}
mDismissTopBottomCountDownTimer.start();
}
private void cancelDismissTopBottomTimer() {
if (mDismissTopBottomCountDownTimer != null) {
mDismissTopBottomCountDownTimer.cancel();
}
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
cancelDismissTopBottomTimer();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (mNiceVideoPlayer.isBufferingPaused() || mNiceVideoPlayer.isPaused()) {
mNiceVideoPlayer.restart();
}
int position = (int) (mNiceVideoPlayer.getDuration() * seekBar.getProgress() / 100f);
mNiceVideoPlayer.seekTo(position);
startDismissTopBottomTimer();
}
/**
* 控制器恢复到初始状态
*/
public void reset() {
topBottomVisible = false;
cancelUpdateProgressTimer();
cancelDismissTopBottomTimer();
mSeek.setProgress(0);
mSeek.setSecondaryProgress(0);
mCenterStart.setVisibility(View.VISIBLE);
mImage.setVisibility(View.VISIBLE);
mBottom.setVisibility(View.GONE);
mFullScreen.setImageResource(R.drawable.ic_player_enlarge);
mTop.setVisibility(View.VISIBLE);
mBack.setVisibility(View.GONE);
mLoading.setVisibility(View.GONE);
mError.setVisibility(View.GONE);
mCompleted.setVisibility(View.GONE);
}
以上代码为最为核心代码。其实还有很多功能可以实现,比如加入重力感应进行全屏,退出全屏,这个需要监听重力感应器的变化。也可以重写onTouch事件监听手势实现滑动快进,音量,屏幕亮度的调节,我会在以后的代码中完善这些功能。希望大家有好的建议多多指教,大家在Android的世界里一起进步。
最后附上demo源码:https://gitee.com/fireqiang/MyVideoPlayer