wordpress 4.0合肥网站的优化
- 作者: 多梦笔记
- 时间: 2026年02月18日 10:24
当前位置: 首页 > news >正文
wordpress 4.0,合肥网站的优化,wordpress 栏目,c 做网站流程转自用MediaPlayerTextureView封装一个完美实现全屏、小窗口的视频播放器 GitHub 为什么使用TextureView 在Android总播放视频可以直接使用VideoView#xff0c;VideoView是通过继承自SurfaceView来实现的。SurfaceView的大概原理就是在现有View的位置上创建一个新的Window…转自用MediaPlayerTextureView封装一个完美实现全屏、小窗口的视频播放器 GitHub 为什么使用TextureView 在Android总播放视频可以直接使用VideoViewVideoView是通过继承自SurfaceView来实现的。SurfaceView的大概原理就是在现有View的位置上创建一个新的Window内容的显示和渲染都在新的Window中。这使得SurfaceView的绘制和刷新可以在单独的线程中进行从而大大提高效率。但是呢由于SurfaceView的内容没有显示在View中而是显示在新建的Window中 使得SurfaceView的显示不受View的属性控制不能进行平移缩放等变换也不能放在其它RecyclerView或ScrollView中一些View中的特性也无法使用。 TextureView是在4.0(API level 14)引入的与SurfaceView相比它不会创建新的窗口来显示内容。它是将内容流直接投放到View中并且可以和其它普通View一样进行移动旋转缩放动画等变化。TextureView必须在硬件加速的窗口中使用。 TextureView被创建后不能直接使用必须要在它被它添加到ViewGroup后待SurfaceTexture准备就绪才能起作用看TextureView的源码TextureView是在绘制的时候创建的内部SurfaceTexture。通常需要给TextureView设置监听器SurfaceTextuListener mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {// SurfaceTexture准备就绪}Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {// SurfaceTexture缓冲大小变化}Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {// SurfaceTexture即将被销毁return false;}Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {// SurfaceTexture通过updateImage更新} }); SurfaceTexture的准备就绪、大小变化、销毁、更新等状态变化时都会回调相对应的方法。当TextureView内部创建好SurfaceTexture后在监听器的onSurfaceTextureAvailable方法中用SurfaceTexture来关联MediaPlayer作为播放视频的图像数据来源。 SurfaceTexture作为数据通道把从数据源MediaPlayer中获取到的图像帧数据转为GL外部纹理交给TextureVeiw作为View heirachy中的一个硬件加速层来显示从而实现视频播放功能。 MediaPlayer介绍 MediaPlayer是Android原生的多媒体播放器可以用它来实现本地或者在线音视频的播放同时它支持https和rtsp。 MediaPlayer定义了各种状态可以理解为是它的生命周期。 MediaPlayer状态图生命周期 这个状态图描述了MediaPlayer的各种状态以及主要方法调用后的状态变化。 MediaPlayer的相关方法及监听接口 setDataSource 设置数据源 Initialized 、prepare 准备播放同步 Preparing —Prepared start 开始或恢复播放 Started pause 暂停 Paused stop 停止 Stopped seekTo 到指定时间点位置 PrePared/Started reset 重置播放器 Idle setAudioStreamType 设置音频流类型 – setDisplay 设置播放视频的Surface – setVolume 设置声音 – getBufferPercentage 获取缓冲半分比 – getCurrentPosition 获取当前播放位置 – getDuration 获取播放文件总时间 – 内部回调接口 OnPreparedListener 准备监听 Preparing ——PreparedOnVideoSizeChangedListener 视频尺寸变化监听 – OnInfoListener 指示信息和警告信息监听– OnCompletionListener 播放完成监听 PlaybackCompletedOnErrorListener 播放错误监听 Error OnBufferingUpdateListener 缓冲更新监听 – MediaPlayer在直接new出来之后就进入了Idle状态此时可以调用多个重载的setDataSource()方法从idle状态进入Initialized状态如果调用setDataSource()方法的时候MediaPlayer对象不是出于Idle状态会抛异常可以调用reset()方法回到Idle状态。 调用prepared()方法和preparedAsync()方法进入Prepared状态prepared()方法直接进入Parpared状态preparedAsync()方法会先进入PreParing状态播放引擎准备完毕后会通过OnPreparedListener.onPrepared()回调方法通知Prepared状态。 在Prepared状态下就可以调用start()方法进行播放了此时进入started()状态如果播放的是网络资源Started状态下也会自动调用客户端注册的OnBufferingUpdateListener.OnBufferingUpdate()回调方法对流播放缓冲的状态进行追踪。 pause()方法和start()方法是对应的调用pause()方法会进入Paused状态调用start()方法重新进入Started状态继续播放。 stop()方法会使MdiaPlayer从Started、Paused、Prepared、PlaybackCompleted等状态进入到Stoped状态播放停止。 当资源播放完毕时如果调用了setLooping(boolean)方法会自动进入Started状态重新播放如果没有调用则会自动调用客户端播放器注册的OnCompletionListener.OnCompletion()方法此时MediaPlayer进入PlaybackCompleted状态在此状态里可以调用start()方法重新进入Started状态。 封装考虑 MediaPlayer的方法和接口比较多不同的状态调用各个方法后状态变化情况也比较复杂。播放相关的逻辑只与MediaPlayer的播放状态和调用方法相关而界面展示和UI操作很多时候都需要根据自己项目来定制。参考原生的VideoView为了解耦和方便定制把MediaPlayer的播放逻辑和UI界面展示及操作相关的逻辑分离。我是把MediaPlayer直接封装到NiceVideoPlayer中各种UI状态和操作反馈都封装到NiceVideoPlayerController里面。如果需要根据不同的项目需求来修改播放器的功能就只重写NiceVideoPlayerController就可以了。 NiceVideoPlayer 首先需要一个FrameLayout容器mContainer里面有两层内容第一层就是展示播放视频内容的TextureView第二层就是播放器控制器mController。那么自定义一个NiceVideoPlayer继承自FrameLayout,将mContainer添加到当前控件 public class NiceVideoPlayer extends FrameLayout{private Context mContext;private NiceVideoController mController;private FrameLayout mContainer;public NiceVideoPlayer(Context context) {this(context, null);}public NiceVideoPlayer(Context context, AttributeSet attrs) {super(context, attrs);mContext context;init();}private void init() {mContainer new FrameLayout(mContext);mContainer.setBackgroundColor(Color.BLACK);LayoutParams params new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);this.addView(mContainer, params);} } 添加setUp方法来配置播放的视频资源路径本地/网络资源 public void setUp(String url, MapString, String headers) {mUrl url;mHeaders headers;} 用户要在mController中操作才能播放因此需要在播放之前设置好mController: public void setController(NiceVideoPlayerController controller) {mController controller;mController.setNiceVideoPlayer(this);LayoutParams params new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);mContainer.addView(mController, params); } 用户在自定义好自己的控制器后通过setController这个方法设置给播放器进行关联。 触发播放时NiceVideoPlayer将展示视频图像内容的mTextureView添加到mContainer中在mController的下层同时初始化mMediaPlayer待mTextureView的数据通道SurfaceTexture准备就绪后就可以打开播放器 public void start() {initMediaPlayer(); // 初始化播放器initTextureView(); // 初始化展示视频内容的TextureViewaddTextureView(); // 将TextureView添加到容器中 }private void initTextureView() {if (mTextureView null) {mTextureView new TextureView(mContext);mTextureView.setSurfaceTextureListener(this);} }private void addTextureView() {mContainer.removeView(mTextureView);LayoutParams params new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);mContainer.addView(mTextureView, 0, params); }private void initMediaPlayer() {if (mMediaPlayer null) {mMediaPlayer new MediaPlayer();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setScreenOnWhilePlaying(true);mMediaPlayer.setOnPreparedListener(mOnPreparedListener);mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);mMediaPlayer.setOnCompletionListener(mOnCompletionListener);mMediaPlayer.setOnErrorListener(mOnErrorListener);mMediaPlayer.setOnInfoListener(mOnInfoListener);mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);} }Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {// surfaceTexture数据通道准备就绪打开播放器openMediaPlayer(surface); }private void openMediaPlayer(SurfaceTexture surface) {try {mMediaPlayer.setDataSource(mContext.getApplicationContext(), Uri.parse(mUrl), mHeaders);mMediaPlayer.setSurface(new Surface(surface));mMediaPlayer.prepareAsync();} catch (IOException e) {e.printStackTrace();} }Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false; }Override public void onSurfaceTextureUpdated(SurfaceTexture surface) {}打开播放器调用prepareAsync()方法后mMediaPlayer进入准备状态准备就绪后就可以开始 private MediaPlayer.OnPreparedListener mOnPreparedListener new MediaPlayer.OnPreparedListener() {Overridepublic void onPrepared(MediaPlayer mp) {mp.start();} }; NiceVideoPlayer的这些逻辑已经实现视频播放了操作相关以及UI展示的逻辑需要在控制器NiceVideoPlayerController中来实现。但是呢UI的展示和反馈都需要依据播放器当前的播放状态所以需要给播放器定义一些常量来表示它的播放状态 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; // 播放完成 播放视频时mMediaPlayer准备就绪(Prepared)后没有马上进入播放状态中间有一个时间延迟时间段然后开始渲染图像。所以将Prepared——“开始渲染”中间这个时间段定义为STATE_PREPARED。 如果是播放网络视频在播放过程中缓冲区数据不足时mMediaPlayer内部会停留在某一帧画面以进行缓冲。正在缓冲时mMediaPlayer可能是在正在播放也可能是暂停状态因为在缓冲时如果用户主动点击了暂停就是处于STATE_BUFFERING_PAUSED所以缓冲有STATE_BUFFERING_PLAYING和STATE_BUFFERING_PAUSED两种状态缓冲结束后恢复播放或暂停。 private MediaPlayer.OnPreparedListener mOnPreparedListener new MediaPlayer.OnPreparedListener() {Overridepublic void onPrepared(MediaPlayer mp) {mp.start();mCurrentState STATE_PREPARED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(onPrepared —— STATE_PREPARED);} };private MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener new MediaPlayer.OnVideoSizeChangedListener() {Overridepublic void onVideoSizeChanged(MediaPlayer mp, int width, int height) {LogUtil.d(onVideoSizeChanged —— width width height height);} };private MediaPlayer.OnCompletionListener mOnCompletionListener new MediaPlayer.OnCompletionListener() {Overridepublic void onCompletion(MediaPlayer mp) {mCurrentState STATE_COMPLETED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(onCompletion —— STATE_COMPLETED);} };private MediaPlayer.OnErrorListener mOnErrorListener new MediaPlayer.OnErrorListener() {Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {mCurrentState STATE_ERROR;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(onError —— STATE_ERROR ———— what what);return false;} };private MediaPlayer.OnInfoListener mOnInfoListener new MediaPlayer.OnInfoListener() {Overridepublic boolean onInfo(MediaPlayer mp, int what, int extra) {if (what MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {// 播放器渲染第一帧mCurrentState STATE_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(onInfo —— MEDIA_INFO_VIDEO_RENDERING_STARTSTATE_PLAYING);} else if (what MediaPlayer.MEDIA_INFO_BUFFERING_START) {// MediaPlayer暂时不播放以缓冲更多的数据if (mCurrentState STATE_PAUSED || mCurrentState STATE_BUFFERING_PAUSED) {mCurrentState STATE_BUFFERING_PAUSED;LogUtil.d(onInfo —— MEDIA_INFO_BUFFERING_STARTSTATE_BUFFERING_PAUSED);} else {mCurrentState STATE_BUFFERING_PLAYING;LogUtil.d(onInfo —— MEDIA_INFO_BUFFERING_STARTSTATE_BUFFERING_PLAYING);}mController.setControllerState(mPlayerState, mCurrentState);} else if (what MediaPlayer.MEDIA_INFO_BUFFERING_END) {// 填充缓冲区后MediaPlayer恢复播放/暂停if (mCurrentState STATE_BUFFERING_PLAYING) {mCurrentState STATE_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(onInfo —— MEDIA_INFO_BUFFERING_END STATE_PLAYING);}if (mCurrentState STATE_BUFFERING_PAUSED) {mCurrentState STATE_PAUSED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(onInfo —— MEDIA_INFO_BUFFERING_END STATE_PAUSED);}} else {LogUtil.d(onInfo —— what what);}return true;} };private MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener new MediaPlayer.OnBufferingUpdateListener() {Overridepublic void onBufferingUpdate(MediaPlayer mp, int percent) {mBufferPercentage percent;} };mController.setControllerState(mPlayerState, mCurrentState)mCurrentState表示当前播放状态mPlayerState表示播放器的全屏、小窗口正常三种状态。 public static final int PLAYER_NORMAL 10; // 普通播放器 public static final int PLAYER_FULL_SCREEN 11; // 全屏播放器 public static final int PLAYER_TINY_WINDOW 12; // 小窗口播放器定义好播放状态后开始暂停等操作逻辑也需要根据播放状态调整 Override public void start() {if (mCurrentState STATE_IDLE|| mCurrentState STATE_ERROR|| mCurrentState STATE_COMPLETED) {initMediaPlayer();initTextureView();addTextureView();} }Override public void restart() {if (mCurrentState STATE_PAUSED) {mMediaPlayer.start();mCurrentState STATE_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(STATE_PLAYING);}if (mCurrentState STATE_BUFFERING_PAUSED) {mMediaPlayer.start();mCurrentState STATE_BUFFERING_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(STATE_BUFFERING_PLAYING);} }Override public void pause() {if (mCurrentState STATE_PLAYING) {mMediaPlayer.pause();mCurrentState STATE_PAUSED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(STATE_PAUSED);}if (mCurrentState STATE_BUFFERING_PLAYING) {mMediaPlayer.pause();mCurrentState STATE_BUFFERING_PAUSED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(STATE_BUFFERING_PAUSED);} } reStart()方法是暂停时继续播放调用。 全屏、小窗口播放的实现 可能最能想到实现全屏的方式就是把当前播放器的宽高给放大到屏幕大小同时隐藏除播放器以外的其他所有UI并设置成横屏模式。但是这种方式有很多问题比如在列表ListView或RecyclerView中除了放大隐藏外还需要去计算滑动多少距离才刚好让播放器与屏幕边缘重合退出全屏的时候还需要滑动到之前的位置这样实现逻辑不但繁琐而且和外部UI偶合严重后面改动维护起来非常困难我曾经就用这种方式被坑了无数道。 分析能不能有其他更好的实现方式呢 整个播放器由mMediaPalyermTexutureViewmController组成要实现全屏或小窗口播放我们只需要挪动播放器的展示界面mTexutureView和控制界面mController即可。并且呢我们在上面定义播放器时已经把mTexutureView和mController一起添加到mContainer中了所以只需要将mContainer从当前视图中移除并添加到全屏和小窗口的目标视图中即可。 那么怎么确定全屏和小窗口的目标视图呢 我们知道每个Activity里面都有一个android.R.content它是一个FrameLayout里面包含了我们setContentView的所有控件。既然它是一个FrameLayout我们就可以将它作为全屏和小窗口的目标视图。 我们把从当前视图移除的mContainer重新添加到android.R.content中并且设置成横屏。这个时候还需要注意android.R.content是不包括ActionBar和状态栏的所以要将Activity设置成全屏模式同时隐藏ActionBar。 Override public void enterFullScreen() {if (mPlayerState PLAYER_FULL_SCREEN) return;// 隐藏ActionBar、状态栏并横屏NiceUtil.hideActionBar(mContext);NiceUtil.scanForActivity(mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);this.removeView(mContainer);ViewGroup contentView (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);LayoutParams params new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);contentView.addView(mContainer, params);mPlayerState PLAYER_FULL_SCREEN;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(PLAYER_FULL_SCREEN); }退出全屏也就很简单了将mContainer从android.R.content中移除重新添加到当前视图并恢复ActionBar、清除全屏模式就行了。 public boolean exitFullScreen() {if (mPlayerState PLAYER_FULL_SCREEN) {NiceUtil.showActionBar(mContext);NiceUtil.scanForActivity(mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);ViewGroup contentView (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);contentView.removeView(mContainer);LayoutParams params new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);this.addView(mContainer, params);mPlayerState PLAYER_NORMAL;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(PLAYER_NORMAL);return true;}return false; }切换横竖屏时为了避免Activity重新走生命周期别忘了需要在Manifest.xml的activity标签下添加如下配置 android:configChanges”orientation|keyboardHidden|screenSize” 进入小窗口播放和退出小窗口的实现原理就和全屏功能一样了只需要修改它的宽高参数 Override public void enterTinyWindow() {if (mPlayerState PLAYER_TINY_WINDOW) return;this.removeView(mContainer);ViewGroup contentView (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);// 小窗口的宽度为屏幕宽度的60%长宽比默认为16:9右边距、下边距为8dp。FrameLayout.LayoutParams params new FrameLayout.LayoutParams((int) (NiceUtil.getScreenWidth(mContext) * 0.6f),(int) (NiceUtil.getScreenWidth(mContext) * 0.6f * 9f / 16f));params.gravity Gravity.BOTTOM | Gravity.END;params.rightMargin NiceUtil.dp2px(mContext, 8f);params.bottomMargin NiceUtil.dp2px(mContext, 8f);contentView.addView(mContainer, params);mPlayerState PLAYER_TINY_WINDOW;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(PLAYER_TINY_WINDOW); }Override public boolean exitTinyWindow() {if (mPlayerState PLAYER_TINY_WINDOW) {ViewGroup contentView (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);contentView.removeView(mContainer);LayoutParams params new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);this.addView(mContainer, params);mPlayerState PLAYER_NORMAL;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(PLAYER_NORMAL);return true;}return false; }这里有个特别需要注意的一点 当mContainer移除重新添加后mContainer及其内部的mTextureView和mController都会重绘mTextureView重绘后会重新new一个SurfaceTexture并重新回调onSurfaceTextureAvailable方法这样mTextureView的数据通道SurfaceTexture发生了变化但是mMediaPlayer还是持有原先的mSurfaceTexut所以在切换全屏之前要保存之前的mSufaceTexture当切换到全屏后重新调用onSurfaceTextureAvailable时将之前的mSufaceTexture重新设置给mTexutureView。这样就保证了切换时视频播放的无缝衔接。 Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {if (mSurfaceTexture null) {mSurfaceTexture surfaceTexture;openMediaPlayer();} else {mTextureView.setSurfaceTexture(mSurfaceTexture);} } NiceVideoPlayerControl 为了解除NiceVideoPlayer和NiceVideoPlayerController的耦合把NiceVideoPlayer的一些功能性和判断性方法抽象到NiceVideoPlayerControl接口中。 public interface NiceVideoPlayerControl {void start();void restart();void pause();void seekTo(int pos);boolean isIdle();boolean isPreparing();boolean isPrepared();boolean isBufferingPlaying();boolean isBufferingPaused();boolean isPlaying();boolean isPaused();boolean isError();boolean isCompleted();boolean isFullScreen();boolean isTinyWindow();boolean isNormal();int getDuration();int getCurrentPosition();int getBufferPercentage();void enterFullScreen();boolean exitFullScreen();void enterTinyWindow();boolean exitTinyWindow();void release(); } NiceVideoPlayer实现这个接口即可。 NiceVideoPlayerManager 同一界面上有多个视频或者视频放在ReclerView或者ListView的容器中要保证同一时刻只有一个视频在播放其他的都是初始状态所以需要一个NiceVideoPlayerManager来管理播放器主要功能是保存当前已经开始了的播放器。 public class NiceVideoPlayerManager {private NiceVideoPlayer mVideoPlayer;private NiceVideoPlayerManager() {}private static NiceVideoPlayerManager sInstance;public static synchronized NiceVideoPlayerManager instance() {if (sInstance null) {sInstance new NiceVideoPlayerManager();}return sInstance;}public void setCurrentNiceVideoPlayer(NiceVideoPlayer videoPlayer) {mVideoPlayer videoPlayer;}public void releaseNiceVideoPlayer() {if (mVideoPlayer ! null) {mVideoPlayer.release();mVideoPlayer null;}}public boolean onBackPressd() {if (mVideoPlayer ! null) {if (mVideoPlayer.isFullScreen()) {return mVideoPlayer.exitFullScreen();} else if (mVideoPlayer.isTinyWindow()) {return mVideoPlayer.exitTinyWindow();} else {mVideoPlayer.release();return false;}}return false;} }采用单例同时onBackPressed供Activity中用户按返回键时调用。 NiceVideoPlayer的start方法以及onCompleted需要修改一下保证开始播放一个视频时要先释放掉之前的播放器同时自己播放完毕要将NiceVideoPlayerManager中的mNiceVideoPlayer实例置空避免内存泄露。 // NiceVideoPlayer的start()方法。 Override public void start() {NiceVideoPlayerManager.instance().releaseNiceVideoPlayer();NiceVideoPlayerManager.instance().setCurrentNiceVideoPlayer(this);if (mCurrentState STATE_IDLE|| mCurrentState STATE_ERROR|| mCurrentState STATE_COMPLETED) {initMediaPlayer();initTextureView();addTextureView();} }// NiceVideoPlayer中的onCompleted监听。 private MediaPlayer.OnCompletionListener mOnCompletionListener new MediaPlayer.OnCompletionListener() {Overridepublic void onCompletion(MediaPlayer mp) {mCurrentState STATE_COMPLETED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d(onCompletion —— STATE_COMPLETED);NiceVideoPlayerManager.instance().setCurrentNiceVideoPlayer(null);} }; NiceVideoPlayerController 播放控制界面上播放、暂停、播放进度、缓冲动画、全屏/小屏等触发都是直接调用播放器对应的操作的。需要注意的就是调用之前要判断当前的播放状态因为有些状态下调用播放器的操作可能引起错误比如播放器还没准备就绪就去获取当前的播放位置。 播放器在触发相应功能的时候都会调用NiceVideoPlayerController的setControllerState(int playerState, int playState)这个方法来让用户修改UI。 不同项目都可能定制不同的控制器播放操作界面这里我就不详细分析实现逻辑了大致功能就类似腾讯视频的热点列表中的播放器。其中横向滑动改变播放进度、左侧上下滑动改变亮度右侧上下滑动改变亮度等功能在代码中都有实现。代码有点长就不贴了需要的直接下载源码。 使用 mNiceVideoPlayer.setUp(url, null); NiceVideoPlayerController controller new NiceVideoPlayerController(this); controller.setTitle(title); controller.setImage(imageUrl); mNiceVideoPlayer.setController(controller);在RecyclerView或者ListView中使用时需要监听itemView的detached mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {Overridepublic void onChildViewAttachedToWindow(View view) {}Overridepublic void onChildViewDetachedFromWindow(View view) {NiceVideoPlayer niceVideoPlayer (NiceVideoPlayer) view.findViewById(R.id.nice_video_player);if (niceVideoPlayer ! null) {niceVideoPlayer.release();}} }); 在ItemViewdetach窗口时需要释放掉itemView内部的播放器。 效果图 最后 整个功能有参考节操播放器但是自己这样封装和节操播放器还是有很大差异一是分离了播放功能和控制界面定制只需修改控制器即可。二是全屏/小窗口没有新建一个播放器只是挪动了播放界面和控制器不用每个视频都需要新建两个播放器也不用同步状态。 MediaPlayer有很多格式不支持项目已添加IjkPlayer的扩展支持可以切换IjkPlayer和原生MediaPlayer后续还会考虑添加ExoPlayer同时也会扩展更多功能。
相关文章
-
wordpress 4.0 多站点深圳市做物流网站
wordpress 4.0 多站点深圳市做物流网站
- 站长
- 2026年02月18日
-
wix做网站网站互动栏目设置
wix做网站网站互动栏目设置
- 站长
- 2026年02月18日
-
winserverfrp可以做网站吗学软件工程培训就业机构
winserverfrp可以做网站吗学软件工程培训就业机构
- 站长
- 2026年02月18日
-
wordpress 4.7 多站点成都建工网站
wordpress 4.7 多站点成都建工网站
- 站长
- 2026年02月18日
-
WordPress 08影院源码镇江网站关键字优化如何
WordPress 08影院源码镇江网站关键字优化如何
- 站长
- 2026年02月18日
-
wordpress 2016清远市企业网站seo
wordpress 2016清远市企业网站seo
- 站长
- 2026年02月18日
