需求描述
实现截取Android应用当前界面的功能,需包含界面中视频(此博客的参考代码以存储在设备本地的视频为例,未检验在线视频的情况)当前的播放帧截图。
调研准备
首先应用需要获取设备存储的读写权限,需要在AndroidManifest.xml中加上请求权限的配置代码:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
此外,Android原生的视频播放组件VideoView不支持修改视频的分辨率(视频分辨率与容器宽高不一致时,需要让视频拉伸填充容器),因此需要自己封装一个继承了VideoView的组件;在项目中新建一个MyVideoView.java,内容如下:
package XXX;
import android.content.Context;
import android.media.MediaPlayer;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.VideoView;
import com.lzy.okgo.utils.HttpUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class MyVideoView extends VideoView {
private static final String TAG = "####MyVideoView ";
// 记录当前播放视频的路径(用于截取播放帧)
private String currentVideoUrl;
public MyVideoView(Context paramContext) {
super(paramContext);
}
public MyVideoView(Context paramContext, AttributeSet paramAttributeSet) {
super(paramContext, paramAttributeSet);
}
public MyVideoView(Context paramContext, AttributeSet paramAttributeSet, int paramInt) {
super(paramContext, paramAttributeSet, paramInt);
}
// 判断是否为视频文件
public static boolean isVideo(String filePath) {
filePath = filePath.toLowerCase();
String[] vFiles = {".mov", ".mkv", ".mp4", ".avi"};
for (byte vIdx = 0; vIdx < vFiles.length; vIdx++) {
if (filePath.endsWith(vFiles[vIdx])) return true;
}
return false;
}
// 循环播放视频
public void LoopPlayBack(final String videoPath) {
File file = new File(videoPath);
if (!file.exists() || !isVideo(videoPath)) return;
// 开始播放视频
this.currentVideoUrl = videoPath;
setVideoPath(videoPath);
start();
// 视频播放完成,重新开始播放
setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
MyVideoView.this.start();
}
});
// 视频播放报错监听
setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp) {
Log.d(TAG, "播放错误..");
return false;
}
});
}
public String getCurrentVideoUrl() { // 获取视频文件路径(用于截取播放帧)
return this.currentVideoUrl;
}
protected void onMeasure(int paramInt1, int paramInt2) { // 调整视频分辨率,使视频拉伸填充容器
setMeasuredDimension(getDefaultSize(getWidth(), paramInt1), getDefaultSize(getHeight(), paramInt2));
}
}
在相应的布局.xml中使用MyVideoView视频组件:
P.S.如果要实现圆角视频效果,可以在MyVideoView外再套一层CardView,可参考:CardView-卡片布局
<LinearLayout
android:orientation="horizontal"
android:layout_width="800.0px"
android:layout_height="600.0px"
>
<XXX.MyVideoView
android:id="@id/video"
android:visibility="visible"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout-align-parent-top="true"
/>
</LinearLayout>
参考代码
如果界面中有视频播放,使用getDrawingCache截取整个应用界面时,视频区域会显示为黑屏;因此要另外获取视频当前的播放帧,再通过Canvas绘制Bitmap将视频截图“粘贴”到界面截图相应区域,从而实现截取整个界面(包括视频)的效果:
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.media.MediaMetadataRetriever;
// 调用此函数进行完整截屏(参数:本地视频路径)
private void taskScreenshot(String videoPath) {
try {
Bitmap screenPic = takeScreenBitmap(); // 屏幕截图
Bitmap videoPic = getCurrentVideoBitmap(this.myVideoView); // 视频截图
if (screenPic != null) {
Bitmap wholePic = screenPic; // 完整截图(默认取屏幕截图)
if (videoPic != null) // 如果获取到了视频截图,完整截图由屏幕截图“粘贴”视频截图得到
wholePic = mergeBitmap(screenPic, scaleBitmap(videoPic, 2), 0, 2);
// 获取截图保存路径
String picPath = Environment.getExternalStorageDirectory().getPath() + "/screenshot/testPic.png";
File picFile = new File(picPath);
if (picFile.exists())
picFile.delete();
FileOutputStream fileOutputStream = new FileOutputStream();
// 保存截图
wholePic.compress(Bitmap.CompressFormat.PNG, 80, fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
}
} catch (Exception e) {
}
}
// 截屏(不包含视频)
private Bitmap takeScreenBitmap() {
int width = getWindow().getDecorView().getRootView().getWidth();
int height = getWindow().getDecorView().getRootView().getHeight();
View view = getWindow().getDecorView().getRootView();
view.setDrawingCacheEnabled(false);
view.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, width, height);
view.destroyDrawingCache();
return bitmap;
}
// 截取视频关键帧
public static Bitmap getCurrentVideoBitmap(MyVideoView paramMyVideoView) {
Bitmap bitmap = null;
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
String videoPath = paramMyVideoView.getCurrentVideoUrl();
try {
if (Build.VERSION.SDK_INT >= 24) {
Uri uri = Uri.parse(videoPath);
mediaMetadataRetriever.setDataSource(this, uri);
} else {
FileInputStream fileInputStream = new FileInputStream();
File file = new File();
this(videoPath);
this(file.getAbsolutePath());
mediaMetadataRetriever.setDataSource(fileInputStream.getFD());
}
bitmap = mediaMetadataRetriever.getFrameAtTime((paramMyVideoView.getCurrentPosition() * 1000), MediaMetadataRetriever.OPTION_CLOSEST);
}
catch (Exception e) { }
finally {
try {
mediaMetadataRetriever.release();
} catch (RuntimeException runtimeException1) {
stringBuilder = new StringBuilder();
stringBuilder.append("getCurrentVideoBitmap-RuntimeException2:");
stringBuilder.append(runtimeException1.getMessage());
StoreData.appendLogFile("####FullscreenActivity ", stringBuilder.toString());
}
return (bitmap == null) ? null : Bitmap.createBitmap(bitmap);
}
}
// bitmap变换:截取的视频截图尺寸和页面容器可能不一致,需拉伸为容器尺寸
private Bitmap scaleBitmap(Bitmap paramBitmap, float cWidth, float cHeight) {
if (paramBitmap == null)
return null;
int vWidth = paramBitmap.getWidth();
int vHeight = paramBitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(cWidth / vWidth, cHeight / vHeight);
Bitmap bitmap = Bitmap.createBitmap(paramBitmap, 0, 0, vWidth, vHeight, matrix, false);
if (!paramBitmap.isRecycled()) paramBitmap.recycle();
return bitmap;
}
// bitmap变换:将视频截图“粘贴”到屏幕截图的对应区域
private Bitmap mergeBitmap(Bitmap paramBitmap1, Bitmap paramBitmap2, int[] size1, int[] size2) {
int width1 = size1[0], height1 = size1[1];
int width2 = size2[0], height2 = size2[1];
// 创建与屏幕截图大小一样的画布,然后分别将屏幕截图、视频截图绘制到画布对应位置
Bitmap bitmap = Bitmap.createBitmap(width1, height1, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(paramBitmap1, new Rect(0, 0, width1, height1), new Rect(0, 0, width1, height1), null);
canvas.drawBitmap(paramBitmap2, new Rect(0, 0, width2, height2), new Rect(0, 0, width2, height2), null);
return bitmap;
}
参考文档
[1] Android播放网络视频截图
[2] setDataSource RuntimeException 0xFFFFFFEA
[3] Android Bitmap相关操作
[4] Android使用Canvas绘制Bitmap相关
[5] CardView-卡片布局