视频播放在Android开发中也比较常见,Android API中提供了VideoView来实现视频播放,但是扩展性不太高,尤其是UI方面,而使用MediaPlayer+SurfaceView+MediaController则可实现多种多样的、扩展性高的视频播放器,但是有利就有弊,使用后者开发量相对而言比较大一点、比较复杂一点。下面就使用MediaPlayer+SurfaceView+自定义MediaController实现一个自定义视频播放器。
功能实现:
- 视频加载播放
- 视频播放。暂停
- 视频快进、快退
- 保留扩展性接口,如切换下一个等
- 其他
效果图:
视频播放器结构图:
- IPlayer作为播放器的总行为接口,定义play、start、pause、stop、seekTo等行为
public interface IPlayer {
void play();
void play(@NonNull String source);
void start();
void pause();
void stop();
void seekTo(int position);
boolean isPlaying();
long getCurrentStreamPosition();
void setStatus(int status);
int getStatus();
float getSpeed();
int getDuration();
void setCallback(@NonNull IPlayerCallback playerCallback);
void setOnMetadataUpdateListener();
void setVolume(float leftVolume, float rightVolume);
}
- VideoMediaPlayer实现了IPlayer,使用Android原生MediaPlayer负责具体播放功能实现
public class VideoMediaPlayer implements IPlayer, MediaPlayer.OnErrorListener,
MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnSeekCompleteListener {
private SurfaceHolder mSurfaceHolder;
private MediaPlayer mMediaPlayer;
private IPlayerCallback mPlayerCallback;
private Context mContext;
private Disposable mProgressSubscribe;
/**
* 是否缓冲完成
*/
private boolean isPrepared = false;
/**
* 播放状态
*/
@PlayStatus
private int mStatus;
public VideoMediaPlayer(Context context, SurfaceHolder surfaceHolder) {
mContext = context;
mSurfaceHolder = surfaceHolder;
createMediaPlayer();
setStatus(PlayStatus.NONE);
}
private void createMediaPlayer() {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnSeekCompleteListener(this);
}
@Override
public void play() {
if (mMediaPlayer.isPlaying()) {
pause();
} else {
start();
}
}
@Override
public void play(@NonNull String source) {
try {
mMediaPlayer.setDataSource(source);
mMediaPlayer.prepareAsync();
setStatus(PlayStatus.BUFFERING);
} catch (IOException e) {
setStatus(PlayStatus.ERROR);
if (mPlayerCallback != null) {
mPlayerCallback.onError(getString(R.string.play_error_prompt));
}
}
}
/**
* 这儿需要注意一下,一般此方法会在Activity/Fragment的start()中调用,但是如果是开始初始化视频的话,当调用方法时视频还未缓冲好,就会报错
*/
@Override
public void start() {
if (!mMediaPlayer.isPlaying() && isPrepared) {
mMediaPlayer.start();
setStatus(PlayStatus.PLAYING);
}
registerProgressListener();
}
@Override
public void pause() {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
setStatus(PlayStatus.PAUSED);
}
unRegisterProgress();
}
@Override
public void stop() {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
setStatus(PlayStatus.STOPPED);
unRegisterProgress();
}
@Override
public void seekTo(int position) {
mMediaPlayer.seekTo(position);
setStatus(PlayStatus.BUFFERING);
unRegisterProgress();
}
@Override
public boolean isPlaying() {
return mMediaPlayer != null && mMediaPlayer.isPlaying();
}
@Override
public long getCurrentStreamPosition() {
return isPlaying() ? mMediaPlayer.getCurrentPosition() : 0;
}
@Override
public void setStatus(int status) {
mStatus = status;
if (mPlayerCallback != null) {
mPlayerCallback.onPlayStatusChange(status);
}
}
@Override
public int getStatus() {
return mStatus;
}
@Override
public float getSpeed() {
return 0;
}
@Override
public int getDuration() {
return mMediaPlayer.getDuration();
}
@Override
public void setCallback(@NonNull IPlayerCallback playerCallback) {
mPlayerCallback = playerCallback;
}
@Override
public void setOnMetadataUpdateListener() {
}
@Override
public void setVolume(float leftVolume, float rightVolume) {
mMediaPlayer.setVolume(leftVolume, rightVolume);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
setStatus(PlayStatus.ERROR);
unRegisterProgress();
if (mPlayerCallback != null) {
mPlayerCallback.onError(getString(R.string.play_error_prompt));
return true;
} else {
return false;
}
}
@Override
public void onPrepared(MediaPlayer mp) {
isPrepared = true;
if (mPlayerCallback != null) {
mPlayerCallback.onPrepared(getDuration());
}
mp.setDisplay(mSurfaceHolder);
start();
}
@Override
public void onCompletion(MediaPlayer mp) {
if (mPlayerCallback != null) {
mPlayerCallback.onComplete();
}
setStatus(PlayStatus.COMPLETE);
unRegisterProgress();
}
@Override
public void onSeekComplete(MediaPlayer mp) {
start();
}
private void registerProgressListener() {
if (mPlayerCallback != null) {
unRegisterProgress();
mProgressSubscribe = Flowable.interval(Config.MEDIA_MONITOR_INTERVAL, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
mPlayerCallback.onUpdateProgress((int) getCurrentStreamPosition());
}
});
}
}
private void unRegisterProgress() {
if (mProgressSubscribe != null && !mProgressSubscribe.isDisposed()) {
mProgressSubscribe.dispose();
}
}
private String getString(@StringRes int res) {
return mContext.getString(res);
}
}
- VideoPlayerFactory负责创建IPalyer实现,这里也就是VideoMediaPlayer
public class VideoPlayerFactory {
public static IPlayer create(Context context, SurfaceHolder surfaceHolder) {
return new VideoMediaPlayer(context, surfaceHolder);
}
}
使用上述三者有什么好处呢?就是可以将播放行为和具体实现实现解耦,当具体播放功能实现由MediaPlayer更换为其他方式时,只需要实现IPlayer,更换VideoPlayerFactory中返回值即可,原来逻辑一点儿也不用变,这儿体现了面向对象依赖倒置原则、开闭原则、单一原则、里式替换原则面向对象几大原则
- VideoPlayerManager实现了IPlayer接口,使用IPlayer的具体实现即VideoMediaPlayer来统一管理各播放器行为(start、pause等)
public class VideoPlayerManager implements IPlayer {
private IPlayer mPlayer;
public VideoPlayerManager(@NonNull IPlayer player) {
mPlayer = player;
}
@Override
public void play() {
mPlayer.play();
}
@Override
public void play(@NonNull String source) {
mPlayer.play(source);
}
@Override
public void start() {
mPlayer.start();
}
@Override
public void pause() {
mPlayer.pause();
}
@Override
public void stop() {
mPlayer.stop();
}
@Override
public void seekTo(int position) {
mPlayer.seekTo(position);
}
@Override
public boolean isPlaying() {
return mPlayer.isPlaying();
}
@Override
public long getCurrentStreamPosition() {
return mPlayer.getCurrentStreamPosition();
}
@Override
public void setStatus(int status) {
}
@Override
public int getStatus() {
return mPlayer.getStatus();
}
@Override
public float getSpeed() {
return mPlayer.getSpeed();
}
@Override
public int getDuration() {
return mPlayer.getDuration();
}
@Override
public void setCallback(@NonNull IPlayerCallback playerCallback) {
mPlayer.setCallback(playerCallback);
}
@Override
public void setOnMetadataUpdateListener() {
}
@Override
public void setVolume(float leftVolume, float rightVolume) {
mPlayer.setVolume(leftVolume, rightVolume);
}
}
- IPlaylerCallback对VideoMediaPlayer播放状态进行回调,例如播放准备完毕、播放错误、播放进度、播放完成等
public interface IPlayerCallback {
void onPrepared(int totalTime);
void onError(String errorMsg);
void onComplete();
void onPlayStatusChange(int status);
void onUpdateProgress(int progress);
}
- PlayStatus中定义了各种播放状态,例如播放中、暂停、停止、缓冲、完成等状态
public @interface PlayStatus {
int NONE = 0x000000001;
int BUFFERING = 0x000000002;
int PLAYING = 0x000000003;
int PAUSED = 0x000000004;
int STOPPED = 0x000000005;
int ERROR = 0x000000006;
int COMPLETE = 0x000000007;
}
- VideoPlayerActivity/Fragment包含了播放器UI界面和各种控制事件,实现了IPlayerCallback接口,用来接收播放状态以用来更新UI播放控件
/**
* Video播放器
*/
public class VideoPlayerActivity extends RootActivity implements IPlayerCallback {
public static final String VIDEO_SOURCE = "https://vd2.bdstatic.com/mda-igwdmz0nv8ah95xv/mda-igwdmz0nv8ah95xv.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D&auth_key=1567068990-0-0-003a15194906c2b947cf493eec963a7f&bcevod_channel=searchbox_feed&pd=bjh";
@BindView(R.id.sl_act_video_player_show)
SurfaceView mSlActVideoPlayerShow;
@BindView(R.id.tv_item_master_bottom_audio_progress_time)
TextView mTvItemMasterBottomAudioProgressTime;
@BindView(R.id.sb_item_master_bottom_audio_progress)
AppCompatSeekBar mSbItemMasterBottomAudioProgress;
@BindView(R.id.tv_item_master_bottom_audio_all_time)
TextView mTvItemMasterBottomAudioAllTime;
@BindView(R.id.img_item_master_skip_previous)
ImageView mImgItemMasterSkipPrevious;
@BindView(R.id.img_item_master_bottom_play_or_pause)
ImageView mImgItemMasterBottomPlayOrPause;
@BindView(R.id.img_item_master_skip_next)
ImageView mImgItemMasterSkipNext;
private VideoPlayerManager mPlayerManager;
private SurfaceHolder mHolder;
private boolean isPlaying = false;
@Nullable
@Override
protected TitleBar setTitleBar() {
return null;
}
@Override
protected int setStatusBar() {
return Type.StatusBar.TRANSPARENT;
}
@Override
protected int setLayoutRes() {
return R.layout.activity_video_player;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.img_item_master_bottom_play_or_pause:
mPlayerManager.play();
break;
}
}
@Override
protected void initView() {
super.initView();
mImgItemMasterSkipPrevious.setVisibility(View.GONE);
mImgItemMasterSkipNext.setVisibility(View.GONE);
mHolder = mSlActVideoPlayerShow.getHolder();
mPlayerManager = new VideoPlayerManager(getPlayer());
mPlayerManager.setCallback(this);
mPlayerManager.play(VIDEO_SOURCE);
}
@Override
protected void setListener() {
super.setListener();
mSbItemMasterBottomAudioProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mPlayerManager.seekTo(seekBar.getProgress());
}
});
}
@Override
protected void onResume() {
super.onResume();
mPlayerManager.start();
}
@Override
protected void onPause() {
super.onPause();
mPlayerManager.pause();
}
@Override
protected void onStop() {
super.onStop();
if (isFinishing()) {
mPlayerManager.stop();
}
}
@Override
public void onPrepared(int totalTime) {
mSbItemMasterBottomAudioProgress.setMax(totalTime);
mTvItemMasterBottomAudioAllTime.setText(MediaUtil.convertToStrProgress(totalTime));
}
@Override
public void onError(String errorMsg) {
showError(errorMsg);
}
@Override
public void onComplete() {
showSuccess(R.string.play_complete);
}
@Override
public void onPlayStatusChange(int status) {
if (status == PlayStatus.PLAYING) {
mImgItemMasterBottomPlayOrPause.setImageResource(R.mipmap.icon_master_audio_pause);
} else {
mImgItemMasterBottomPlayOrPause.setImageResource(R.mipmap.icon_master_audio_play);
}
if (status == PlayStatus.COMPLETE) {//特殊情况下播放完毕和SeekBar进度稍微差点,这儿就给设置完毕了
mSbItemMasterBottomAudioProgress.setProgress(mSbItemMasterBottomAudioProgress.getMax());
}
}
@Override
public void onUpdateProgress(int progress) {
mSbItemMasterBottomAudioProgress.setProgress(progress);
mTvItemMasterBottomAudioProgressTime.setText(MediaUtil.convertToStrProgress(progress));
}
private IPlayer getPlayer() {
return VideoPlayerFactory.create(this, mHolder);
}
}
- SurfaceView和ControllerLayout是VideoPlayerActivity/Fragment的布局实现
以上各司其职,共同构成了一个视频播放器,有些地方可能没解释详细,但是需要注意的点在代码中都进行了注释,有不明白的地方或其他观点请留言交流,共同进步!
后记:
此视频播放器实现了基本功能,读者可在次基础上扩展实现其他更多功能。