分析qq7.0:
视频在打开登录界面就开始播放 了,而且期间无黑屏
而且是循环播放的
画质问题这里就不说了,这个看视频源了。
可以让不规则的宽高各种宽高不定的视频比例 以及视频大小都能 适应任意安卓手机的宽高 包括平板,且不留任何缝隙
播放器控件选取:解决的是手机适配的问题,另外是播放器控件,这里选择系统播放器比较好. 因为有些播放器不支持读取asset文件夹的Uri比如七牛的
视频加载速度比较慢第一帧用图片代替且需要耦合视频的第一帧
图片的第一帧截取我用的是一个比较专业的adobe premiere的开发工具 这个你们也可以让ps等后期的去做,这种事情对我来说的话还是小kiss,
技术点:
如何读取资源文件视频
如何测量
如何根据视频大小计算应该缩放的比例大小 解决任意尺寸视频手机不留黑边
如何让图片的封面缩放大小和视频的缩放大小吻合
如何调用onStart短暂黑屏问题
架构搭建
资源的读取
String VIDEO_PATH = "android.resource://" + BuildConfig.APPLICATION_ID + "/" + R.raw.login;
videoView.setVideoURI(Uri.parse(Constants.VIDEO_PATH));
创建一个自定义视频类 自定义图片类 图片在视频的上面因为视频不是马上播放 加载有一定时间这里也会存在一个黑屏
关于读取视频的问题,之前尝试过读取assests里面的视频失败了,在stackoverflow照的方案也不行,最后还是把视频放到和res/raw文件夹里面了,
具体实现之视频控件
1. 拿到视频的宽高度才能进行测量重新布局
在继承的VideoView里添加setOnPreparedListener方法获取视频宽高度设置给成员变量就可以拿到了
super.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(final MediaPlayer mp) {
SystemVideoView.this.videoWidth = mp.getVideoWidth();
SystemVideoView.this.videoHeight = mp.getVideoHeight();
}
}
2. 继承VideoView重写onMeasure测量方法
需要一个完美的算法来解决宽高都铺满屏幕问题
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
MeasureUtil.Size measure = MeasureUtil.measure(displayAspectRatio, widthMeasureSpec, heightMeasureSpec, videoWidth, videoHeight);
setMeasuredDimension(measure.width, measure.height);
}
这里的算法比较麻烦,不懂的同学搬用模版代码
测量工具类MeasureUtil.measure方法抽出来
的大致代码是
public static MeasureUtil.Size measure(int displayAspectRatio, int widthMeasureSpec, int heightMeasureSpec, int videoWidth, int videoHeight) {
if (widthMode == View.MeasureSpec.EXACTLY && heightMode == View.MeasureSpec.EXACTLY) {
if (percentVideo > percentView) {
width = widthSize;
height = (int) ((float) widthSize / percentVideo);
} else {
height = heightSize;
width = (int) ((float) heightSize * percentVideo);
}
}else if (widthMode == View.MeasureSpec.EXACTLY) {
width = widthSize;
height = widthSize * videoHeight / videoWidth;
if (heightMode == View.MeasureSpec.AT_MOST && height > heightSize) {
height = heightSize;
}
} else if (heightMode == View.MeasureSpec.EXACTLY) {
height = heightSize;
width = heightSize * videoWidth / videoHeight;
if (widthMode == View.MeasureSpec.AT_MOST && width > widthSize) {
width = widthSize;
}
} else {
width = videoWidth;
height = videoHeight;
if (heightMode == View.MeasureSpec.AT_MOST && videoHeight > heightSize) {
height = heightSize;
width = heightSize * videoWidth / videoHeight;
}
if (widthMode == View.MeasureSpec.AT_MOST && width > widthSize) {
width = widthSize;
height = widthSize * videoHeight / videoWidth;
}
}
}
public static class Size {
public final int width;
public final int height;
public Size(int width, int height) {
this.width = width;
this.height = height;
}
}
3. 黑屏问题解决探讨
只要调用start就会有一定概率的黑屏毫秒
先不管测量铺满问题,我们发现会存在一个坑,就是视频黑屏问题,进入这个界面肯定要让它不黑屏的.
1.尝试过在onPrepared里面再在让VideoView显示隐藏结果没卵用
1.直接隐藏控件在方案1的基础上延长几秒,start过程中依然隐藏(不同手机需要的延长时间不同,)但是如果0秒到1秒的过程中如果没有画面动还好,如果动了,延长超过1秒后在显示此控件那么视频就需要留长 不然首帧和此时videoview显示的时间不一致,后面发现这种死办法又没法解决循环播放问题
最后的解决方法通过百度找到 是根据info的视频第一帧来判断:
mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
if (onCorveHideListener != null) {
onCorveHideListener.requestHide();
}
}
if (onInfoListener != null) {
onInfoListener.onInfo(mp, what, extra);
}
return false;
}
});
图片的缩放解决方案和视频缩放一样,你这都需要代码得话打赏一个吧,哈哈,
隐藏的方法在外面了。叫 setOnCorveHideListener ,实际上进入界面就应该马上显示画面的隐藏视频的话是一个白屏,所以这里需要
最后界面activity或者fragment代码
String VIDEO_PATH = "android.resource://" + BuildConfig.APPLICATION_ID + "/" + R.raw.login;
loginActivityBinding.videoView.setDisplayAspectRatio(MeasureUtil.ASPECT_RATIO_PAVED_PARENT);
loginActivityBinding.videoView.setOnCorveHideListener(new SystemVideoView.OnCorveHideListener() {
@Override
public void requestHide() {
loginActivityBinding.corver.setVisibility(View.GONE);
}
});
loginActivityBinding.videoView.setVideoURI(Uri.parse(Constants.VIDEO_PATH));
loginActivityBinding.videoView.start();
loginActivityBinding.videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
loginActivityBinding.videoView.seekTo(0);
loginActivityBinding.videoView.start();
}
});
@Override
public void onPause() {
super.onPause();
loginActivityBinding.videoView.pause();
}
@Override
public void onResume() {
super.onResume();
loginActivityBinding.videoView.start();
}
完整SystemVideoView代码
public class SystemVideoView extends VideoView {
private int videoWidth;//width
private int videoHeight;
private int displayAspectRatio;
public SystemVideoView(Context context) {
super(context);
}
public SystemVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SystemVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
protected void init(Context context) {
this.videoHeight = context.getResources().getDisplayMetrics().heightPixels;
this.videoWidth = context.getResources().getDisplayMetrics().widthPixels;
super.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(final MediaPlayer mp) {
SystemVideoView.this.videoWidth = mp.getVideoWidth();
SystemVideoView.this.videoHeight = mp.getVideoHeight();
mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
if (onCorveHideListener != null) {
onCorveHideListener.requestHide();
}
}
if (onInfoListener != null) {
onInfoListener.onInfo(mp, what, extra);
}
return false;
}
});
}
});
}
MediaPlayer.OnPreparedListener onPreparedListener = null;
public interface OnCorveHideListener {
void requestHide();
}
@Override
public void setOnInfoListener(MediaPlayer.OnInfoListener onInfoListener) {
this.onInfoListener = onInfoListener;
}
MediaPlayer.OnInfoListener onInfoListener;
public void setOnCorveHideListener(OnCorveHideListener onCorveHideListener) {
this.onCorveHideListener = onCorveHideListener;
}
OnCorveHideListener onCorveHideListener;
@Override
public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {
this.onPreparedListener = l;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
MeasureUtil.Size measure = MeasureUtil.measure(displayAspectRatio, widthMeasureSpec, heightMeasureSpec, videoWidth, videoHeight);
setMeasuredDimension(measure.width, measure.height);
public void setDisplayAspectRatio(int var1) {
displayAspectRatio = var1;
this.requestLayout();
}
@Override
public boolean isPlaying() {
return false;
}
public int getDisplayAspectRatio() {
return displayAspectRatio;
}
public void setCorver(int resource) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), resource, opts);
}
总结:
由于加载视频比较慢,比如会有黑屏,但是图片的加载速度所产生的黑屏人几乎感受不到,所以采用的方法是 先获取第一张视频第一帧图片 覆盖在视频的区域上面,
当视频加载回调首帧的时候隐藏,
需要解决的问题是图片的缩放方法和视频的一样,这样当图片隐藏后视频的第一帧才可以完美衔接起来.
还需要解决的问题就是 铺满全屏的不留任何空隙的算法问题。