Android仿微信小视频录制功能(二)

接着上一篇,在完成了录制功能后,伟大的哲学家沃兹基索德曾经说过:“有录就有放。”,那么紧接着就来实现播放功能,按照国际惯例,先上下效果图:

可以看到界面上存在着瑕疵,强迫症患者可能无法忍受,所以抓紧进入功能实现上来。

需求

简单分析下需求,需求很简单:因为我们录制的视频保存在本地,获取它不需要进行网络交互,但是仍然希望有一个进度条的展示,在进度条展示期间所呈现的是视频的预览图片,进度条加载完成后再将视频展示并播放,播放完成后再循环播放,并且提示点击可以关闭。

实现

功能实现上,提到视频播放首先想到的就是调用系统的VideoView控件来实现。所以,我们先用它来实现前面分析的需求上的功能,再来简单探究下这个VideoView
这里先给出界面布局吧:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/video_root"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black"android:orientation="vertical" ><VideoView android:id="@+id/video_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerVertical="true"/><ImageView android:id="@+id/video_thumb_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerVertical="true"android:scaleType="fitXY"/><com.example.activity.widget.movie.view.CircleProgressView
        android:id="@+id/circle_progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"/>
</RelativeLayout>

简单暴力的把三个控件叠在一起。
功能实现上,由于采用VideoView的缘故,许多方法都是封装好的,所以实现起来也是非常的简单,就先给出代码:

public class MoviePlayerActivity extends BaseActivity implements OnPreparedListener,OnErrorListener,OnCompletionListener{private VideoView mVideoView;private CircleProgressView mProgressView;private ImageView mThumbView;private int completeCount = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.movie_player_activity);initView();initData();}private void initView() {mVideoView = (VideoView) findViewById(R.id.video_view);mProgressView = (CircleProgressView) findViewById(R.id.circle_progress);mThumbView = (ImageView) findViewById(R.id.video_thumb_view);mVideoView.setOnPreparedListener(this);mVideoView.setOnErrorListener(this);mVideoView.setOnCompletionListener(this);mProgressView.setMax(100);
//      View contentView = getWindow().getDecorView().findViewById(R.id.content);RelativeLayout root = (RelativeLayout) findViewById(R.id.video_root);root.setOnTouchListener(mContentTouch);}private void initData() {MediaObject MediaObject = (MediaObject) getIntent().getSerializableExtra("MediaObj");PlayerTask task = new PlayerTask(MediaObject);task.execute();}private OnTouchListener mContentTouch = new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {int action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:if (completeCount >= 1)finish();break;default:break;}return true;}};@Overrideprotected void onResume() {// TODO Auto-generated method stubsuper.onResume();if(mVideoView != null)mVideoView.resume();}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();if(mVideoView != null)mVideoView.pause();}@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();if(mVideoView != null)mVideoView.stopPlayback();}@Overridepublic void onCompletion(MediaPlayer mp) {// TODO Auto-generated method stubcompleteCount ++;if(completeCount >= 1)Tools.showToast("点击关闭..");mVideoView.start();}@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {return false;}@Overridepublic void onPrepared(MediaPlayer mp) {}private class PlayerTask extends AsyncTask<Void, Integer, Void>{/* (non-Javadoc)*/private int count = 0;private MediaObject mMediaObject;public PlayerTask(MediaObject obj){this.mMediaObject = obj;}private Bitmap decodeThumbBitmap(String path){BitmapFactory.Options options = new Options();options.inPreferredConfig = Bitmap.Config.RGB_565;return BitmapFactory.decodeFile(path,options);}@Overrideprotected void onPreExecute() {int screenWidth = DisplayUtil.getScreenWidth();mProgressView.setVisibility(View.VISIBLE);mThumbView.setVisibility(View.VISIBLE);if(!StringUtils.isEmpty(mMediaObject.getOutputVideoThumbPath())){Bitmap thumbBitmap = decodeThumbBitmap(mMediaObject.getOutputVideoThumbPath());int width = thumbBitmap.getWidth();int height = thumbBitmap.getHeight();mThumbView.getLayoutParams().width = screenWidth;mThumbView.getLayoutParams().height = (int)((width/height *1.0f) * screenWidth);mThumbView.setImageBitmap(thumbBitmap);}mVideoView.setVideoPath(mMediaObject.getOutputVideoPath());}/* (non-Javadoc)*/@Overrideprotected Void doInBackground(Void... params) {while(count <= 50){count += 2;publishProgress(count);try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return null;}/* (non-Javadoc)*/@Overrideprotected void onProgressUpdate(Integer... values) {mProgressView.setProgress(values[0]);}/* (non-Javadoc)*/@Overrideprotected void onPostExecute(Void result) {count = 0;mProgressView.setVisibility(View.GONE);mThumbView.setVisibility(View.GONE);mVideoView.start();}}
}

