Giter Club home page Giter Club logo

videoplayer's Introduction

视频播放在Android开发中也比较常见,Android API中提供了VideoView来实现视频播放,但是扩展性不太高,尤其是UI方面,而使用MediaPlayer+SurfaceView+MediaController则可实现多种多样的、扩展性高的视频播放器,但是有利就有弊,使用后者开发量相对而言比较大一点、比较复杂一点。下面就使用MediaPlayer+SurfaceView+自定义MediaController实现一个自定义视频播放器。

功能实现:

  1. 视频加载播放
  2. 视频播放。暂停
  3. 视频快进、快退
  4. 保留扩展性接口,如切换下一个等
  5. 其他

效果图:

VideoPlayer.png

视频播放器结构图:

结构图.png

  • 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的布局实现

以上各司其职,共同构成了一个视频播放器,有些地方可能没解释详细,但是需要注意的点在代码中都进行了注释,有不明白的地方或其他观点请留言交流,共同进步!

后记:

此视频播放器实现了基本功能,读者可在次基础上扩展实现其他更多功能。

videoplayer's People

Contributors

lvtravler avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

rohitnotes

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.