美文网首页优秀案例
MediaPlayer封装原生视频播放器

MediaPlayer封装原生视频播放器

作者: 南柯梦夏 | 来源:发表于2017-06-14 15:46 被阅读708次

    MediaPlayer可以用来控制视频和音频文件流,也就是说可以通过它播放音乐和视频。通常如果我们不用第三方的框架,有三种方式可以去播放视频。

    1.VideoView
    2.MediaPlayer+SurfaceView
    3.MediaPlayer+TextureView

    首先VideoView是继承自SurfaceView,内部维护着一个MediaPlayer,用过VideoView的人都知道,它的控制界面是比较丑陋的,当然我们一般在开发中是不会使用它的。而后面两种都可以自定义不妨界面,当然它们的区别是一个是SurfaceView,而另一个是TextureView。SurfaceView的原理就是在View的位置上重新创建一个Window,所有界面的绘制和渲染都是在新的Window中执行,不会影响主线程的执行,当然这也存在线程同步问题。另一个问题就是由于SurfaceView的显示是在新的Windows中,它不会受到View的属性控制,也不能放在RecycelerView或ListView中,也需要自己管理其生命周期。TextureView是在API14之后被加入的,和SurfaceView不同的是,TextureView不会在View的位置上创建一个新的窗口,所有的界面的绘制渲染都是在View上,这样就允许TextureView能够被移动,缩放或做些其它的动画。TextureView只能在硬件加速窗口中使用,当在软件中渲染时,TextureView将不会做任何绘制。

    好了,说了这么多,决定用第三种封装自己的播放器,当然本文是参考这篇文章做的
    用MediaPlayer+TextureView封装一个完美实现全屏、小窗口的视频播放器

    TextureView使用

    TexureView的使用是比较简单的,首先你需要做的就是获取它的SurfaceTexture,它能够被用来去渲染内容。它的使用大致如下

    private TextureView mTextureView;
    
    mTextureView = new TextureView(this);
    mTextureView.setSurfaceTextureListener(this);
    
    //监听回调
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    //SurfaceTexture准备就绪
    }
    
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    //SurfaceTexture缓冲大小变化
    }
    
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        //SurfaceTexture即将销毁
        return false;
    }
    
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        //SurfaceTexture状态更新
    }
    

    TextureView不能直接被使用,只有当它attached到一个Window之后,SurfaceTexture准备就绪之后,TextureView才能起作用。当监听的回调onSurfaceTextureAvailable被调用后,可以通过传入的的surface关联Mediaplayer,SurfaceTexture作为数据通道,把从数据源Mediaplayer中获取到的图像帧数据转化为GL外部纹理,交给TextureView作为View heirachy中的一个硬件加速层来显示,从而实现视频播放的功能。

    //关联Mediaplayer,之所以要做判断,是因为当mTextureView重新绘制之后,生命
    //周期方法会被回调,监听器也会被回调,而mediaplayer不会被销毁任然持
    //有SurfaceTexture的引用,所以当生命周期回调之后直接使用持有的
    //SurfaceTexture,任然可以继续播放
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        if(mSurfaceTexture == null){
            mSurfaceTexture = surface;
            mediaPlayerStart();
        }else {
            mTextureView.setSurfaceTexture(mSurfaceTexture);
        }
    }
    

    代码设计

    为了使业务逻辑分离解耦,将数据源和控制器部分分成两个类处理

    //数据源播放
    public class VideoPlayer extends FrameLayout implements TextureView.SurfaceTextureListener,
                                         VideoPlayerControl{}
    
    //控制器部分
    public class VideoPlayerController extends FrameLayout implements
            View.OnClickListener,
            SeekBar.OnSeekBarChangeListener,
            View.OnTouchListener{}
    

    其中数据源部分专门负责播放视频,处理播放状态的初始化和状态变化,而控制器部分专门负责播放界面的 播放,暂停,全屏,小窗口 操作。而两个类之间通过一个接口VideoPlayerControl关联。

    public interface VideoPlayerControl {
    
        void start();         //播放
        void pause();         //暂停
        void seekTo(int pos); //进度条拖动
        void restart();
        void release();
    
        boolean isIdle();  //是否是空闲
        boolean isError();
        boolean isPreparing();
        boolean isPrepared();
        boolean isBufferingPlaying();
        boolean isBufferingPaused();
        boolean isPlaying();
        boolean isPaused();
        boolean isCompleted();
    
        int getDuration();
        int getCurrentProgress();
        int getBufferPercent();
        FrameLayout getContainer();
    
        boolean isFullScreen();  //全屏
        boolean isNormalScreen();//普通窗口
        boolean isTinyScreen();  //小窗口
    
        void enterFullScreen();    //进入全屏
        boolean exitFullScreen();  //退出全屏
        void enterTinyScreen();    //进入小屏
        boolean exitTinyScreen();  //退出小屏
    }
    

    VideoPlayer

    常量

    定义几个常量来标注播放状态和界面窗口状态

    public static final int STATE_ERROR = -1;          // 播放错误
    public static final int STATE_IDLE = 0;            // 播放未开始
    public static final int STATE_PREPARING = 1;       // 播放准备中
    public static final int STATE_PREPARED = 2;        // 播放准备就绪
    public static final int STATE_PLAYING = 3;         // 正在播放
    public static final int STATE_PAUSED = 4;          // 暂停播放
    /**
     * 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
     **/
    public static final int STATE_BUFFERING_PLAYING = 5;
    /**
     * 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停)
     **/
    public static final int STATE_BUFFERING_PAUSED = 6;
    public static final int STATE_COMPLETED = 7;       // 播放完成
    
    public static final int PLAYER_NORMAL = 10;        // 普通播放器
    public static final int PLAYER_FULL_SCREEN = 11;   // 全屏播放器
    public static final int PLAYER_TINY_WINDOW = 12;   // 小窗口播放器
    

    mContainer

    初始化界面时,mController和mTextureView是添加到mContainer中的,这样做的好处就是,方便移除和添加窗口,所有的操作只需要通过对mContainer操作来完成

    关联视频播放器

    public void setController(VideoPlayerController controller){
       mController = controller;
       mController.setVideoPlayer(this);
       updateVideoPlayerState();
       mContainer.removeView(mController);
       LayoutParams lp = new LayoutParams(
               ViewGroup.LayoutParams.MATCH_PARENT,
               ViewGroup.LayoutParams.MATCH_PARENT);
       mContainer.addView(mController, lp);
    }
    

    我们需要传入一个controller然后关联此VideoPlayer就可以了。然后添加到mContainer中就可以了。

    设置视频播放uri

    public void setPlayUri(String uri){
        mUri = uri;
    }
    

    MediaPlayer

    要使用MediaPlayer需要设置几个监听器,

    mMediaPlayer.setOnPreparedListener(mOnPreparedListener);   //mediaplayer准备就绪回调
    mMediaPlayer.setOnCompletionListener(mOnCompletionListener); //mediaplayer播放完成监听
    mMediaPlayer.setOnErrorListener(mOnErrorListener);   //播放错误监听回调
    mMediaPlayer.setOnInfoListener(mOnInfoListener);     //播放器渲染状态变化监听
    mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); //播放器缓冲进度0-100回调
    

    初始化和播放

    //初始化
    private void mediaPlayerInit() {
        if(mMediaPlayer == null){
            mMediaPlayer = new MediaPlayer();
    
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setScreenOnWhilePlaying(true);  //在播放时屏幕一直开启着
    
            mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
            mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangeListener);
            mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
            mMediaPlayer.setOnErrorListener(mOnErrorListener);
            mMediaPlayer.setOnInfoListener(mOnInfoListener);
            mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);
        }
    }
    
    //播放
    private void mediaPlayerStart(){
        try {
            //设置数据源
            mMediaPlayer.setDataSource(mContext.getApplicationContext(), Uri.parse(mUri));
            //设置surface
            mMediaPlayer.setSurface(new Surface(mSurfaceTexture));
            //异步网络准备
            mMediaPlayer.prepareAsync();
    
            mCurrentState = STATE_PREPARING;
            updateVideoPlayerState();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    全屏和小窗口进入和退出

    全屏和小窗口差不多,它们的实现原理大致是,先从自己的view中移除mContainer,然后设置LayoutParams,最后在android.R.id.content中添加mContainer就可以了。只要设置不同的lp参数就可以实现全屏和小窗口播放。退出的话就只需要移除然后添加到FrameLayout容器中就可以了。注意全屏要设置屏幕方向,通过setRequestedOrientation请求屏幕方向。ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE为横屏,SCREEN_ORIENTATION_PORTRAIT为竖屏。

    注意: 变化的状态都可以通过updateVideoPlayerState()回调控制器更新窗口

     private void updateVideoPlayerState() {
        mController.setControllerState(mCurrentState, mWindowState);
     }
    

    VideoPlayerController

    这个类为自定义的一个控制器,所有播放器的操作都是通过这个控制器回调播放器VideoPlayer,这个控制器定义了一系列方法控制播放状态

    setTitle 设置页面标题
    setImage 设置封面背景
    setTopBottomVisible 头部和底部是否隐藏
    setControllerState 设置播放器工作状态
    updateProgress 更新进度

    同时做了一个小窗口界面拖拽

    @Override
    public boolean onTouch(View v, MotionEvent event) {
    
        if(!mVideoPlayerControl.isTinyScreen()) return super.onTouchEvent(event);
    
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                startX = event.getRawX();
                startY = event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
    
                float endX = event.getRawX();
                float endY = event.getRawY();
    
                float dx = endX - startX;
                float dy = endY - startY;
    
                LayoutParams params = (LayoutParams) mVideoPlayerControl.getContainer().getLayoutParams();
    
                int left = (int) (params.leftMargin + dx);
                int top = (int) (params.topMargin + dy);
                int viewHeight = mVideoPlayerControl.getContainer().getHeight() + 50;
                int viewWidth = mVideoPlayerControl.getContainer().getWidth();
    
                if(left < -1){
                    left = 0;
                }else if(left > screenWidth - viewWidth){
                    left = screenWidth - viewWidth;
                }
    
                if(top < -1){
                    top = 0;
                }else if(top > screenHeight - viewHeight){
                    top = screenHeight - viewHeight;
                }
    
                params.leftMargin = left;
                params.topMargin = top;
                mVideoPlayerControl.getContainer().setLayoutParams(params);
    
                startX = endX;
                startY = endY;
                break;
        }
    
        return super.onTouchEvent(event);
    }
    

    比较简单,就不多解释了。

    VideoPlayerManager

    这个类的作用就是保证在recyclerview或者listview中播放时只有一个列表可以播放。

    /**
     * 单例管理视频播放  在listview或者recyclerview中保证页面只有一个播放器在播放
     */
    public class VideoPlayerManager {
    
        private VideoPlayer mVideoPlayer;
    
        private VideoPlayerManager(){}
    
        private static VideoPlayerManager sInstance;
    
        public static VideoPlayerManager getInstance(){
    
            if(sInstance == null){
                sInstance = new VideoPlayerManager();
            }
    
            return sInstance;
        }
    
        public void releaseMediaplayer(){
            if(mVideoPlayer != null){
                mVideoPlayer.release();
                mVideoPlayer = null;
            }
        }
    
        public void setCurrentVideoPlayer(VideoPlayer videoPlayer){
            mVideoPlayer = videoPlayer;
        }
    
        /**
         *  释放资源
         */
        public boolean onBackPress(){
    
            if(mVideoPlayer.isFullScreen()){
                mVideoPlayer.exitFullScreen();
                return true;
            }else if(mVideoPlayer.isTinyScreen()){
                mVideoPlayer.exitTinyScreen();  //退出小屏
                return true;
            }
    
            if(mVideoPlayer != null){
                mVideoPlayer.release();
            }
    
            return false;
        }
    }
    
    

    用法

    private VideoPlayer videoPlayer;
    private VideoPlayerController mController;
    
    videoPlayer = (VideoPlayer) findViewById(R.id.vp);
    videoPlayer.setPlayUri("http://tanzi27niu.cdsb.mobi/wps/wp-content/uploads/2017/05/2017-05-17_17-33-30.mp4");
    mController = new VideoPlayerController(this);
    mController.setTitle("办公室小野开番外了,居然在办公室开澡堂!老板还点赞?");
    mController.setImage("http://tanzi27niu.cdsb.mobi/wps/wp-content/uploads/2017/05/2017-05-17_17-30-43.jpg");
    videoPlayer.setController(mController);
    

    效果

    这里写图片描述

    源码:github

    相关文章

      网友评论

        本文标题:MediaPlayer封装原生视频播放器

        本文链接:https://www.haomeiwen.com/subject/ujzsqxtx.html