前言上一篇我们介绍了SurfaceView和TextureView的基础知识点;SurfaceView和TextureView都继承自android.view.View,它是Android提供的控制系统的一部分。与普通视图不同,它们都是在单独的线程中绘制和渲染的。因此,相对于普通的ImageView,它们具有更高的性能,所以常用于对绘制速率要求比较高的应用场景,解决普通View由于绘制的时间延迟导致的掉帧问题。例如作为相机预览、视频播放的媒介;今天我们就简单的用TextureView封装视频播放器;一、视频播放器方案介绍1、videoView+mediaPlayervideoView继承自SurfaceView。SurfaceView是在已有的View上创建一个新的Window,内容的显示和渲染都在新的Window中,这样就可以让SurfaceView的绘制和刷新在一个单独的线程中进行。由于SurfaceView的内容在新创建的Window中,SurfaceView不能放在RecyclerView或ScrollView中,View中的一些特性也不能使用。2、textureView+mediaPlayertextureView不会新建窗口,使用方法和其他普通View一样。考虑到以后的可扩展性,最终采用了这个方案3、为什么使用TextureViewTextureView是在4.0(APIlevel14)引入的。与SurfaceView相比,它不会创建新的窗口来显示内容。它将内容流直接放入View中,可以像其他普通View一样移动、旋转、缩放、动画等。TextureView必须在硬件加速窗口中使用。二、TextureView的使用介绍1、TextureView创建后不能直接使用。它必须添加到ViewGroup。2、TextureView必须等待SurfaceTexture准备好才能工作。这里,通常需要为TextureView设置一个监听器SurfaceTextureListener。等待onSurfaceTextureAvailable回调后才能使用。3.创建并初始化TextureView//初始化一个TextureView并添加到ViewGroup或者找到你的TextureView组件mTextureView=newTextureView(getContext());//设置画布监听textureView.setSurfaceTextureListener(this);//添加到layoutfragment.addView(textureView,newLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT,Gravity.CENTER));/***TextureView准备回调*@paramsurface内画布渲染面*@paramwidthTextureView布局宽度*@paramheightTextureView布局高度*/@OverridepublicvoidonSurfaceTextureAvailable(SurfaceTexturesurface,intwidth,intheight){Logger.d(TAG,"onSurfaceTextureAvailable-->width:"+width+",height:"+height);处理完之后,声明一个mSurfaceTexture,更新if(mSurfaceTexture==null){mSurfaceTexture=surface;//prepare();}else{mTextureView.setSurfaceTexture(mSurfaceTexture);}}/***TextureView时的TextureView宽高changes发生变化时的回调*@paramsurfaceinternalsurface*@paramwidthnewTextureView布局宽度*@paramheightnewTextureView布局高度*/@OverridepublicvoidonSurfaceTextureSizeChanged(SurfaceTexturesurface,intwidth,intheight){Logger.d(TAG,"onSurfaceTextureSizeChanged-->width:"+width+",height:"+height);}/***TextureView销毁时的回调*@paramsurfaceinternalsurface*@returnMostapplicationsshouldreturntrue。*/@OverridepublicbooleanonSurfaceTextureDestroyed(SurfaceTexturesurface){Logger.d(TAG,"onSurfaceTextureDestroyed");returnull==mSurfaceTexture;}/***TextureView回调*@paramsurfaceinternalsurface*/@OverridepublicvoidonSurfaceTextureUpdated(SurfaceTexturesurface){}3.MediaPlayer简介1、重要状态idle:空闲状态当mediaPlayer没有prepareAsync时,处于空闲状态。prepared:就绪状态。如果想让mediaPlayer开始播放,不能直接启动,必须??先prepareSync。在此期间,mediaPlayer会进行preparation准备,直到进入prepared状态。started:当mediaPlayer就绪后,可以调用mediaPlayer的start方法进入started状态。paused:当调用pause方法时,进入暂停状态。completed:播放完成,进入completed状态。错误:播放错误。2、重要方法prepareAsync:要使用mediaPlayer,必须先调用prepareAsync。这是第一步。start:开始pause:暂停reset:播放完成后如果想重新开始,调用该方法。3、重要的回调onSurfaceTextureAvailable:开始关联mediaPlayeronPrepared:这里开始调用mediaPlayer.start()onInfo:播放开始后,视频到底是什么状态,在onInfo处理@OverridepublicbooleanonInfo(MediaPlayermp,intwhat,intextra){if(what==MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START){//播放器渲染第一帧如果(mCurrentState==STATE_PAUSED||mCurrentState==STATE_BUFFERING_PAUSED){mCurrentState=STATE_BUFFERING_PAUSED;}else{mCurrentState=STATE_BUFFERING_PLAYING;}mController.onPlayStateChanged(mCurrentState);}elseif(what==MediaPlayer.MEDIA_INFO_BUFFERING/缓冲区填充后),MediaPlayer恢复播放/暂停if(mCurrentState==STATE_BUFFERING_PLAYING){mCurrentState=STATE_PLAYING;mController.onPlayStateChanged(mCurrent状态);}if(mCurrentState==STATE_BUFFERING_PAUSED){mCurrentState=STATE_PAUSED;mController.onPlayStateChanged(mCurrentState);}}else{LogUtil.d("onInfo——>what:"+what);}returntrue;}4.MediaPlayer初始化完毕,准备播放mMediaPlayer.setOnPreparedListener(this);//...这里省略一系列监听设置//异步准备mMediaPlayer.prepareAsync();/***播放器准备就绪*@parammp解码器*/@OverridepublicvoidonPrepared(MediaPlayermp){Logger.d(TAG,"onPrepared");if(null!=mSurfaceTexture){if(null!=mSurface){mSurface.release();mSurface=null;}mSurface=newSurface(mSurfaceTexture);mp.setSurface(mSurface);}//开始播放mp.start();}4.封装视频播放器1.封装播放器。视频播放控件应该包含两层:顶层是播放器控制器mController,底层是播放视频内容的TextureView这里将这两层封装在一个容器FrameLayout中;publicVideoPlayer(@NonNullContextcontext,@NullableAttributeSetattrs){super(context,attrs);mContext=context;if(mNetworkChangeReceiver==null){mNetworkChangeReceiver=newNetworkChangeReceiver(this);}allow4GFlag=false;init();}privatevoidinit(){mContainer=newFrameLayout(mContext);mContainer.setBackgroundColor(Color.BLACK);LayoutParamsparams=newLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);this.addView(mContainer,params);}addTextureViewprivatevoidaddTextureView(){mContainer.removeView(mTextureView);LayoutParamsparams=newLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT,Gravity.CENTER);mContainer.addView(mTextureView,0,params);}setControllerpublicvoidsetController(IVideoControllercontroller){mContainer.removeView(mController);mController=controller;mController.reset();mController.setVideoPlayer(this);LayoutParamsparams=newLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);mContainer.addView(mController,params);}播放、初始化TextureView、MediaPlayer、ControllerTextureView的数据通道SurfaceTexture准备好后,打开播放器privatevoidopenMediaPlayer(){//屏幕常亮mContainer.setKeepScreenOn(true);//设置监听mMediaPlayer.setOnPreparedListener(this);mMediaPlayer.setOnVideoSizeChangedListener(this);mMediaPlayer.setOnCompletionListener(this);mMediaPlayer.setOnErrorListener(this);mMediaPlayer.setOnInfoListener(this);mMediaPlayer.setOnBufferingUpdateListener(this);mCurrentNetworkState=NetworkChangeReceiver.getNetworkStatus(CtripBaseApplication.getInstance());.setDataSource(mUrl);如果(mSurface==null){mSurface=newSurface(mSurfaceTexture);}mMediaPlayer.setSurface(mSurface);mMediaPlayer.prepareAsync();mCurrentState=STATE_PREPARING;mController.onPlayStateChanged(mCurrentState);}catch(IOExceptione){e.printStackTrace();LogUtil.e("打开播放器时出错",e);}}privatevoidinitMediaPlayer(){if(mMediaPlayer==null){mMediaPlayer=newMediaPlayer();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);}}privatevoidinitTextureView(){if(mTextureView==null){mTextureView=newTourTextureView(mContext);mTextureView.setSurfaceTextureListener(this);//此时回调onSurfaceTextureAvailable}}@OverridepublicvoidonSurfaceTextureAvailable(SurfaceTexturesurface,inTexture,inti1){if(mSurfaceTexture==null){mSurfaceTexture=surfaceTexture;openMediaPlayer();}else{mTextureView.setSurfaceTexture(mSurfaceTexture);}}播放逻辑写完后,具体的UI显示逻辑在VideoPlayerControllerVideoPlayerController根据不同状态显示不同UIpublicstaticfinalintSTATE_ERROR=-1;//播放错误publicstaticfinalintSTATE_IDLE=0;//播放未开始publicstaticfinalintSTATE_PREPARING=1;//播放准备publicstaticfinalintSTATE_PREPARED=2;//播放准备publicstaticfinalintSTATE_PLAYING=3;//播放中准备好播放publicStaticFinalIntState_paused=4;//uppulanStaticFinalIntState_buffering_playing=5;/////////////////////////////////////////////////////////////////////////////////////////////////////////////////-断netpublicstaticfinalintMODE_NORMAL=10;//普通模式publicstaticfinalintMODE_FULL_SCREEN=11;//全屏模式publicstaticfinalintMODE_TINY_WINDOW=13;//小窗模式2、全屏、小窗播放实现全屏:去掉mContainer,加入android.R.内容,并设置为横屏@OverridepublicvoidenterFullScreen(){if(mCurrentMode==MODE_FULL_SCREEN)return;//隐藏ActionBar、状态栏、横屏TourVideoUtil.hideActionBar(mContext);TourVideoUtil.scanForActivity(mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);newHandler().post(newRunnable(){@Overridepublicvoidrun(){ViewGroupcontentView=(ViewGroup)TourVideoUtil.scanForActivity(mContext).findViewById(android.R.id.content);if(mCurrentMode==MODE_TINY_WINDOW){contentView.removeView(mContainer);}else{TourVideoPlayer.this.removeView(mContainer);}LayoutParamsparams=newLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);contentView.addView(mContainer,params);}});mCurrentMode=MODE_FULL_SCREEN;mController.onPlayModeChanged(mCurrentMode);}实现小窗:去掉mContainer,添加到android.R.content中,设置宽高@OverridepublicvoidenterTinyWindow(){if(mCurrentMode==MODE_TINY_WINDOW)return;this.removeView(mContainer);newHandler().post(newRunnable(){@Overridepublicvoidrun(){ViewGroupcontentView=(ViewGroup)TourVideoUtil.scanForActivity(mContext).findViewById(android.R.id.content);//小窗的宽度为屏幕宽度的60%,默认纵横比为16:9,右侧底部边距为8dpFrameLayout.LayoutParamsparams=newFrameLayout.LayoutParams((int)(CommonUtil.getScreenWidth(mContext)*0.6f),(int)(CommonUtil.getScreenWidth(mContext)*0.6f*9f/16f));params.gravity=重力。TOP|Gravity.START;params.topMargin=CommonUtil.dp2px(mContext,48f);contentView.addView(mContainer,params);}});mCurrentMode=MODE_TINY_WINDOW;mController.onPlayModeChanged(mCurrentMode);}视频播放包总结知识点还是很多的。今天的知识简单介绍一下打包的步骤和思路;如果要自己打包,可以参考网上的NiceVieoPlayer;以后我会继续讲解关于视频播放器的知识点;本文转载自微信♂《Android开发编程》
