【Android】 从头搭建视频播放器(1)——概述

来源:互联网 发布:asp编程要点 编辑:程序博客网 时间:2024/04/29 03:16

【Android】 从头搭建视频播放器(1)——概述


        转载请注明出处:http://blog.csdn.net/arnozhang12/article/details/48731443
 
 
        近来有做播放器方面的需求,在搭建过程中,逐渐对 Android 上面视频播放器的实现有了一些初步的了解,在此总结一下,在 Android 上面,如何从头考虑设计并最终实现一个功能完备的视频播放器。

1、功能 & 思路

        我们通常看到一个通用的播放器如下:

半屏播放器

        在点击全屏按钮或者旋转屏幕后,可以展开到全屏:

全屏播放器

        我们可以看出,一个通用的播放器有如下一些功能点:

  • 播放/暂停
  • 全屏切换
  • SeekBar 进度调节
  • 手势调节屏幕亮度/音量/播放进度
  • 屏幕旋转支持

        其中,播放器的基础功能我们可以使用系统的 MediaPlayer 或者第三方的一些 Player 实现。全屏切换可以通过更改 ScreenOrientation屏幕布局来完成。其他的手势调节可以通过 GestureDetector 来完成。屏幕旋转通过 OrientationEventListener 来实现。

2、基础设计

        有了一个大致的功能描述以及实现思路之后,我们需要设计底层的基本交互及类。经过功能提炼及交互划分,首先的模块设计如下:

基础结构设计

  • BaseMediaPlayer:定义了一个播放器应该具备的基础接口;你可以跟进这个接口再加播放器底层,来实现不同的播放器;只提供播放的接口,不提供用户 UI 交互;
  • SystemMediaPlayerImpl:继承自 BaseMediaPlayer,是基于 Android 系统 MediaPlayer 的一份实现;
  • StrawMediaPlayer:继承自 FrameLayout,是 Android 上的一个布局 View,封装了播放器的所有操作,用来进行可视化和用户 UI 交互;
  • PlayerBottomControl:播放器底部控件,用于控制播放、进度、全屏调节等;
  • MediaPlayerGestureController:播放器手势控制器,用于手势识别和相应的控制;
  • ScreenOrientationSwitcher:屏幕方向切换控制器。

        基础的模块就这么一些,其中应该还有一些用于展示屏幕亮度、声音、进度的小的 View,自己可以轻松实现,在此没有列出来。

        StrawMediaPlayer 就是我们最终提供出去的播放器控件,上层可以直接使用这个控件。

3、播放器布局

        整个 StrawMediaPlayer 的布局如下:

界面布局

        通过结合功能点进行初步设计,整个播放器层级如下:

  • FrameLayout:用于容纳所有的 View;
  • SurfaceView:用于展示视频内容;
  • LoadingLayout:在视频加载的时候,用于展示 Loading 画面;
  • TopBarControl:顶部的 Bar,用于展示视频信息、返回按钮等等;
  • PlayerBottomControl:底部的 Bar,用于展示视频控制按钮、播放进度、全屏切换等 View。

4、BaseMediaPlayer

        结合模块设计及布局划分,我们先定义 BaseMediaPlayer 应该具备的接口。必须具备的接口如下:

接口 功能 BaseMediaPlayer(context, surfaceView) 构造函数 play(videoInfo) 播放 addPlayerListener(listener) 添加播放回调 Listener pause() 暂停 resume() 恢复播放 stop() 停止播放 seekTo(millSeconds) 跳转到某个进度播放 setVolume(volume) 调节音量 getDuration() 获取整个视频的时间,返回 ms getCurrentPosition() 获取视频当前播放的进度,返回 ms doDestroy() 销毁播放器,释放系统资源 isPlaying() 是否正在播放 isLoading() 是否正在加载视频 getBufferPercent() 获取视频缓冲百分比

5、BaseMediaPlayerListener

        外部有可能需要监听一系列 Player 的事件,这时候,我们需要通过 addPlayerListener 添加一个 Listener,用于捕获事件。该接口定义如下:

BaseMediaPlayerListener

6、BaseMediaPlayer 代码片段