依次简单说下,VideoView允许我们监听到三个状态分别是:OnPreparedOnCompletionOnError对应的就是:完成准备、播放完成和播放出错。如果之前了解MediaPlayer状态机,那么会发现:VideoView这三个状态相对来说真是非常简洁。当然,正式的VideoView还会添加一个MediaPlayerControl用于控制视频的播放、暂停、控制播放进度。因为我们的需求很简单,所以就没有用到它。

那么,进入实现流程:在获取到控件以及MediaObject对象后,我们实现一个AsyncTask来模拟视频加载的任务:onPreExcute()中,我们先将之前保存好的视频预览图取出(实际上就是在视频录制完成后对视频文件第一帧的截图),并初始化到ImageView中去,然后将视频文件的path路径设置到VideoView中去。

doInBackground(...)中,就是简单模拟下加载进度了,不要忘了调用publishProgress()就行。

onProgressUpdate(...),更新我们的ProgressView

onPostExecute(...)中,将进度条和预览图都隐藏掉,调用VideoView.start()即可。

当然不要忘了执行我们的Task,循环播放就是在每次OnCompletion中重新start()就好,并且统计下播放完成的次数,如果大于等于1了就可以提示点击取消了,onTouch就好。

纵然这么简单,也不能忘记我们粗糙的进度条:

public class CircleProgressView extends View {private Paint mOutSidePaint;private Paint mInsidePaint;private int mMax = 1;private int mProgress;private float mCenterX;private float mCenterY;private RectF mOutSideCircleRectF = new RectF();private RectF mInSideCircleRectF = new RectF();private int mOutSideRadius = DisplayUtil.dip2px(getContext(), 30);private int mInSideRadius = DisplayUtil.dip2px(getContext(), 28);public CircleProgressView(Context context){this(context, null);}public CircleProgressView(Context context, AttributeSet attrs) {super(context, attrs);  initPaint();}private void initPaint() {// TODO Auto-generated method stubmOutSidePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mOutSidePaint.setColor(Color.LTGRAY);mOutSidePaint.setStrokeWidth(2.0f);mOutSidePaint.setStyle(Paint.Style.STROKE);mInsidePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mInsidePaint.setColor(Color.LTGRAY);mInsidePaint.setStyle(Paint.Style.FILL);}/* (non-Javadoc)*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//      super.measure(widthMeasureSpec, heightMeasureSpec);
//      setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));calculateCircleCenter();calculateDrawRectF();}//
/*  private int measure(int measureSpec, boolean isWidth) {int result;int mode = MeasureSpec.getMode(measureSpec);int size = MeasureSpec.getSize(measureSpec);int padding = isWidth ? getPaddingLeft() + getPaddingRight(): getPaddingTop() + getPaddingBottom();if (mode == MeasureSpec.EXACTLY) {result = size;} else {result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();result += padding;if (mode == MeasureSpec.AT_MOST) {result = Math.min(result, size);}}return result;}*/private void calculateCircleCenter() {mCenterX = (getWidth() - getPaddingLeft() - getPaddingRight()) / 2.0f+ getPaddingLeft();mCenterY = (getHeight() - getPaddingTop() - getPaddingBottom()) / 2.0f+ getPaddingTop();}private void calculateDrawRectF() {mOutSideCircleRectF.left = mCenterX - mOutSideRadius;mOutSideCircleRectF.top = mCenterY - mOutSideRadius;mOutSideCircleRectF.right = mCenterX + mOutSideRadius;mOutSideCircleRectF.bottom = mCenterY + mOutSideRadius;mInSideCircleRectF.left = mCenterX - mInSideRadius;mInSideCircleRectF.top = mCenterY - mInSideRadius;mInSideCircleRectF.right = mCenterX + mInSideRadius;mInSideCircleRectF.bottom = mCenterY + mInSideRadius;}/* * */@Overrideprotected void onDraw(Canvas canvas) {canvas.save();//画外圈canvas.drawCircle(mCenterX, mCenterY, mOutSideRadius, mOutSidePaint);
//      //画内圈canvas.drawArc(mInSideCircleRectF, -90,mProgress * 360 / mMax, true, mInsidePaint);canvas.restore();}public void setMax(int max){this.mMax = max;}public void setProgress(int progress){this.mProgress = progress;invalidate();}
}

OK,大功告成。

优化

关于优化,这里先说下我的思路,因为还没有具体实现… 等具体实现好了,我再回来补充。

说是优化,其实是换一种实现方式,因为在使用VideoView时,发现其许多的功能点在我们需求分析中都运用不上,类似MediaPlayerControl以及seekTo()等等都是可以“咔擦”掉的。

那么就来看看VideoView是怎么实现的,实际上他就是一个SurfaceView的子类,内部视频播放功能其实是靠MediaPlayer来完成的,暴露出的那三个状态也正是MediaPlayer状态机中三个重要的状态,这里附张图,给自己巩固和加深下印象:

详细介绍在这里:Android MediaPlayer状态机
通过自己的实现,可以更多的去监听状态从而处理一些业务。

大体的思路如下:
继承SurfaceView并且实现其Callback接口
一样的定义一些状态码:

    // all possible internal statesprivate static final int STATE_ERROR              = -1;private static final int STATE_IDLE               = 0;private static final int STATE_PREPARING          = 1;private static final int STATE_PREPARED           = 2;private static final int STATE_PLAYING            = 3;private static final int STATE_PAUSED             = 4;private static final int STATE_PLAYBACK_COMPLETED = 5;

同样用两个变量来控制状态:

    private int mCurrentState = STATE_IDLE;private int mTargetState  = STATE_IDLE;

以及其他的变量:

...private int mVideoWidth;private int mVideoHeight;
...

初始化View:

    protected void initVideoView() {mVideoWidth = 0;mVideoHeight = 0;...mCurrentState = STATE_IDLE;mTargetState = STATE_IDLE;}

在设置Path的时候进入到Prepared状态:

