作为一个练手项目,基于IjkMediaPlayer的仿照腾讯视频风格的播放器,支持全屏缩放,对其中核心的代码做记录。
播放器总体架构如图所示:
player是继承frameLayout,自己内部拥有IjkMediaPlayer的实例,以及做渲染显示用的TextureView,同时player只有一个mContainer子控件,mContainer同样也是一个frameLayout容器,容纳TextureView和Controller;Controller也是继承FrameLayout,并作为容器容纳各个控制控件的容器,内部通过接口和player相关联,控制player,同时也根据player的状态改变自己的界面。实现全屏,就是把mContainer先从player中移除掉,然后把mContainer添加到Activity的android.R.id.content根布局下面,然后隐藏导航栏,这样就实现全屏了。
下面是NiceVideoPlayer的主要代码:
1、初始化Container
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);
}
2、添加并关联播放控制器控件
public void setController(BaseVideoPlayerController controller){
mContainer.removeView(mController);
mController = controller;
mController.reset();
controller.setNiceVideoPlayer(this);
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mContainer.addView(mController,params);
}
3、用户点击播放,需要开始准备播放,获取声音焦点
private void initAudioManager(){
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAudioManager.requestAudioFocus(null,AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);
}
4、初始化MediaPlayer,这里分IjkMediaPlayer和SDK自带的MediaPlayer
private void initMediaPlayer(){
if(mMediaPlayer == null){
if(mPlayerType == TYPE_IJK){
mMediaPlayer = new IjkMediaPlayer();
}else if(mPlayerType == TYPE_NATIVE){
mMediaPlayer = new AndroidMediaPlayer();
}
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
}
5、初始化TextureView,并把TextureView添加到mContainer中,注意是添加到最底层
private void initTextureView(){
if(mTextureView == null){
mTextureView = new NiceTextureView(mContext);
mTextureView.setSurfaceTextureListener(this); //这里设置监听,在textureview准备好之后进行下一步
}
}
private void addTextureView(){
mContainer.removeView(mTextureView);
LayoutParams params = new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER);
mContainer.addView(mTextureView,0,params);
}
6、TextureView准备好之后,处理监听回调:
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
if(mSurfaceTexture == null){
Log.i(TAG,"onSurfaceTextureAvailable");
mSurfaceTexture = surfaceTexture;
openMediaPlayer();
}else{
mTextureView.setSurfaceTexture(mSurfaceTexture);
}
}
7、打开播放器,设置播放所必须的监听器,并且关联textureView的Surface,同时设置播放资源:
private void openMediaPlayer(){
//屏幕长量
mContainer.setKeepScreenOn(true);
//设置监听
mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
mMediaPlayer.setOnInfoListener(mOnInfoListener);
mMediaPlayer.setOnErrorListener(mOnErrorListener);
mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
//设置资源
try {
mMediaPlayer.setDataSource(mContext,Uri.parse(mUrl));
if(mSurface == null){
mSurface = new Surface(mSurfaceTexture);
}
mMediaPlayer.setSurface(mSurface);
mMediaPlayer.prepareAsync();
mCurrentState = STATE_PREPARING;
mController.onPlayStateChanged(mCurrentState);
Log.i(TAG,"播放器准备中");
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG,"打开播放器错误",e);
}
}
8、 IMediaPlayer.OnPreparedListener播放器异步准备监听
private IMediaPlayer.OnPreparedListener mOnPreparedListener = new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
mCurrentState = STATE_PREPARED;
mController.onPlayStateChanged(mCurrentState);
iMediaPlayer.start(); //开始播放
}
};
9、IMediaPlayer.OnCompletionListener播放完毕的监听
private IMediaPlayer.OnCompletionListener mOnCompletionListener = new IMediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
mCurrentState = STATE_COMPLETED;
mController.onPlayStateChanged(mCurrentState);
//关闭屏幕长亮
mContainer.setKeepScreenOn(false);
}
};
10、这里主要监听播放器开始在TextureView中渲染的信息
private IMediaPlayer.OnInfoListener mOnInfoListener = new IMediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(IMediaPlayer iMediaPlayer, int what, int extra) {
if(what == IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START){
//播放器开始渲染画面
mCurrentState = STATE_PLAYING;
mController.onPlayStateChanged(mCurrentState);
}
return true;
}
};
11、 IMediaPlayer.OnBufferingUpdateListener 缓存进度改变的信息
private IMediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int i) {
mBufferPercent = i; //此数据可以用来更新缓存进度条
}
};
12、进入全屏的方法:
public void enterFullScreen() {
if(mCurrentMode == MODE_FULL_SCREEN) return;
//隐藏状态栏
NiceUtil.hideActionBar(mContext);
//横屏
NiceUtil.getAppCompatActivity(mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
ViewGroup viewGroup = NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);
if(mCurrentMode == MODE_TINY_WINDOW){
}else{
this.removeView(mContainer);
}
LayoutParams layoutParams = new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
viewGroup.addView(mContainer,layoutParams);
mCurrentMode = MODE_FULL_SCREEN;
mController.onPlayModeChanged(mCurrentMode);
}
13、退出全屏的操作:
public boolean exitFullScreen() {
if(mCurrentMode != MODE_FULL_SCREEN) return false;
ViewGroup viewGroup = NiceUtil.getAppCompatActivity(mContext).findViewById(android.R.id.content);
viewGroup.removeView(mContainer);
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
this.addView(mContainer,params);
//显示状态栏
NiceUtil.ShowActionBar(mContext);
//竖屏
NiceUtil.getAppCompatActivity(mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mCurrentMode = MODE_NORMAL;
mController.onPlayModeChanged(mCurrentMode);
return true;
}
14、重新onBackPress()方法,如果在全屏的时候,退出全屏
public boolean onBackPress(){
if(mCurrentMode == MODE_FULL_SCREEN){
return exitFullScreen();
}
if(mCurrentMode == MODE_TINY_WINDOW){
return exitTinyWindow();
}
return false;
}