具体集成不作赘述
vitamio集成注意
1、4.2版本只需要倒入vitamio文件夹作lib,不过4.2版本有漏洞
2、minsdkversion-15小了报错
3、集成使用之后,第一次启动会出一个原生安卓暗色的加载界面(vitamio初始化)影响用户体验 得去掉
4、直接vedioview使用容易造成内存泄漏(我没用过)建议使用surfaceview+mediaplay播放
5、播放界面的长宽得设计好,太高画面会变形。我现在是固定200dp,得找个方式自适应比例
xml布局
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<io.vov.vitamio.widget.CenterLayout
android:id="@+id/surface_container"
android:background="@android:color/black"
android:layout_width="match_parent"
android:layout_height="@dimen/vedio_height"
android:orientation="vertical" >
<SurfaceView
android:id="@+id/surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" >
</SurfaceView>
</io.vov.vitamio.widget.CenterLayout>
<ImageView
android:id="@+id/first_frame"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="@dimen/vedio_height" />
<ImageView
android:layout_centerInParent="true"
android:src="@drawable/mp_play_normal"
android:alpha="0.7"
android:scaleType="fitCenter"
android:layout_width="@dimen/vedio_center_play_button"
android:layout_height="@dimen/vedio_center_play_button"
android:id="@+id/play" />
<RelativeLayout
android:alpha="0.7"
android:layout_width="match_parent"
android:layout_height="@dimen/vedio_height"
android:id="@+id/control_ui">
<ImageView
android:layout_margin="@dimen/vedio_button_margin"
android:src="@drawable/mp_back_normal"
android:layout_width="@dimen/vedio_back_size"
android:layout_height="@dimen/vedio_back_size"
android:id="@+id/vedio_back" />
<LinearLayout
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:background="#50f5f5f5"
android:gravity="center_vertical"
android:layout_height="@dimen/vedio_bottom_actionbar_size">
<android.support.v7.widget.AppCompatSeekBar
android:layout_width="0dp"
android:layout_weight="1"
style="@style/Widget.AppCompat.SeekBar"
android:layout_height="wrap_content"
android:id="@+id/vedio_seekbar" />
<TextView
android:layout_marginLeft="@dimen/button_margin"
android:layout_marginRight="@dimen/button_margin"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/vedio_fulltime" />
<ImageView
android:id="@+id/full_player"
android:layout_margin="@dimen/button_margin"
android:src="@drawable/mp_square_fullscreen_normal"
android:layout_width="20dp"
android:layout_height="20dp" />
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
vitamio初始化&播放初始化
isInitialized方法的调用必须要放在setcontentview之前
Vitamio.isInitialized(this);
setContentView(R.layout.activity_video_play);
SurfaceView播放初始化/* * 初始化播放 & ui初始 * */ private void initVatmioPlayer(){ mPreview = (SurfaceView) findViewById(R.id.surface); mPreviewContainer = (CenterLayout) findViewById(R.id.surface_container); holder = mPreview.getHolder(); holder.addCallback(this); holder.setFormat(PixelFormat.RGBA_8888); path= Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download/super.mp4";///Download Log.e("path2",path); if (!FileUtils.isFileExists(path)) new AlertDialog.Builder(VideoPlay.this).setMessage("当前文件路径\n"+path+"\n设置失败!").show(); setPlayURI(path); }
播放前的准备/* * 播放前的工作 * */ private void playVideo() { doCleanUp();//释放 try { // Create a new media player and set the listeners mMediaPlayer = new MediaPlayer(); Log.e("load","load"); mMediaPlayer.setDataSource(path);//资源 Log.e("load","load2"); mMediaPlayer.setDisplay(holder); Log.e("load","load3"); mMediaPlayer.prepareAsync(); Log.e("load","load4"); mMediaPlayer.setOnBufferingUpdateListener(this); Log.e("load","load5"); mMediaPlayer.setOnCompletionListener(this);//播放完成 Log.e("load","load6"); mMediaPlayer.setOnPreparedListener(this); Log.e("load","load7"); mMediaPlayer.setOnVideoSizeChangedListener(this); Log.e("load","load8"); setVolumeControlStream(AudioManager.STREAM_MUSIC);//声音 Log.e("load","load9"); } catch (Exception e) { Log.e(TAG, "error: " + e.getMessage(), e); } }
prepareAsync播放/* * prepareAsync * */ public void onPrepared(MediaPlayer mediaplayer) { Log.e(TAG, "onPrepared called"); mIsVideoReadyToBePlayed = true; if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown) { startVideoPlayback();//播放 } initVedioMsg();//更新视频信息 }
使用mediaplayer控制播放/* * 播放 * */ private void startVideoPlayback() { Log.e(TAG, "startVideoPlayback"); holder.setFixedSize(mVideoWidth, mVideoHeight); MediaHelper.setSurfaceViewMeasure(mPreviewContainer,mPreview, mMediaPlayer.getVideoWidth(),mMediaPlayer.getVideoHeight()); //如果是继续播放则继续 return if (CONTINUE_PLAY){ continuePlay((int)currentPosition); return; } //如果正在播放 return if (!START_PLAY) return; mMediaPlayer.start(); }
播放联动ui的控制,在播放开始之后设置播放按钮的状态以及操作栏的显示/隐藏
操作栏控制private Handler hideControlUI=new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case HIDE_PLAY_ACTION: setPlayBtnStatus(0); break; case HIDE_CONTROL_ACTION: setControlUI(0); break; default: Log.e(TAG, "handleMessage - empty msg!"); break; } super.handleMessage(msg); } };
播放按钮/* * 设置上下的ui显示状态 * */ private void setControlUI(int visible){ switch (visible){ case 0://invisile controlUI.setVisibility(View.INVISIBLE); break; case 1://visible controlUI.setVisibility(View.VISIBLE); break; default: break; } }
/* * 设置中间播放按钮的状态 * */ private void setPlayBtnStatus(int visible){ switch (visible){ case 0://invisile play.setVisibility(View.INVISIBLE); removeFirstFrame();//移除第一贞(后文有详细赘述) break; case 1://visible play.setVisibility(View.VISIBLE); break; default: break; } //改变播放按钮状态的同时该变控制UI的状态 setControlUI(visible); }
视频信息
每1s更新一次
private void upDatePlayingTimeInfo(){
Log.e(TAG, "update vedio info timer called!" );
mTimer = new Timer();
mTimer.schedule(new playingTimeUpdate(),0,1000);
}
timetask
/*
* timer 更新进度条以及时长倒计时
* */
class playingTimeUpdate extends TimerTask{
@Override
public void run() {
//seekbar在拖动,不在播放中,对象为空都不更新进度(用户体验)
if(SEEKBAR_IN_MOVING==true
|START_PLAY==false|mMediaPlayer==null)
return;
progressCahnge();
timeTextChange();
}
private void progressCahnge(){
//防止进度条后跳
if (vedioSeekbar.getProgress()<currentPosition)
vedioSeekbar.setProgress((int)mMediaPlayer.getCurrentPosition());
currentPosition=mMediaPlayer.getCurrentPosition();
}
private void timeTextChange(){
//倒计时
int leftDuration = (int)mMediaPlayer.getDuration() - (int)mMediaPlayer.getCurrentPosition();
if (vedioFullTime!=null&&leftDuration>=0)
updateTimeText.sendEmptyMessage(leftDuration);
}
}
工具
大多数视频app都会在列表界面有个第一贞预览的小细节,我的实现方法是在视频未开始播放时默认在surfaceview上一层添加一个与surfaceview一样大小的imageview。将获取到的第一贞的bitmap设置上imageview,以达到一贞预览的效果。
public static Bitmap createVideoThumbnail(String filePath) {
// MediaMetadataRetriever is available on API Level 8
// but is hidden until API Level 10
try {
MediaMetadataRetriever media = new MediaMetadataRetriever();
media.setDataSource(filePath);
Bitmap bitmap = media.getFrameAtTime();
if (bitmap != null)
Log.e(TAG, "createVideoThumbnail - media.getFrameAtTime() success!");
return bitmap;
}catch (IllegalArgumentException ex){
Log.e(TAG, "createVideoThumbnail: ", ex);
}
return null;
}
在视频播放的时候,因为源视频的长宽都是未知的,而且xml中surfaceview大小不一,不一定能恰好匹配上视频的大小。所以这时候就需要根据surface大小及视频大小来缩放视频,达到正常的播放状态
//计算缩放比例
public static void setSurfaceViewMeasure(CenterLayout container, SurfaceView surfaceView,float videoWith,float videoHeight){
if (container==null||surfaceView==null)
return;
Log.e(TAG, "setSurfaceViewMeasure: "+videoWith+"/"+videoHeight);
int VIDEO_TYPE=0;
if (videoWith>videoHeight) VIDEO_TYPE=0;//横条型
if (videoWith<videoHeight) VIDEO_TYPE=1;//竖条型
if (videoWith==videoHeight) VIDEO_TYPE=2;//方型
int measuredWidth = container.getMeasuredWidth();
int measuredHeight = container.getMeasuredHeight();
float scale=videoWith/videoHeight;
Log.e(TAG, "setSurfaceViewMeasure: scale = "+(scale) );
Log.e(TAG, "setSurfaceViewMeasure: measuredWidth="+measuredWidth+" measuredHeight="+measuredHeight );
float surfaceWith=0;
float surfaceHeight=0;
switch (VIDEO_TYPE){
case 0://以宽为准
//scale=measuredWidth/videoWith;
surfaceWith = measuredWidth;
surfaceHeight = surfaceWith/scale;
break;
case 1://以高为准
//scale=measuredHeight/videoHeight;
surfaceHeight = measuredHeight;
surfaceWith = surfaceHeight*scale;
break;
case 2://以高为准
//scale=measuredHeight/videoHeight;
surfaceHeight = measuredHeight;
surfaceWith = surfaceHeight*scale;
break;
}
Log.e(TAG, "setSurfaceViewMeasure: videoWith="+surfaceWith+" videoHeight="+surfaceHeight);
if (surfaceWith==0|surfaceHeight==0)
return;
surfaceView.setLayoutParams(new CenterLayout.LayoutParams((int)surfaceWith,(int)surfaceHeight,0,0));
}
//计算时间
public static String formatDuring(long mss,boolean withOutHours) {
long hours = (mss % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60);
long minutes = (mss % (1000 * 60 * 60)) / (1000 * 60);
long seconds = (mss % (1000 * 60)) / 1000;
String hour = "";
if(hours < 10) {
hour = "0" + hours;
}else{
hour = hours+"";
}
String minute = "";
if(minutes < 10) {
minute = "0" + minutes;
}else{
minute = hours+"";
}
String second = "";
if(seconds < 10) {
second = "0" + seconds;
}else{
second = seconds+"";
}
//不需要显示小时
if (withOutHours){
minutes=hours*60+minutes;
if (minutes<10)
minute = "0" + minutes;
else
minute = "" + minutes;
return minute + ":" + second ;
}
return hour + ":" + minute + ":" + second ;
}
public static int duration2seconds(long mss){
int seconds=(int)(mss/1000);
return seconds;
}
其他
数据释放
/*
* 释放
* */
private void releaseMediaPlayer() {
if (mMediaPlayer != null) {
mMediaPlayer.release();
mMediaPlayer = null;
}
}
/*
* mediaplayer数据源清除
* */
private void doCleanUp() {
mVideoWidth = 0;
mVideoHeight = 0;
mIsVideoReadyToBePlayed = false;
mIsVideoSizeKnown = false;
}
源代码