Android仿微信小视频录制功能(二)
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
允许我们监听到三个状态分别是:OnPrepared
、OnCompletion
和OnError
对应的就是:完成准备、播放完成和播放出错。如果之前了解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;}}}};
对MediaPlayer
OnCompletion的监听:
private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mCurrentState = STATE_PLAYBACK_COMPLETED;if (mOnCompletionListener != null)//这里我们向外暴露OnCompletion状态mOnCompletionListener.onCompletion(mp);}};
对MediaPlayer
VideoSizeChange的监听:
OnVideoSizeChangedListener mVideoSizeChangedListener = new OnVideoSizeChangedListener() {@Overridepublic void onVideoSizeChanged(MediaPlayer mp, int width, int height) {mVideoWidth = width;mVideoHeight = height;...};
对MediaPlayer
OnError的监听:
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;}}
以上是个大体的思路,非常有可能不完善,只是简单走完状态机中主要的状态。
然后,回到一开始说到的界面上的瑕疵,解决思路其实就是重写SurfaceView
的onMeasure()
方法,我们可以看到系统在重写’onMeasure()’方法是做了许多情况的判断,这里就不列举出来了。而针对我们这种固定的需求,因为前面获取到了mVideoWidth
、mVideoHeight
两个变量,运用起它们,大致是:
@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仿微信小视频录制功能(二)相关推荐
- Android仿微信小视频录制功能
还没看完,应该还不错,先收藏,觉得可以开拓 https://blog.csdn.net/u012227600/article/details/50835633 -------------------- ...
- Android 仿微信小视频录制
Android 仿微信小视频录制 WechatShortVideo和WechatShortVideo文章
- Android录制视频,仿微信小视频录制(一)
Android录制视频,第一部分自定义控件 简述 公司有一个录制视频并上传的功能,录制视频具体使用类如下:硬件控制使用Camera,视频录制的格式音频等具体配置与录制使用MediaRecorder,预 ...
- FFmpeg 开发(12):Android FFmpeg 实现带滤镜的微信小视频录制功能
前文利用 FFmpeg 分别实现了对 Android Camera2 采集的预览帧进行编码生成 mp4 文件,以及对 Android AudioRecorder 采集 PCM 音频进行编码生成 aac ...
- Android 仿微信短视频录制
VideoRecorder 项目地址:junerver/VideoRecorder 简介: Android 仿微信短视频录制 更多:作者 提 Bug 标签: Android 仿微信短视频录制 项目 ...
- 仿android微信视频编辑,Android 仿微信短视频录制
VideoRecorder Android 仿微信短视频录制 预览 Bug 修复与更新日志: 更新日志: 1.2.0:仿照微信,短按拍照长按拍摄 --19.06.21 1.1.5:增加进度条,修改依赖 ...
- Android 微信小视频录制功能实现
目录 开发之前 开发环境 相关知识点 开始开发 案例预览 案例分析 搭建布局 视频预览的实现 自定义双向缩减的进度条 录制事件的处理 长按录制 抬起保存 上滑取消 双击放大(变焦) 实现视频的录制 实 ...
- Android仿微信小视频的简单实现
一个可播放的网络视频url http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4 由于项目中用到了视频认证功能,所以想到了采用与微信小视频录制类似的界面功 ...
- 仿微信朋友圈,仿微信小视频 ,录制视频功能
https://github.com/Naoki2015/VCameraDemo 仿微信小视频 CircleDemo https://github.com/Naoki2015/CircleDe ...
最新文章
- 【Android 应用开发】BluetoothClass详解
- seo自动发布外链工具_没资源、零经验,如何建立高质量外链?
- Java虚拟机(JVM)面试题大集合
- python开发效率高吗_从运行效率与开发效率比较Python和C++
- Redisson:这么强大的实现分布式锁框架,你还没有?
- java 夏令时 容器 问题,Java夏令时有关问题
- json字符串与js对象转换
- C# word 转 pdf
- 图论 有向无环图 拓扑排序 是什么
- linux centos 安装mysql,linux/centos安装mysql
- 一道学吧上的题 ^ 题目:不允许重复的实验 - 从数字1、2、3、4、5中随机抽取3次数字(不允许重复)组成一个三位数,则其各位数字之和等于n的概率为________ 输入整数 输出一个小数(保留
- 手机通讯录整理excel2csv2vcf
- Learning Premiere Elements 15 Premiere Elements 15教程 Lynda课程中文字幕
- 常用谷歌地址和常用搜索引擎
- 如何在国内快速访问Github
- 分布式环境时钟同步问题
- ElasticSearch wildcard查询(英文检索)
- 大数据比较 同比与环比的区别
- 满足功能安全要求的代码测试方案:Parasoft C++test
- 尚未解决的10个最困难的数学问题
热门文章
- 初代超级计算机,科技发展太快:20年前的超级计算机算力仅与新Xbox相同
- ShaderToy(四)画更好的笑脸
- 基于七牛和fresco的一整套安卓图片解决方案
- 一位同学的Python大作业【分析当当网书籍价格、出版社、电子书版本占比数据】
- 5.1声道和虚拟环绕
- 开发工程师人生之路-与30岁左右的开发人员共勉开发工程师人生之路(转载)
- HDU-A Fibonacci sequence斐波那契数列-大数求和
- 线圈绕制过程中的关键点
- The Power of H3D2, 截图,视频! 少女时代 gee gee gee
- 深度强化学习制作森林冰火人游戏AI(一)下载游戏