Android 从头搭建视频播放器——概述

Posted 牧秦丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 从头搭建视频播放器——概述相关的知识,希望对你有一定的参考价值。

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

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

以上是关于Android 从头搭建视频播放器——概述的主要内容,如果未能解决你的问题,请参考以下文章

Android 从头搭建视频播放器——SystemMediaPlayerImpl

Android 从头搭建视频播放器——SystemMediaPlayerImpl

Android 从头搭建视频播放器——将所有放在一起

Android 从头搭建视频播放器——将所有放在一起

Android 从头搭建视频播放器——屏幕旋转处理

Android 从头搭建视频播放器——屏幕旋转处理