/** * BaseMediaPlayer.java * * @author arnozhang * @email  zyfgood12@163.com * @date   2015.9.25 */public abstract class BaseMediaPlayer {    protected static final String TAG = "MediaPlayer";    protected static interface NotifyListenerRunnable {        void run(BaseMediaPlayerListener listener);    }    protected Context mContext;    protected SurfaceView mSurfaceView;    protected SurfaceHolder.Callback mSurfaceCallback;    protected List<BaseMediaPlayerListener> mPlayerListeners = new ArrayList<>();    protected VideoInfo mVideoInfo;    protected int mVideoWidth;    protected int mVideoHeight;    protected boolean mIsLoading;    protected boolean mMediaPlayerIsPrepared;    protected boolean mVideoSizeInitialized;    protected int mBufferPercent;    protected int mVideoContainerZoneWidth;    protected boolean mAutoPlayWhenHolderCreated;    public BaseMediaPlayer(Context context, SurfaceView surfaceView) {        mContext = context;        mSurfaceView = surfaceView;        initSurfaceCallback();        SurfaceHolder videoHolder = mSurfaceView.getHolder();        videoHolder.addCallback(mSurfaceCallback);    }    public void play(VideoInfo videoInfo) {        if (!PlayerUtils.isNetworkAvailable()) {            StrawToast.makeText(mContext, R.string.network_connection_failed).show();            return;        }        mIsLoading = true;        mVideoSizeInitialized = false;        mMediaPlayerIsPrepared = false;        mVideoInfo = videoInfo;        Handler handler = ThreadManager.getInstance().getUIHandler();        handler.removeCallbacks(mLoadingFailedRunnable);        handler.postDelayed(mLoadingFailedRunnable, 30 * 1000);    }    public void addPlayerListener(BaseMediaPlayerListener listener) {        mPlayerListeners.add(listener);    }    public abstract void pause();    public abstract void resume();    public abstract void stop();    public abstract void seekTo(int millSeconds);    public abstract void setVolume(float volume);    public abstract int getDuration();    public abstract int getCurrentPosition();    public abstract void doDestroy();    public abstract boolean isPlaying();    protected abstract void playWithDisplayHolder(SurfaceHolder holder);    public boolean isLoading() {        return mIsLoading;    }    public boolean isLoadingOrPlaying() {        return isLoading() || isPlaying();    }    public int getBufferPercent() {        return mBufferPercent;    }    public Context getContext() {        return mContext;    }    private void initSurfaceCallback() {        mSurfaceCallback = new SurfaceHolder.Callback() {            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {            }            public void surfaceCreated(SurfaceHolder holder) {                if (mAutoPlayWhenHolderCreated) {                    mAutoPlayWhenHolderCreated = false;                    playWithDisplayHolder(holder);                }            }            public void surfaceDestroyed(SurfaceHolder holder) {                if (isPlaying()) {                    stop();                }            }        };    }    protected void updateSurfaceSize() {        updateSurfaceSize(mVideoContainerZoneWidth);    }    public void updateSurfaceSize(int containerWidth) {        if (mVideoContainerZoneWidth == containerWidth) {            return;        }        mVideoContainerZoneWidth = containerWidth;        if (mVideoWidth == 0 || mVideoHeight == 0) {            return;        }        final float ratio = (float) mVideoWidth / (float) mVideoHeight;        int width = 0;        int height = (int) (containerWidth / ratio);        if (height > mVideoHeight) {            width = containerWidth;        } else {            height = mVideoHeight;            width = (int) (mVideoWidth * ratio);        }        ViewGroup.LayoutParams params = mSurfaceView.getLayoutParams();        params.width = width;        params.height = height;        mSurfaceView.setLayoutParams(params);    }    protected Runnable mLoadingFailedRunnable = new Runnable() {        @Override        public void run() {            notifyLoadFailed();        }    };    protected void notifyListener(NotifyListenerRunnable runnable) {        for (BaseMediaPlayerListener listener : mPlayerListeners) {            runnable.run(listener);        }    }    protected void notifyLoading() {        LogUtils.e(TAG, "MediaPlayer Loading...");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onLoading();            }        });    }    protected void notifyFinishLoading() {        LogUtils.e(TAG, "MediaPlayer Finish Loading!");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onFinishLoading();            }        });    }    protected void notifyLoadFailed() {        LogUtils.e(TAG, "MediaPlayer Load **Failed**!!");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onLoadFailed();            }        });    }    protected void notifyError(final int what, final String message) {        LogUtils.e(TAG, "MediaPlayer Error. what = %d, message = %s.", what, message);        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onError(what, message);            }        });    }    protected void notifyStartPlay() {        LogUtils.e(TAG, "MediaPlayer Will Play!");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onStartPlay();            }        });    }    protected void notifyPlayComplete() {        LogUtils.e(TAG, "MediaPlayer Play Current Complete!");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onPlayComplete();            }        });    }    protected void notifyPaused() {        LogUtils.e(TAG, "MediaPlayer Paused.");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onPaused();            }        });    }    protected void notifyResumed() {        LogUtils.e(TAG, "MediaPlayer Resumed.");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onResumed();            }        });    }    protected void notifyStopped() {        LogUtils.e(TAG, "MediaPlayer Stopped!");        notifyListener(new NotifyListenerRunnable() {            @Override            public void run(BaseMediaPlayerListener listener) {                listener.onStopped();            }        });    }}
2 1