MediaPlayer封装原生视频播放器

来源:互联网 发布:淘宝直播刷粉丝软件 编辑:程序博客网 时间:2024/06/10 01:16

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 更新进度

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

@Overridepublic 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

阅读全文
0 0