最近有一个需求,要实现一个播放器,然后可以实现横竖屏的播放,然后可以在页面内部实现网络视频的切换。尝试了几种方式,记录一下:
首先,实现横竖屏的思路,开始实现是通过新建两个布局 ,也就是在layout_land 和layout_port 两个文件夹里建立相应的布局,然后视频切换时候通过传递记录视频信息的对象来实现播放,每次执行都会执行oncreate 方法,然后调用之前的播放状态,通过seekto 跳转到之前的播放位置。这样做基本是可以实现连续播放的,但是需要进行管理的状态有点多,包括视频是否暂停的状态,home键以及back之后需要处理的东西,很琐碎,另外,视频播放进度并不是实时的,seekto方法也并不能跳到指定的位置(总会有一点小偏差),所以这种实现实际是不太现实的。 实现思路二 ,直接使用代码进行横屏 ,通过在manifists文件中的activity 中使用:
然后调用横竖屏的代码,通过对部分布局进行隐藏,实现横竖屏切换: //设置横屏 private void setFullScreen() { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //这样mVideoView会自己充满全屏 fullScreen.setImageResource(R.mipmap.min_screen); videoHeadbar.setVisibility(View.GONE); mListView.setVisibility(View.GONE); isPortrit = false; } //设置竖屏操作 private void setLittle() { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); fullScreen.setImageResource(R.mipmap.max_screen); videoHeadbar.setVisibility(View.VISIBLE); mListView.setVisibility(View.VISIBLE); isPortrit = true; }
通过这种方式,videoview会自动的执行横屏的动作,播放的位置也是对的,这应该是横竖屏最简便的实现的方式了。项目还有一个需求,是通过当前activity 进行视频的切换,通过vitamo的videoview我并没有找到合适的办法,切换时候总会卡着,切换url也并没有任何的效果,后来去stackoverflow 上看到有人通过mediaplayer 实现切换 ,所以我也改变了视频的实现的方式,通过vitamo的mediaplayer + surfaceView 的方式实现当前界面的网络视频切换:
切换视频代码:
//开始播放 private void setplay(String url) { releaseMediaPlayer(); try { mProgres.setVisibility(View.VISIBLE); mMediaPlayer = new MediaPlayer(this); mMediaPlayer.setDataSource(url); mMediaPlayer.setDisplay(holder); mMediaPlayer.prepareAsync(); mMediaPlayer.setOnBufferingUpdateListener(this); mMediaPlayer.setOnCompletionListener(this); mMediaPlayer.setOnPreparedListener(this); mMediaPlayer.setOnVideoSizeChangedListener(this); setVolumeControlStream(AudioManager.STREAM_MUSIC); } catch (IOException ex) { } }
总体思路就这点,全部代码:布局文件:
主要的视频部分代码,包含了实现了一个定时进行无预览拍照的部分代码:
package com.post.posttime.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.hardware.Camera; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; import com.google.gson.Gson; import com.post.posttime.Adapters.VideoPageListAdapter; import com.post.posttime.BaseClass.BaseActivity; import com.post.posttime.BaseClass.BaseData; import com.post.posttime.Bean.Net.TheoVideoExaminBean; import com.post.posttime.Bean.Net.VideoListBean; import com.post.posttime.R; import com.post.posttime.Utils.GetIPlocation; import com.post.posttime.Utils.ImagePostUtils; import com.post.posttime.Utils.NetUtils; import com.post.posttime.Utils.SystemUtil; import com.post.posttime.View.CameraPreview; import org.xutils.common.Callback; import org.xutils.ex.HttpException; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import io.vov.vitamio.MediaPlayer; import io.vov.vitamio.Vitamio; import io.vov.vitamio.utils.StringUtils; //显示视频的activity public class VideoActivity extends BaseActivity implements SurfaceHolder.Callback, MediaPlayer.OnCompletionListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnVideoSizeChangedListener { //视频控件 SurfaceView surface; private SurfaceHolder holder; MediaPlayer mMediaPlayer; SeekBar mProgressBar; //进度条 TextView playTime; //播放时间 TextView alltime; //所有时间 UpDateSeekBar update; // 更新seekbar 的线程 ImageView begin; //开始按钮 ImageView fullScreen; //全屏按钮 boolean isPortrit = true; //是否竖屏 RelativeLayout containder; ImageView midView; //中间大的播放按钮 boolean isPlay = false; //是否播放 RelativeLayout controler; //包括了播放控制的布局 long lastPosition = 0; //视频播放位置 String TAG = "videoactivity"; boolean isFinish = false; // 是否播放完成 ListView mListView; //展示所有章节 //播放数据 VideoListBean videodata; //传过来的播放的内容 int thispostion; //listview的点击位置 最好的方式是通过视频点击之后获取的videoid 获取到播放位置的 现在是根据位置,需要修改一下 VideoPageListAdapter adapter; RelativeLayout videoHeadbar; //顶部的bar 显示标题 ImageView coverImg; //相机 private Camera mCamera; private CameraPreview mCameraPreview; FrameLayout container; SystemUtil su; boolean isBegin = true; TheoVideoExaminBean recordData; int lesson_id; int learnRecordId; //学时id int currentPositon = 0; //记录拍照时候的视频播放位置 int lastClickPositon; //点击listivew之前记录的position ProgressBar mProgres; AlertDialog.Builder errorDialog; AlertDialog.Builder creatRcordDialog; ProgressDialog mDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_video); Vitamio.isInitialized(this); initData(); init(); initMedia(); } private void init() { su = new SystemUtil(this); errorDialog = new AlertDialog.Builder(this); creatRcordDialog = new AlertDialog.Builder(this); playTime = (TextView) findViewById(R.id.play_time); alltime = (TextView) findViewById(R.id.alltime); midView = (ImageView) findViewById(R.id.vid_mid_image); update = new UpDateSeekBar(); container = (FrameLayout) findViewById(R.id.video_camera); begin = (ImageView) findViewById(R.id.vid_begin); mProgressBar = (SeekBar) findViewById(R.id.progressBar); fullScreen = (ImageView) findViewById(R.id.vid_bigscreen); containder = (RelativeLayout) findViewById(R.id.vid_container); controler = (RelativeLayout) findViewById(R.id.vid_control); videoHeadbar = (RelativeLayout) findViewById(R.id.video_head); mProgres = (ProgressBar) findViewById(R.id.vid_progress); coverImg = (ImageView) findViewById(R.id.video_coverImage); containder.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { controler.setVisibility(View.VISIBLE); showControl(); } }); fullScreen.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isPortrit) { setFullScreen(); } else { setLittle(); } } }); findViewById(R.id.headbar_back).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } }); begin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { beginToPause(); } }); midView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { beginToPause(); } }); errorDialog.setTitle("验证失败").setMessage("是否重新验证").setPositiveButton("继续验证", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startExamImage(); } }).setNegativeButton("放弃验证", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { thispostion = lastClickPositon; } }); creatRcordDialog.setTitle("尚未创建学时").setMessage("是否创建学时").setPositiveButton("创建", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startExamImage(); } }).setNegativeButton("不创建", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { thispostion = lastClickPositon; } }); } //初始化播放器 private void initMedia() { surface = (SurfaceView) findViewById(R.id.video_surface); holder = surface.getHolder(); holder.addCallback(this); holder.setFormat(PixelFormat.RGBA_8888); holder.setSizeFromLayout(); } //访问人脸识别界面 重新获取学时 private void startExamImage() { Intent intent = new Intent(this, CameraActivity.class); startActivityForResult(intent, 14); } //开始播放 private void setplay(String url) { releaseMediaPlayer(); try { mProgres.setVisibility(View.VISIBLE); mMediaPlayer = new MediaPlayer(this); mMediaPlayer.setDataSource(url); mMediaPlayer.setDisplay(holder); mMediaPlayer.prepareAsync(); mMediaPlayer.setOnBufferingUpdateListener(this); mMediaPlayer.setOnCompletionListener(this); mMediaPlayer.setOnPreparedListener(this); mMediaPlayer.setOnVideoSizeChangedListener(this); setVolumeControlStream(AudioManager.STREAM_MUSIC); } catch (IOException ex) { } } //释放视频播放器 private void releaseMediaPlayer() { if (mMediaPlayer != null) { if (mMediaPlayer.isPlaying()) mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; } } //横竖屏时候调用 @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } //设置定时器 完成progressbar 的前进 Handler mHandler = new Handler() { public void handleMessage(Message msg) { int s = msg.what; if (s == 1) { if (mMediaPlayer == null) { return; } playTime.setText(StringUtils.generateTime(mMediaPlayer .getCurrentPosition() == 547442232540l ? 0 : mMediaPlayer .getCurrentPosition())); if (mMediaPlayer != null) { seekBar(mMediaPlayer.getCurrentPosition()); } } else if (s == 2) { controler.setVisibility(View.INVISIBLE); } else if (s == 3) { if (mMediaPlayer != null) {//定时调用相机拍照的程序
if (mMediaPlayer.isPlaying() && learnRecordId != -1 && videodata.getData().get(thispostion).getStatus() == 0) { currentPositon = (int) (mMediaPlayer.getCurrentPosition() / 1000); recordData.getData().getOption().get(thispostion).setLastplay(currentPositon); takePic(); } else { // currentPositon=recordData.getData().getOption().get(thispostion).getLastplay(); } } } } }; //拍照方法 private void takePic() { try { if (mCamera != null) { mCamera.startPreview(); mCamera.takePicture(null, null, mPicture); } } catch (Exception e) { e.printStackTrace(); } } //定时调取摄像头拍照 Runnable cameraThread = new Runnable() { @Override public void run() { mHandler.sendEmptyMessage(3); mHandler.postDelayed(cameraThread, BaseData.EXAMIMAGETIME); } }; //照片返回 private Camera.PictureCallback mPicture = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); Matrix matrix = new Matrix(); matrix.preRotate(270); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); String base64 = ImagePostUtils.bitmapToBase64(bitmap); } }; //获取播放所有的videolist和位置 private void initData() { videodata = (VideoListBean) getIntent().getSerializableExtra("video"); thispostion = getIntent().getIntExtra("postion", 0); lastClickPositon = thispostion; recordData = (TheoVideoExaminBean) getIntent().getSerializableExtra("learnrecord"); lesson_id = getIntent().getIntExtra("lesson_id", 0); mListView = (ListView) findViewById(R.id.video_lv); adapter = new VideoPageListAdapter(videodata.getData(), this); mListView.setAdapter(adapter); adapter.setPostion(thispostion); mListView.smoothScrollToPosition(thispostion); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { //切换视频 lastClickPositon = thispostion; thispostion = position; //点击位置变化 如果成功设置位置,失败的话不改变listviw展示的变化 if (mMediaPlayer != null) { mMediaPlayer.pause(); } setStop(); if (learnRecordId != -1) { //已经创建了学时 adapter.setPostion(position); lastPosition = recordData.getData().getOption().get(position).getLastplay() * 1000; setplay(videodata.getData().get(thispostion).getUrl()); lastClickPositon = thispostion; isFinish = false; } else if (videodata.getData().get(position).getStatus() == 1) { //视频已经完成 重头播放 adapter.setPostion(position); lastPosition = 0; lastClickPositon = thispostion; setplay(videodata.getData().get(thispostion).getUrl()); // new Thread(update).start(); isFinish = false; } else { //没有完成,也没有获取过学时 需要拍照获取学时 重新获取到option 然后设置url 和位置 然后再播放 设置listview点击位置 creatRcordDialog.create().show(); } } }); } //获取到播放位置的图片,然后设置成背景图 非常耗时。。。 // private void initImage() { // try { // MediaMetadataRetriever mmr = new MediaMetadataRetriever(this); // mmr.setDataSource(this, Uri.parse(videodata.getData().get(thispostion).getUrl())); // Bitmap bitmap; // bitmap = mmr.getFrameAtTime(1); // firstImg.setImageBitmap(bitmap); // firstImg.setVisibility(View.VISIBLE); // mmr.release(); // } catch (Exception e) { // // } // // } //定时获取播放进度 class UpDateSeekBar implements Runnable { @Override public void run() { if (!isFinish) { Message msg = mHandler.obtainMessage(); msg.what = 1; mHandler.sendMessage(msg); mHandler.postDelayed(update, 1000); } } } //过一段时间隐藏操作栏 private void showControl() { if (isPlay) { Message msg = mHandler.obtainMessage(); msg.what = 2; mHandler.sendMessageDelayed(msg, 5000); } } //点击事件 private void beginToPause() { if (isBegin) { setplay(videodata.getData().get(thispostion).getUrl()); } else { if (isFinish) { isFinish = false; mMediaPlayer.seekTo(0); mMediaPlayer.start(); // new Thread(update).start(); setStart(); } else { if (isPlay) { mMediaPlayer.pause(); setStop(); } else { mMediaPlayer.start(); setStart(); } } } } //运行时设置图片 private void setStart() { begin.setImageResource(R.mipmap.pause); midView.setImageResource(R.mipmap.video_pause_bg); isPlay = true; showControl(); } //设置暂停时图片 private void setStop() { begin.setImageResource(R.mipmap.play); midView.setImageResource(R.mipmap.video_player_bg); isPlay = false; controler.setVisibility(View.VISIBLE); } //开始时候开启摄像头 private void beginCamera() { mCamera = CameraActivity.getCameraInstance(1); if (mCamera != null) { mCameraPreview = new CameraPreview(this, mCamera); container.removeAllViews(); container.addView(mCameraPreview); mHandler.postDelayed(cameraThread, 5000); } else { toast("摄像头权限被禁止,将无法记录学时"); } } //设置seekbar 位置 private void seekBar(long size) { if (mMediaPlayer.isPlaying()) { long mMax = mMediaPlayer.getDuration(); int sMax = mProgressBar.getMax(); mProgressBar.setProgress((int) (size * sMax / mMax)); } } //视频播放完毕 拍照上传 停止视频 改变当前的是否看完的信息 @Override public void onCompletion(MediaPlayer mp) { currentPositon = (int) (mMediaPlayer.getDuration() / 1000); takePic(); isFinish = true; mMediaPlayer.pause(); setStop(); videodata.getData().get(thispostion).setStatus(1); toast("当前视频播放完成"); } @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { } @Override protected void onPause() { super.onPause(); mHandler.removeCallbacks(cameraThread); releaseCamera(); if (mMediaPlayer != null) { mMediaPlayer.pause(); } setStop(); //默认退出界面后 包括home键之后结束计时 否则系统杀死后无法计时 // finish(); } @Override protected void onResume() { super.onResume(); //开始运行时开启摄像头 beginCamera(); } @Override protected void onDestroy() { super.onDestroy(); if (mMediaPlayer != null) { releaseMediaPlayer(); } if (mCamera != null) { releaseCamera(); } isFinish = true; isPlay = false; mHandler.removeCallbacks(update); mHandler.removeCallbacks(cameraThread); } //视频准备阶段 执行onstart 方法之后每次都执行这个 @Override public void onPrepared(MediaPlayer mp) { mProgres.setVisibility(View.GONE); controler.setVisibility(View.VISIBLE); new Thread(update).start(); alltime.setText(StringUtils.generateTime(mMediaPlayer.getDuration())); isBegin = false; setStart(); mp.start(); coverImg.setVisibility(View.GONE); } @Override public void onSeekComplete(MediaPlayer mp) { loge("seekto complete"); } //设置横屏 private void setFullScreen() { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //这样mVideoView会自己充满全屏 fullScreen.setImageResource(R.mipmap.min_screen); videoHeadbar.setVisibility(View.GONE); mListView.setVisibility(View.GONE); isPortrit = false; } //设置竖屏操作 private void setLittle() { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); fullScreen.setImageResource(R.mipmap.max_screen); videoHeadbar.setVisibility(View.VISIBLE); mListView.setVisibility(View.VISIBLE); isPortrit = true; } @Override public void onBackPressed() { if (!isPortrit) { setLittle(); } else super.onBackPressed(); } //释放摄像头 private void releaseCamera() { if (mCamera != null) { mCamera.release(); mCamera = null; } } //格式化时间 private String getFormatTime(long time) { Date date = new Date(time); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.format(date); } //surface 的变化的listener @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } //开始的位置跳转通过这个方法调用 @Override public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { if (width == 0 || height == 0) { return; } mp.seekTo(lastPosition); } }
有几个地方是不太满意的,首先在开始时候跳转是通过seekto方法,但是视频准备好之前是不起作用的,先调用Mediaplayer.start 方法之后(而且是需要过一点时间)才能调用seekto,所以这里我在onsurfacechange的方法调用了seekto,然后在onprepared之中直接调用了start这样,其他地方调用mMediaPlayer.prepareAsync() 之后直接就会开始视频,控制上感觉有点不足。
另外surfaceview 开始加载会有穿透的效果,也就是会直接看到之前的一个activity ,这里开始我是通过获取第一帧图片来遮挡实现,但是这个方法执行会减慢进入界面的速度,估计是阻塞了UI线程,所以我是通过直接加载了一个黑色背景实现的,这里总感觉不够好,以后可以尝试更漂亮方法实现。就记录到这里