Android VideoView播放網路視訊簡介(轉)
最近專案中用到了很多視訊播放的地方,不管是聊天傳送的視訊訊息,還是類似內涵段子的視訊列表,都會涉及這些知識,不過網上的知識都很零散,一會找快取方法,一會找預覽圖片的方法,一會找視訊動態修改尺寸的方法,總之找的人好煩,所以自己寫一篇來記錄這些知識點,也方便別人查閱
獲取視訊首幀當預覽圖(MediaMetadataRetriever)
在VideoView中,如果直接設定播放路徑,然後seekTo(1)當然也能產生預覽效果,但是,如果VideoView較多,設定播放路徑的方法會產生幾個問題,設定路徑後VideoView會取網上拉取視訊(緩衝池大小),這樣造成流量浪費,而且,多個VideoView會造成顯示首幀非常非常慢,且有嚴重的卡頓
那如何解決這個問題,我的想法是,還是用首幀當預覽圖,不過我是在ImageView裡面顯示預覽圖,所以預覽的時候不用VideoView了,獲取預覽圖也是變的簡單化,省流量,還快捷,下來我們瞭解下MediaMetadataRetriever類如何獲取視訊的首幀。MediaMetadataRetriever類不但可以獲取視訊首幀,還可以獲取標題,時長,作者等資訊,大家根據需要可以獲取,我在這裡就不一一舉例,在獲取到首幀後,我們做下快取處理,以便下一次預覽不用每次從網上拉取,然後用Glide載入顯示
ThreadPoolUtils.execute(new Runnable() {
@Override
public void run() {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bitmap = null;
try {
//這裡要用FileProvider獲取的Uri
if (url.contains("http")) {
retriever.setDataSource(url, new HashMap<String, String>());
} else {
retriever.setDataSource(url);
}
bitmap = retriever.getFrameAtTime();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
showImageMessage(bitmap, positionTag, vv);
}
});
後記:其實還有一種辦法來做預覽顯示,就是讓後臺將預覽圖處理好,然後拿到圖片地址直接用Glide顯示,都不用自己快取,而且後臺可以生成GIF,也可以用Glide顯示,且顯得高大上
預覽圖載入完畢後,點選預覽圖,然後我們可以做各種處理,如隱藏ImageView且顯示VideoView,或者跳到視訊播放介面等,各種載入邏輯大家可以發揮自己得想象
VideoView載入一個網路視訊
VideoView載入視訊其實很簡單,我們直接看程式碼吧
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<VideoView
android:id="@+id/mVideoView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
/**
* 香港衛視:http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8
* CCTV1高清:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
* CCTV3高清:http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8
* CCTV5高清:http://ivi.bupt.edu.cn/hls/cctv5hd.m3u8
* CCTV5+高清:http://ivi.bupt.edu.cn/hls/cctv5phd.m3u8
* CCTV6高清:http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8
* 蘋果提供的測試源(點播):http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8
*/
private void initView() {
String url="http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8";
VideoView videoView=findViewById(R.id.mVideoView);
videoView.setVideoPath(url);
videoView.requestFocus();
videoView.start();
}
這樣一個網路視訊就可以播放了
視訊控制元件長寬的大小調整
視訊是播放出來了,怎麼看都有點不和諧,大白邊框太醜了,那縮小吧,一不小心縮變形了,看起來更彆扭,怎麼才能按照視訊的比例來顯示呢?我們上面不是講過MediaMetadataRetriever嗎?我們可以根據獲取的首幀圖片的大小確定視訊的大小,MediaMetadataRetriever還可以採用
int videoWidth=retriever.METADATA_KEY_VIDEO_WIDTH;
int videoHeight=retriever.METADATA_KEY_VIDEO_HEIGHT;
來確定視訊的大小。從而動態設定VideoView的大小,咦,設定那麼大的控制元件,怎麼才顯示那麼小的視訊?哈哈,問題來了,小視訊怎麼動態適配控制元件大小?
小視訊適配大控制元件(動態調整視訊顯示的大小)
不說原理了,我也是百度的,普通的LayoutParams只能調整控制元件的大小,當視訊比控制元件小時,視訊只能顯示大預設大小,可是怎麼來調整呢?請看程式碼↓
自定義VideoView控制元件CustomVideoView.java
/**
* @author Created by MrRight on 2017/10/24.
*/
public class CustomVideoView extends VideoView{
private Context mContext;
final int defaultHeight=200; //單位DP
public CustomVideoView(Context context) {
super(context);
mContext=context;
}
public CustomVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
}
public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext=context;
}
//widthMeasureSpec 和 heightMeasureSpec的值 由父容器決定
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super .onMeasure(widthMeasureSpec,heightMeasureSpec);
// 預設高度,為了自動獲取到focus
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width;
// 這個之前是預設的拉伸影象
if (this.width > 0 && this.height > 0) {
width = this.width;
height = this.height;
}
setMeasuredDimension(width, height);
}
private int width,height;
public void setMeasure(int width, int height) {
this.width = width;
this.height = height;
}
}
怎麼用呢!!很簡單,繼續看程式碼↓
videoViewParent.post(new Runnable() {
@Override
public void run() {
int[] widthAndHeight=getWidthAndHeight(holder.videoViewParent,dynamicsBean.getWeight(),dynamicsBean.getHeight());
videoView.getHolder().setFixedSize(widthAndHeight[0], widthAndHeight[1]);
// 重繪VideoView大小,這個方法是在重寫VideoView時對外丟擲方法
videoView.setMeasure(widthAndHeight[0], widthAndHeight[1]);
// 請求調整
videoView.requestLayout();
}
});
就這樣,視訊可以按你的需求行進動態調整了!!
VideoView的常用監聽和作用
VideoView有好多監聽,真的是好多,許多監聽是重複的,至於怎麼重複的?為什麼重複?有興趣的自己去看看!首先看第一個非常重要的一個監聽:點選事件和雙擊事件的監聽,你們有沒有試過設定OnClick事件?是不是沒有什麼用啊?沒用就對了,點選事件的正確姿勢是↓↓↓
/*
* 對VideoView setOnClickListener時,發現無效,搜尋一番後找到解決方案;
* 同時監聽VideoView的點選雙擊和滑動事件,通過對VideoView的OnTouchListener設定進行監聽,
* 首先例項化一個手勢識別器,並返回它的onTouchEvent。
* 然後初始化GestureDetector ,這裡面有一個坑,如果單純的設定OnGestureListener,發現當onDown的返回值為true的
* 時候可以響應單擊長摁和滑動事件,為false的時候只會響應長摁事件;如果想要監聽雙擊事件,就要對GestureDetector設
* 置OnDoubleTapListener,需要注意的的是,在OnGestureListener的onDown返回值為false的時候OnDoubleTapListener
* 裡面所有的回撥是不會去響應的
*/
holder.videoView.setOnTouchListener(new View.OnTouchListener() {
GestureDetector mGesture;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mGesture == null) {
mGesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
//返回false的話只能響應長摁事件
return true;
}
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
}
});
mGesture.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
controlLayoutShowAndHiden(holder.controlLayout,holder.cancheImage);
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
});
}
return mGesture.onTouchEvent(event);
}
});
OK!點選事件看完之後,我們看下剩下的其他的監聽方法,剩下的比較簡單,光看名字就知道是幹什麼用的,我們只寫下方法和作用,不再贅述
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
if(what==MediaPlayer.MEDIA_ERROR_UNKNOWN //未指定的媒體播放器錯誤。
||what==MediaPlayer.MEDIA_ERROR_SERVER_DIED //媒體伺服器死了。在這種情況下,應用程式必須釋放
||what==MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK//視訊流,其容器對逐行掃描無效。
||what==MediaPlayer.MEDIA_ERROR_MALFORMED//檔案或網路操作錯誤
||what==MediaPlayer.MEDIA_ERROR_UNSUPPORTED//位元流符合相關的編碼標準或檔案規範,但 媒體框架不支援該功能。
||what==MediaPlayer.MEDIA_ERROR_TIMED_OUT//超時
||what==MediaPlayer.MEDIA_ERROR_IO){ //IO劉錯誤
if(controlImageBig.getVisibility()==View.VISIBLE){
controlImageBig.setBackgroundResource(R.drawable.vodeo_retry);
}
}
return true;//如果設定true就可以防止他彈出錯誤的提示框!
}
});
videoView.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what==MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START){
controlImageBig.setVisibility(View.GONE);
cancheImage.setVisibility(View.GONE);
controlImageBig.setBackgroundResource(R.drawable.eventdynamics_play_big);
}
LogUtils.i(TAG,"\n extra is "+extra
+"\n what is "+what);
return false;
}
});
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(TAG,"onPrepared methmod is called and position is "+position);
int duration=holder.videoView.getDuration();
totleTime.setText(intTimeToString(duration));
seekBar.setMax(duration);
videoViewParent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
controlLayoutShowAndHiden(holder.controlLayout,holder.cancheImage);
}
});
}
});
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
threadPoolUtils.shutDownNow();
if(cancheImage.getVisibility()==View.GONE){
cancheImage.setVisibility(View.VISIBLE);
}
if(controlImageBig.getVisibility()==View.GONE){
controlImageBig.setVisibility(View.VISIBLE);
}
playControl.setImageResource(R.drawable.eventdynamics_play);
seekBar.setProgress(0);
}
});
好了,大概就這麼多,後續有新東西還會持續更新,大家有什麼好的建議也可以留言交流
---------------------
作者:baoolong
來源:CSDN
原文:https://blog.csdn.net/baoolong/article/details/79607393
版權宣告:本文為博主原創文章,轉載請附上博文連結!