    public void setVideoPath(String path) {...if (StringUtils.isNotEmpty(path)) {mTargetState = STATE_PREPARED;openVideo(Uri.parse(path));}}
public void openVideo(Uri uri) {Exception exception = null;try {if (mMediaPlayer == null) {...//这里初始化设置我们的mMediaPlayer,设置监听mMediaPlayer = new MediaPlayer();mMediaPlayer.setOnPreparedListener(mPreparedListener);mMediaPlayer.setOnCompletionListener(mCompletionListener);mMediaPlayer.setOnErrorListener(mErrorListener);mMediaPlayer.setOnVideoSizeChangedListener(mVideoSizeChangedListener);...} else {mMediaPlayer.reset();}mMediaPlayer.setDataSource(getContext(), uri);mMediaPlayer.prepareAsync();mCurrentState = STATE_PREPARING;} ...} catch (Exception ex) {exception = ex;}if (exception != null) {//捕获到异常,切换状态mCurrentState = STATE_ERROR;if (mErrorListener != null)mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);}}

MediaPlayer的Prepared状态监听:

    MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {//必须是正常状态if (mCurrentState == STATE_PREPARING) {...mCurrentState = STATE_PREPARED;mVideoWidth = mp.getVideoWidth();mVideoHeight = mp.getVideoHeight();...switch (mTargetState) {case STATE_PREPARED://这里是我们向外暴露的Prepared状态if (mOnPreparedListener != null)mOnPreparedListener.onPrepared(mMediaPlayer);break;case STATE_PLAYING:start();break;}}}};

MediaPlayerOnCompletion的监听:

    private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mCurrentState = STATE_PLAYBACK_COMPLETED;if (mOnCompletionListener != null)//这里我们向外暴露OnCompletion状态mOnCompletionListener.onCompletion(mp);}};

MediaPlayerVideoSizeChange的监听:

    OnVideoSizeChangedListener mVideoSizeChangedListener = new OnVideoSizeChangedListener() {@Overridepublic void onVideoSizeChanged(MediaPlayer mp, int width, int height) {mVideoWidth = width;mVideoHeight = height;...};

MediaPlayerOnError的监听:

    private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int framework_err, int impl_err) {...mCurrentState = STATE_ERROR;if (mOnErrorListener != null)//这里我们向外暴露onError状态mOnErrorListener.onError(mp, framework_err, impl_err);return true;...}};

start()方法:

    public void start() {mTargetState = STATE_PLAYING;//这里可用状态包括{Prepared, Started, Paused, PlaybackCompleted}if (mMediaPlayer != null && (mCurrentState == STATE_PREPARED || mCurrentState == STATE_PAUSED || mCurrentState == STATE_PLAYING || mCurrentState == STATE_PLAYBACK_COMPLETED)) {try {if (!isPlaying())mMediaPlayer.start();mCurrentState = STATE_PLAYING;...} catch (IllegalStateException e) {...} catch (Exception e) {...}}}

最后release()方法:

    public void release() {mTargetState = STATE_IDLE;mCurrentState = STATE_IDLE;if (mMediaPlayer != null) {try {...mMediaPlayer.stop();mMediaPlayer.release();} catch (IllegalStateException e) {...} catch (Exception e) {...}mMediaPlayer = null;}}

以上是个大体的思路,非常有可能不完善,只是简单走完状态机中主要的状态。

然后,回到一开始说到的界面上的瑕疵,解决思路其实就是重写SurfaceViewonMeasure()方法,我们可以看到系统在重写’onMeasure()’方法是做了许多情况的判断,这里就不列举出来了。而针对我们这种固定的需求,因为前面获取到了mVideoWidthmVideoHeight两个变量,运用起它们,大致是:

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...//这里省去了对MeasureSpec三个状态的判断int width = DisplayUtil.getScreenWidth();int height = (int)((mVideoWidth/mVideoHeight *1.0f) * width);setMeasuredDimension(width, height);...}

结语

总的来说,整个的实现过程中必然会存在多少瑕疵,也许以上的优化方面并不会为整个功能带来性能上的改变,反而在调用系统的控件获得的是稳定的体验。但是了解其实现原理,则是必不可少的,蛤蛤。OK,播放功能先暂时就这样。

Android仿微信小视频录制功能(二)相关推荐

  1. Android仿微信小视频录制功能

    还没看完,应该还不错,先收藏,觉得可以开拓 https://blog.csdn.net/u012227600/article/details/50835633 -------------------- ...

  2. Android 仿微信小视频录制

    Android 仿微信小视频录制 WechatShortVideo和WechatShortVideo文章

  3. Android录制视频,仿微信小视频录制(一)

    Android录制视频,第一部分自定义控件 简述 公司有一个录制视频并上传的功能,录制视频具体使用类如下:硬件控制使用Camera,视频录制的格式音频等具体配置与录制使用MediaRecorder,预 ...

  4. FFmpeg 开发(12):Android FFmpeg 实现带滤镜的微信小视频录制功能

    前文利用 FFmpeg 分别实现了对 Android Camera2 采集的预览帧进行编码生成 mp4 文件,以及对 Android AudioRecorder 采集 PCM 音频进行编码生成 aac ...

  5. Android 仿微信短视频录制

    VideoRecorder 项目地址:junerver/VideoRecorder 简介: Android 仿微信短视频录制 更多:作者   提 Bug 标签: Android 仿微信短视频录制 项目 ...

  6. 仿android微信视频编辑,Android 仿微信短视频录制

    VideoRecorder Android 仿微信短视频录制 预览 Bug 修复与更新日志: 更新日志: 1.2.0:仿照微信,短按拍照长按拍摄 --19.06.21 1.1.5:增加进度条,修改依赖 ...

  7. Android 微信小视频录制功能实现

    目录 开发之前 开发环境 相关知识点 开始开发 案例预览 案例分析 搭建布局 视频预览的实现 自定义双向缩减的进度条 录制事件的处理 长按录制 抬起保存 上滑取消 双击放大(变焦) 实现视频的录制 实 ...

  8. Android仿微信小视频的简单实现

    一个可播放的网络视频url http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4 由于项目中用到了视频认证功能,所以想到了采用与微信小视频录制类似的界面功 ...

  9. 仿微信朋友圈,仿微信小视频 ,录制视频功能

    https://github.com/Naoki2015/VCameraDemo 仿微信小视频    CircleDemo  https://github.com/Naoki2015/CircleDe ...

最新文章

  1. 【Android 应用开发】BluetoothClass详解
  2. seo自动发布外链工具_没资源、零经验,如何建立高质量外链?
  3. Java虚拟机(JVM)面试题大集合
  4. python开发效率高吗_从运行效率与开发效率比较Python和C++
  5. Redisson:这么强大的实现分布式锁框架,你还没有?
  6. java 夏令时 容器 问题,Java夏令时有关问题
  7. json字符串与js对象转换
  8. C# word 转 pdf
  9. 图论 有向无环图 拓扑排序 是什么
  10. linux centos 安装mysql,linux/centos安装mysql
  11. 一道学吧上的题 ^ 题目:不允许重复的实验 - 从数字1、2、3、4、5中随机抽取3次数字(不允许重复)组成一个三位数,则其各位数字之和等于n的概率为________ 输入整数 输出一个小数(保留
  12. 手机通讯录整理excel2csv2vcf
  13. Learning Premiere Elements 15 Premiere Elements 15教程 Lynda课程中文字幕
  14. 常用谷歌地址和常用搜索引擎
  15. 如何在国内快速访问Github
  16. 分布式环境时钟同步问题
  17. ElasticSearch wildcard查询(英文检索)
  18. 大数据比较 同比与环比的区别
  19. 满足功能安全要求的代码测试方案:Parasoft C++test
  20. 尚未解决的10个最困难的数学问题

热门文章

  1. 初代超级计算机,科技发展太快:20年前的超级计算机算力仅与新Xbox相同
  2. ShaderToy(四)画更好的笑脸
  3. 基于七牛和fresco的一整套安卓图片解决方案
  4. 一位同学的Python大作业【分析当当网书籍价格、出版社、电子书版本占比数据】
  5. 5.1声道和虚拟环绕
  6. 开发工程师人生之路-与30岁左右的开发人员共勉开发工程师人生之路(转载)
  7. HDU-A Fibonacci sequence斐波那契数列-大数求和
  8. 线圈绕制过程中的关键点
  9. The Power of H3D2, 截图,视频! 少女时代 gee gee gee
  10. 深度强化学习制作森林冰火人游戏AI(一)下载游戏