前言
最近项目有个需求 , 做个类似抖音的视频效果. 又因为包大小的问题不使用第三方SDK,所以使用原生的VideoView开发了一下, 搭配RecyclerView和PageSnapHelper来实现抖音的效果.
全部代码: github
看一下实现流程:
首先创建了一个继承自ConstraintLayout的View,用来实现自己的布局.东西不多, 里面主要是播放控制的按钮, 和一个可以拖动的进度条.在视频加载成功之前显示一个封面图.
然后创建了一个State的enum类:
public enum VideoState {unKnow,loadFinish,playing,playEnd,error,pause}
这是主要是用于在视频进度有变化的时候用来保存一下状态 .
之后创建了一个handler , 用于实时更新进度条,以及已播放的时间.
@SuppressLint(\"HandlerLeak\")private final Handler mHandler = new Handler() {@SuppressLint(\"SetTextI18n\")@Overridepublic void handleMessage(Message msg) {if (msg.what == UPDATE_PROGRESS) {if (videoView.isPlaying()) {int currentTime = videoView.getCurrentPosition();if (currentTime >= mDuration) {videoView.seekTo(0);seekBarProgress.setProgress(0);alreadyTextView.setText(\"00:00\");mHandler.removeMessages(UPDATE_PROGRESS);} else {seekBarProgress.setProgress(currentTime);mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 500);alreadyTextView.setText(TimeUtil.formatTimeWhichExist(currentTime));}}}}};
然后实现一个SeekBar 的监听 ,
3个函数分别是:
- OnSeekBarChangeListener: 进度条有改变
- onStartTrackingTouch 手指按下进度条
- onStopTrackingTouch 手指离开进度条
private SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {alreadyTextView.setText(TimeUtil.formatTimeWhichExist(progress));if (mOnProgressChangedListener != null) {mOnProgressChangedListener.onProgressChanged(progress);}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {// 暂停刷新mHandler.removeMessages(UPDATE_PROGRESS);}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {int progress = seekBar.getProgress();if (videoView != null) {if (progress + 1000 < mDuration) {// 设置当前播放的位置videoView.seekTo(progress);mHandler.sendEmptyMessage(UPDATE_PROGRESS);} else {mVideoState = VideoState.playEnd;start();}}}};
在按下的时候 , 停止跟随视频进度改变, 离开时 , 从当前位置继续播放.
但是视频是有关键帧的, 所以可能会跳到前一个关键帧的位置播放.
在进度有改变的时候, 实时改变已播放的时间的textview.
在播放器初始化的时候, 监听一下视频播放.
- setOnPreparedListener 是去加载视频 , 有一个加载完成时的回调, 这里在加载完成时, 隐藏封面并开始播放.
- setOnCompletionListener 是播放完成时的回调,此时改变进度并重新播放视频.
- setOnErrorListener 是视频播放出错的回调
videoView.setOnPreparedListener(mp -> {mVideoState = VideoState.loadFinish;mHandler.sendEmptyMessage(UPDATE_PROGRESS);totalPlayTextView.setText(Util.formatTimeWhichExist(mDuration));videoThumb.setVisibility(GONE);start();});videoView.setOnCompletionListener(mp -> {mHandler.removeMessages(UPDATE_PROGRESS);mVideoState = VideoState.playEnd;changePlayIcon();seekBarProgress.setProgress(0);alreadyTextView.setText(\"00:00\");videoThumb.setVisibility(VISIBLE);});videoView.setOnErrorListener((mp, what, extra) -> {//异常回调mVideoState = VideoState.error;return false;});
之后定义了一些操控视频的方法 , 像播放,暂停,获取播放进度等等的public方法, 用来给外界调用.
public void start() {if (mVideoState == VideoState.playEnd) {videoView.resume();} else {videoView.start();}mVideoState = VideoState.playing;changePlayIcon();mHandler.sendEmptyMessage(UPDATE_PROGRESS);}public void pause() {videoView.pause();mVideoState = VideoState.pause;changePlayIcon();}public void stop() {videoView.stopPlayback();mHandler.removeMessages(UPDATE_PROGRESS);}public void setVideoVisible() {controllerLayout.setVisibility(VISIBLE);}public void setVideoGone() {controllerLayout.setVisibility(GONE);}public VideoState getState() {if (videoView.isPlaying()) {mVideoState = VideoState.playing;}return mVideoState;}public int getCurrentPosition() {return videoView.getCurrentPosition();}
设置视频数据:
public void setVideo(VideoInfo video) {try {videoView.setVideoURI(Uri.parse(video.getUrl()));mDuration = (int) video.getDuration();seekBarProgress.setMax(mDuration);start();//宽高比int width = video.getWidth();int height = video.getHeight();float aspectRatio = (float) width / height;ConstraintLayout.LayoutParams layoutParamsThumb = (ConstraintLayout.LayoutParams) videoThumb.getLayoutParams(); //取控件textView当前的布局参数setLayoutParam(layoutParamsThumb, aspectRatio);videoThumb.setLayoutParams(layoutParamsThumb);} catch (Throwable ignore) {}}
这里是播放的网络视频, 所以传进来的是一个url , 并且根据宽高比显示封面图 , 使其和视频的位置和大小是一致的.
其他代码应该都不需要介绍了, 详细了解可以看看github