需求:日常开发中我们经常需要视频的开始画面或者最后结束画面
实际开发中我在做视频连续播放的时候,视频播放间隔会出现加载间隙,我想让播放画面的最后一帧一直保持,直到下个视频加在完毕开始播放,这里需要获取视频最后一帧画面,产生了这样的需求,查了下资料,做了获取帧画面的简单封装
直接上源码:
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
public class MediaDecoder {
public interface OnMediaDecoderListener {
void onDecodeFrame(Bitmap bitmap);
}
private static Reference<MediaDecoder> mediaDecoder;
private static Reference<MediaMetadataRetriever> retriever;
private OnMediaDecoderListener listener;
private int type;
private int flag;
public final static int TYPE_VIDEO_LOCAL = 0x001;
public final static int TYPE_VIDEO_NET = 0x002;
public final static int VIDEO_START = 0x001;
public final static int VIDEO_CUSTOM = 0x002;
public final static int VIDEO_END = 0x003;
@IntDef({TYPE_VIDEO_LOCAL, TYPE_VIDEO_NET})
@Retention(RetentionPolicy.SOURCE)
private @interface MediaDecoderType {
}
@IntDef({VIDEO_START, VIDEO_CUSTOM, VIDEO_END})
@Retention(RetentionPolicy.SOURCE)
private @interface MediaDecoderTime {
}
private static MediaDecoder getMediaDecoder() {
if (!checkNotNull()) {
mediaDecoder = new WeakReference<>(new MediaDecoder());
}
return mediaDecoder.get();
}
public static MediaDecoder create() {
return getMediaDecoder();
}
public MediaDecoder setType(@MediaDecoderType int mediaType) {
type = mediaType;
return getMediaDecoder();
}
public MediaDecoder setListener(OnMediaDecoderListener onMediaDecoderListener) {
mediaDecoder.get().listener = onMediaDecoderListener;
return getMediaDecoder();
}
public MediaDecoder setDataSource(String path) {
retriever = new WeakReference<>(new MediaMetadataRetriever());
switch (type) {
case TYPE_VIDEO_LOCAL: {
retriever.get().setDataSource(path);
}
break;
case TYPE_VIDEO_NET: {
retriever.get().setDataSource(path, new HashMap<String, String>());
}
break;
}
return getMediaDecoder();
}
/**
* @param flag 只有flag为VIDEO_CUSTOM时候需要传time
* @param time 单位为毫秒
*/
public void start(@MediaDecoderTime int flag, int... time) {
this.flag = flag;
new Thread(new GetFrameThread(time)).start();
}
private static boolean checkNotNull() {
return (mediaDecoder != null && mediaDecoder.get() != null);
}
class GetFrameThread implements Runnable {
private int time;
GetFrameThread(int... time) {
switch (flag) {
case VIDEO_CUSTOM: {
if (time.length == 0) {
flag = VIDEO_START;
} else {
this.time = time[0];
}
}
break;
}
}
@Override
public void run() {
Bitmap bitmap;
try {
//获得第一帧图片
switch (flag) {
case VIDEO_END: {
String duration = retriever.get().extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
time = Integer.parseInt(duration);
}
case VIDEO_CUSTOM: {
//getFrameAtTime(单位为微秒)
bitmap = retriever.get().getFrameAtTime(time * 1000, MediaMetadataRetriever.OPTION_CLOSEST);
}
break;
default: {
bitmap = retriever.get().getFrameAtTime();
}
break;
}
if (listener != null) {
listener.onDecodeFrame(bitmap);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (retriever != null && retriever.get() != null) retriever.get().release();
}
}
}
}
使用示例(获取最后一帧画面):
MediaDecoder.create()
.setType(MediaDecoder.TYPE_VIDEO_NET)
.setDataSource(url)
.setListener(new MediaDecoder.OnMediaDecoderListener() {
@Override
public void onDecodeFrame(Bitmap bitmap) {
}
}).start(MediaDecoder.VIDEO_END);
setType为设置视频类型,暂时为两种:网络视频、本地视频
setDataSource为设置路径:网路视频为网络Url;本地视频为存储file路径
start为设置帧位置:VIDEO_START为第一帧,VIDEO_END为最后一帧,自定义为VIDEO_CUSTOM,并且传入需要截取帧的毫秒位置(start(MediaDecoder.VIDEO_CUSTOM,5*1000))