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


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

1、功能 & 思路

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

【Android】 从头搭建视频播放器(1)——概述_第1张图片

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

【Android】 从头搭建视频播放器(1)——概述_第2张图片

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

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

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

2、基础设计

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

【Android】 从头搭建视频播放器(1)——概述_第3张图片

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

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

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

3、播放器布局

        整个 StrawMediaPlayer 的布局如下:

【Android】 从头搭建视频播放器(1)——概述_第4张图片

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

  • 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,用于捕获事件。该接口定义如下:

【Android】 从头搭建视频播放器(1)——概述_第5张图片

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 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();            }        });    }}

更多相关文章

  1. 2012版辅助开发工具包(ADT)新功能特性介绍及安装使用
  2. Android核心功能
  3. Android 4.0新增WiFiDirect功能
  4. Android:Camera2开发详解(上):实现预览、拍照、保存照片等功能
  5. 2011.10.14(2)——— android 仿照微信的图片展示功能 之 放大超过
  6. 自定义实现圆形播放进度条(android,飞一般的感觉)
  7. Qt for Android 调用android原生接口分享图片或文字
  8. Android使用MediaRecorder实现录音功能

随机推荐

  1. Android(安卓)技巧 - notification cente
  2. Android(安卓)3D 旋转的三角形(三)
  3. android:软件的安装和卸载源代码
  4. android 图片处理
  5. Android之应用首次使用的欢迎界面实例
  6. android studio 读取短信
  7. android ping
  8. Android中CheckBox复选框操作
  9. android 6.0权限全面详细分析和解决方案
  10. android 图片水平显示,类Gallery效果