分析qq7.0:
視頻在打開登錄界面就開始播放 了,而且期間無黑屏
而且是循環(huán)播放的
畫質問題這里就不說了,這個看視頻源了。
可以讓不規(guī)則的寬高各種寬高不定的視頻比例 以及視頻大小都能 適應任意安卓手機的寬高 包括平板,且不留任何縫隙
播放器控件選取:解決的是手機適配的問題,另外是播放器控件,這里選擇系統(tǒng)播放器比較好. 因為有些播放器不支持讀取asset文件夾的Uri比如七牛的
視頻加載速度比較慢第一幀用圖片代替且需要耦合視頻的第一幀
圖片的第一幀截取我用的是一個比較專業(yè)的adobe premiere的開發(fā)工具 這個你們也可以讓ps等后期的去做,這種事情對我來說的話還是小kiss,
技術點:
如何讀取資源文件視頻
如何測量
如何根據(jù)視頻大小計算應該縮放的比例大小 解決任意尺寸視頻手機不留黑邊
如何讓圖片的封面縮放大小和視頻的縮放大小吻合
如何調用onStart短暫黑屏問題
架構搭建
資源的讀取
String VIDEO_PATH = "android.resource://" + BuildConfig.APPLICATION_ID + "/" + R.raw.login;
videoView.setVideoURI(Uri.parse(Constants.VIDEO_PATH));
創(chuàng)建一個自定義視頻類 自定義圖片類 圖片在視頻的上面因為視頻不是馬上播放 加載有一定時間這里也會存在一個黑屏
關于讀取視頻的問題,之前嘗試過讀取assests里面的視頻失敗了,在stackoverflow照的方案也不行,最后還是把視頻放到和res/raw文件夾里面了,
具體實現(xiàn)之視頻控件
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就會有一定概率的黑屏毫秒
先不管測量鋪滿問題,我們發(fā)現(xiàn)會存在一個坑,就是視頻黑屏問題,進入這個界面肯定要讓它不黑屏的.
1.嘗試過在onPrepared里面再在讓VideoView顯示隱藏結果沒卵用
1.直接隱藏控件在方案1的基礎上延長幾秒,start過程中依然隱藏(不同手機需要的延長時間不同,)但是如果0秒到1秒的過程中如果沒有畫面動還好,如果動了,延長超過1秒后在顯示此控件那么視頻就需要留長 不然首幀和此時videoview顯示的時間不一致,后面發(fā)現(xiàn)這種死辦法又沒法解決循環(huán)播放問題
最后的解決方法通過百度找到 是根據(jù)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);
}
總結:
由于加載視頻比較慢,比如會有黑屏,但是圖片的加載速度所產生的黑屏人幾乎感受不到,所以采用的方法是 先獲取第一張視頻第一幀圖片 覆蓋在視頻的區(qū)域上面,
當視頻加載回調首幀的時候隱藏,
需要解決的問題是圖片的縮放方法和視頻的一樣,這樣當圖片隱藏后視頻的第一幀才可以完美銜接起來.
還需要解決的問題就是 鋪滿全屏的不留任何空隙的算法問題。