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
- MediaPlayer封装原生视频播放器
- MediaPlayer/MediaPlayer 视频播放
- Android视频播放器mediaplayer
- android视频播放(二) 利用android原生的MediaPlayer+SurfaceView
- android视频播放(二) 利用android原生的MediaPlayer+SurfaceView
- 一个封装好的SurfaceView+MediaPlayer开源视频播放器
- 用MediaPlayer+TextureView封装一个实现全屏、小窗口的视频播放器
- Android原生视频播放器
- Android MediaPlayer本地视频播放器
- Android MediaPlayer本地视频播放器
- MediaPlayer+surfaceView实现视频播放器
- Android SurfaceView + MediaPlayer实现视频播放器
- Android视频播放器之学习MediaPlayer
- android MediaPlayer SurfaceView 网络视频播放器
- android surfaceView+mediaPlayer 自定义视频播放器
- Android 视频播放器SurfaceView+Mediaplayer
- iOS MediaPlayer(视频播放器) 笔记
- Android MediaPlayer 多媒体(视频播放器)
- Redis Memcached 全方位大比拼
- 视频行为识别年度进展
- Coin Test
- 解决为什么提交spark job 在web上没有进度
- Binder机制
- MediaPlayer封装原生视频播放器
- SOA开发-05 程序发布规范
- 中文文本情感分析:基于机器学习方法的思路
- Spring Boot 入门
- python 编译错误TypeError: 'builtin_function_or_method' object has no attribute '__getitem__'
- spring boot jpa多表查询展示实体的方法
- Camera 和 Camera2
- java通用程序设计的建议(一)
- 安卓开发--左侧或右侧出来的popuwindows替代drawerlayout侧拉菜单