上一篇中介绍了有关视频提取图片的知识点,如果对这个不太了解 建议看下android提取视频多张图片和视频信息之前这篇.
这里实现的是仿微信的视频编辑页面,主要是播放视频和显示该视频的一系列图片,可以滑动图片的列表,视频也跟着动(seekto),然后可以拖动滑块实现视频的seekto。最后会进行视频的剪切操作,就是剪切2个滑块之前的区域,视频重复播放2个滑块之间的区域。
先看下效果图:
https://github.com/ta893115871/VideoEdit
实现是:上面是videoview(你也可以用一些开源的视频播放器),底部是recycleview(显示视频的提取图片)+自定义view rankbar (用于制定需要截取的视频)。
android提取视频多张图片和视频信息这篇文章中用
Bitmap bitmap = metadataRetriever.getFrameAtTime(time * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
返回的是bitmap图片然后保存到sd中然后用glide进行展示,因为这里展示的图片是小图且没有必要很清晰,所以需要对bitmap进行处理然后再保存再展示,以达到快速提取的目标:
/**
* 设置固定的宽度,高度随之变化,使图片不会变形
*
* @param bm Bitmap
* @return Bitmap
*/
private Bitmap scaleImage(Bitmap bm) {
if (bm == null) {
return null;
}
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = extractW * 1.0f / width;
// float scaleHeight =extractH*1.0f / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleWidth);
Bitmap newBm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix,
true);
if (!bm.isRecycled()) {
bm.recycle();
bm = null;
}
return newBm;
}
这里demo中的展示视频缩率图的整个的宽是固定的,所以以宽为准的高度随之变化,大家可以根据具体的需要做相应的处理。
因为视频的时长不固定,而且一般的短视频考虑到服务器以及成本,所以视频一般不会太长,比如微信:最长可以支持上传10s的视频 (我的需求是最长支持60s的视频),通过观察微信如下规则:
long startPosition = 0;
long endPosition = duration;
int thumbnailsCount;
int rangeWidth;
boolean isOver_60_s;
//MAX_CUT_DURATION为60 秒
//MAX_COUNT_RANGE为10张
if (endPosition <= MAX_CUT_DURATION) {
isOver_60_s = false;
thumbnailsCount = MAX_COUNT_RANGE;
rangeWidth = mMaxWidth;
} else {
isOver_60_s = true;
thumbnailsCount = (int) (endPosition * 1.0f / (MAX_CUT_DURATION * 1.0f) * MAX_COUNT_RANGE);
rangeWidth = mMaxWidth / MAX_COUNT_RANGE * thumbnailsCount;
}
mRecyclerView.addItemDecoration(new EditSpacingItemDecoration(UIUtil.dip2px(this, 35), thumbnailsCount));
超过60s的视频提取的图片个数为是:视频时长/60s * 10张。整个展示这些图片的宽也就是recycleview的宽rangeWidth为:mMaxWidth( 屏幕或者减去设计师的宽)/10张*视频提取的图片个数。
既然可以滑动底部的recycleview的视频缩率图,根据滑动的位置来计算视频应该展示seenkto哪一秒,所以我们需要知道滑动的距离以及每毫秒所占的px(像素)averageMsPx,所以:
averageMsPx = duration(时长) * 1.0f(乘以1.0为了精确) / rangeWidth (recycleview的宽,不是可见区域) * 1.0f;
然后就可以利用recycleview的OnScrollListener方法进行监听处理,具体的代码可以下载去看,这里只是梳理下思路。
这个没啥好说的了,就是android为了方便开发者的使用而封装的控件。
我的需求是使用的ijk播放器(地址:https://github.com/Bilibili/ijkplayer),这里就用的videoview不过都一样。获取播放的进度可以用handler实现来保证视频一直在所选中的范围内重复播放。
private Handler handler = new Handler();
private Runnable run = new Runnable() {
@Override
public void run() {
videoProgressUpdate();
handler.postDelayed(run, 1000);
}
};
private void videoProgressUpdate() {
long currentPosition = mVideoView.getCurrentPosition();
Log.d(TAG, "----onProgressUpdate-cp---->>>>>>>" + currentPosition);
if (currentPosition >= (rightProgress)) {
mVideoView.seekTo((int) leftProgress);
positionIcon.clearAnimation();
if (animator != null && animator.isRunning()) {
animator.cancel();
}
anim();
}
}
就是中间那个黄色的渐变矩形,就是一个imageview通过动画不断的设置它的leftMargin即可。动画就是使用ValueAnimator来生成一系列的数字,然后监听回调即可。有人会说为什么不根据进度进行设置呢?我尝试过用视频的进度进行处理,当时用的ijk播放器,好多都是异步回调的,比如seekto方法,这个就很难控制了,导致进度不准确,而且进度顿顿的感觉。后来采用了动画就很好了,反正本地视频一般不会卡吧。这个可以去看下代码。
这个demo中我改为videoview后发现以下问题:
我测试了下:有时候seekto方法你调用完了,立马去获取进度可能为0。所以指针的位置不对了。
WTF?!why?因为
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
//设置MediaPlayer的OnSeekComplete监听
mp.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer mp) {
Log.d(TAG, "------ok----real---start-----" );
if (!isSeeking) {
videoStart();
}
}
});
}
});
当前这些问题在demo中都已经修复,具体可以看下代码
这个算是除了视频提取图片外的比较重要的部分了。目标呢就是这个控件可以回调出来一个方法,可以知道按下去,滑动中,抬起来等状态,可以知道左右的2个视频时间,可以知道拖动的是哪个,这个回调看起来这样。
private final RangeSeekBar.OnRangeSeekBarChangeListener mOnRangeSeekBarChangeListener = new RangeSeekBar.OnRangeSeekBarChangeListener() {
@Override
public void onRangeSeekBarValuesChanged(RangeSeekBar bar, long minValue, long maxValue, int action, boolean isMin, RangeSeekBar.Thumb pressedThumb) {
Log.d(TAG, "-----minValue----->>>>>>" + minValue);
Log.d(TAG, "-----maxValue----->>>>>>" + maxValue);
leftProgress = minValue + scrollPos;
rightProgress = maxValue + scrollPos;
Log.d(TAG, "-----leftProgress----->>>>>>" + leftProgress);
Log.d(TAG, "-----rightProgress----->>>>>>" + rightProgress);
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "-----ACTION_DOWN---->>>>>>");
videoPause();
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "-----ACTION_MOVE---->>>>>>");
mVideoView.seekTo((int) (pressedThumb == RangeSeekBar.Thumb.MIN ?
leftProgress : rightProgress));
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "-----ACTION_UP--leftProgress--->>>>>>" + leftProgress);
//从minValue开始播
mVideoView.seekTo((int) leftProgress);
videoStart();
break;
default:
break;
}
}
};
自定义view不是这篇的重点,但是只要搞清楚自定义的一些方法和步骤就可以搞定一些简单的控件。
下面是extend view的一些步骤:
最后,如果有什么问题,大家可以探讨一下哈。知识是开放的,大家一起学习。