Android使用Vitamio框架自定義視訊播放器
做過Android視訊播放器的碼農們都或多或少知道自帶的VideoView用著沒有那麼順心。需要處理很多東西。於是就各種度娘、Google。終於皇天不負苦心人。找到了一個卻又不大符合。無奈,想自己動手寫吧!又浪費時間。在這裡,附上一個採用Vitamio框架寫的視訊播放器貢獻給大夥。希望對你有用。好了。進入正題:
附上github下載地址:https://github.com/eternityzqf/VitamioTestDemo
先來個效果圖看看:
圖片可能有點糙,但執行在手機上是沒問題的。
功能點:
①:播放網路視訊
②:可以實現快取/快取載入提示
③:豎屏縮放正常畫面、橫屏縮放全屏畫面
④:滑動左邊亮度調節、滑動右邊調節聲音
⑤:自定義媒體控制畫面。增加擴充套件性。
主要類:
一、App:用於全域性初始化VItamio
/** * class from * Created by zqf * Time 2017/7/25 15:43 */ public class App extends Application { @Override public void onCreate() { super.onCreate(); //初始化一次Vitamio Vitamio.isInitialized(this); } }
二、CustomMediaController:自定義視訊播放器控制器
/** * Created by zqf on 2017/7/25. * 自定義視訊控制器 */ public class CustomMediaController extends MediaController { private static final int HIDEFRAM = 0;//控制提示視窗的顯示 private GestureDetector mGestureDetector; private ImageButton img_back;//返回按鈕 private TextView mFileName;//檔名 private VideoView videoView; private Activity activity; private Context context; private String videoname;//視訊名稱 private int controllerWidth = 0;//設定mediaController高度為了使橫屏時top顯示在螢幕頂端 private View mVolumeBrightnessLayout;//提示視窗 private ImageView mOperationBg;//提示圖片 private TextView mOperationTv;//提示文字 private AudioManager mAudioManager; private SeekBar progress; private boolean mDragging; private MediaPlayerControl player; //最大聲音 private int mMaxVolume; //當前聲音 private int mVolume = -1; //當前亮度 private float mBrightness = -1f; //返回監聽 private View.OnClickListener backListener = new View.OnClickListener() { public void onClick(View v) { if (activity != null) { activity.finish(); } } }; private View.OnClickListener scaleListener = new View.OnClickListener() { @Override public void onClick(View v) { if (activity != null) { switch (activity.getResources().getConfiguration().orientation) { case Configuration.ORIENTATION_LANDSCAPE://橫屏 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); break; case Configuration.ORIENTATION_PORTRAIT://豎屏 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); break; } } } }; private Handler myHandler = new Handler() { @Override public void handleMessage(Message msg) { long pos; switch (msg.what) { case HIDEFRAM://隱藏提示視窗 mVolumeBrightnessLayout.setVisibility(View.GONE); mOperationTv.setVisibility(View.GONE); break; } } }; private ImageView mIvScale; //videoview 用於對視訊進行控制的等,activity為了退出 public CustomMediaController(Context context, VideoView videoView, Activity activity) { super(context); this.context = context; this.videoView = videoView; this.activity = activity; WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); controllerWidth = wm.getDefaultDisplay().getWidth(); mGestureDetector = new GestureDetector(context, new MyGestureListener()); } @Override protected View makeControllerView() { //此處的 mymediacontroller 為我們自定義控制器的佈局檔名稱 View v = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(getResources().getIdentifier("mymediacontroller", "layout", getContext().getPackageName()), this); v.setMinimumHeight(controllerWidth); //獲取控制元件 img_back = (ImageButton) v.findViewById(getResources().getIdentifier("mediacontroller_top_back", "id", context.getPackageName())); mFileName = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_filename", "id", context.getPackageName())); //縮放控制元件 mIvScale = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_scale", "id", context.getPackageName())); if (mFileName != null) { mFileName.setText(videoname); } //聲音控制 mVolumeBrightnessLayout = (RelativeLayout) v.findViewById(R.id.operation_volume_brightness); mOperationBg = (ImageView) v.findViewById(R.id.operation_bg); mOperationTv = (TextView) v.findViewById(R.id.operation_tv); mOperationTv.setVisibility(View.GONE); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mMaxVolume = mAudioManager .getStreamMaxVolume(AudioManager.STREAM_MUSIC); //註冊事件監聽 img_back.setOnClickListener(backListener); mIvScale.setOnClickListener(scaleListener); return v; } @Override public boolean dispatchKeyEvent(KeyEvent event) { System.out.println("MYApp-MyMediaController-dispatchKeyEvent"); return true; } @Override public boolean onTouchEvent(MotionEvent event) { if (mGestureDetector.onTouchEvent(event)) return true; // 處理手勢結束 switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_UP: endGesture(); break; } return super.onTouchEvent(event); } /** * 手勢結束 */ private void endGesture() { mVolume = -1; mBrightness = -1f; // 隱藏 myHandler.removeMessages(HIDEFRAM); myHandler.sendEmptyMessageDelayed(HIDEFRAM, 1); } private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { return false; } /** * 因為使用的是自定義的mediaController 當顯示後,mediaController會鋪滿螢幕, * 所以VideoView的點選事件會被攔截,所以重寫控制器的手勢事件, * 將全部的操作全部寫在控制器中, * 因為點選事件被控制器攔截,無法傳遞到下層的VideoView, * 所以 原來的單機隱藏會失效,作為代替, * 在手勢監聽中onSingleTapConfirmed()新增自定義的隱藏/顯示, * * @param e * @return */ @Override public boolean onSingleTapConfirmed(MotionEvent e) { //當手勢結束,並且是單擊結束時,控制器隱藏/顯示 toggleMediaControlsVisiblity(); return super.onSingleTapConfirmed(e); } @Override public boolean onDown(MotionEvent e) { return true; } //滑動事件監聽 @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { float mOldX = e1.getX(), mOldY = e1.getY(); int y = (int) e2.getRawY(); int x = (int) e2.getRawX(); Display disp = activity.getWindowManager().getDefaultDisplay(); int windowWidth = disp.getWidth(); int windowHeight = disp.getHeight(); if (mOldX > windowWidth * 3.0 / 4.0) {// 右邊滑動 螢幕 3/4 onVolumeSlide((mOldY - y) / windowHeight); } else if (mOldX < windowWidth * 1.0 / 4.0) {// 左邊滑動 螢幕 1/4 onBrightnessSlide((mOldY - y) / windowHeight); } return super.onScroll(e1, e2, distanceX, distanceY); } @Override public boolean onDoubleTap(MotionEvent e) { playOrPause(); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return super.onFling(e1, e2, velocityX, velocityY); } } /** * 滑動改變聲音大小 * * @param percent */ private void onVolumeSlide(float percent) { if (mVolume == -1) { mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (mVolume < 0) mVolume = 0; // 顯示 mVolumeBrightnessLayout.setVisibility(View.VISIBLE); mOperationTv.setVisibility(VISIBLE); } int index = (int) (percent * mMaxVolume) + mVolume; if (index > mMaxVolume) index = mMaxVolume; else if (index < 0) index = 0; if (index >= 10) { mOperationBg.setImageResource(R.drawable.volmn_100); } else if (index >= 5 && index < 10) { mOperationBg.setImageResource(R.drawable.volmn_60); } else if (index > 0 && index < 5) { mOperationBg.setImageResource(R.drawable.volmn_30); } else { mOperationBg.setImageResource(R.drawable.volmn_no); } //DecimalFormat df = new DecimalFormat("######0.00"); mOperationTv.setText((int) (((double) index / mMaxVolume) * 100) + "%"); // 變更聲音 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0); } /** * 滑動改變亮度 * * @param percent */ private void onBrightnessSlide(float percent) { if (mBrightness < 0) { mBrightness = activity.getWindow().getAttributes().screenBrightness; if (mBrightness <= 0.00f) mBrightness = 0.50f; if (mBrightness < 0.01f) mBrightness = 0.01f; // 顯示 mVolumeBrightnessLayout.setVisibility(View.VISIBLE); mOperationTv.setVisibility(VISIBLE); } WindowManager.LayoutParams lpa = activity.getWindow().getAttributes(); lpa.screenBrightness = mBrightness + percent; if (lpa.screenBrightness > 1.0f) lpa.screenBrightness = 1.0f; else if (lpa.screenBrightness < 0.01f) lpa.screenBrightness = 0.01f; activity.getWindow().setAttributes(lpa); mOperationTv.setText((int) (lpa.screenBrightness * 100) + "%"); if (lpa.screenBrightness * 100 >= 90) { mOperationBg.setImageResource(R.drawable.light_100); } else if (lpa.screenBrightness * 100 >= 80 && lpa.screenBrightness * 100 < 90) { mOperationBg.setImageResource(R.drawable.light_90); } else if (lpa.screenBrightness * 100 >= 70 && lpa.screenBrightness * 100 < 80) { mOperationBg.setImageResource(R.drawable.light_80); } else if (lpa.screenBrightness * 100 >= 60 && lpa.screenBrightness * 100 < 70) { mOperationBg.setImageResource(R.drawable.light_70); } else if (lpa.screenBrightness * 100 >= 50 && lpa.screenBrightness * 100 < 60) { mOperationBg.setImageResource(R.drawable.light_60); } else if (lpa.screenBrightness * 100 >= 40 && lpa.screenBrightness * 100 < 50) { mOperationBg.setImageResource(R.drawable.light_50); } else if (lpa.screenBrightness * 100 >= 30 && lpa.screenBrightness * 100 < 40) { mOperationBg.setImageResource(R.drawable.light_40); } else if (lpa.screenBrightness * 100 >= 20 && lpa.screenBrightness * 100 < 20) { mOperationBg.setImageResource(R.drawable.light_30); } else if (lpa.screenBrightness * 100 >= 10 && lpa.screenBrightness * 100 < 20) { mOperationBg.setImageResource(R.drawable.light_20); } } /** * 設定視訊檔名 * * @param name */ public void setVideoName(String name) { videoname = name; if (mFileName != null) { mFileName.setText(name); } } /** * 隱藏或顯示 */ private void toggleMediaControlsVisiblity() { if (isShowing()) { hide(); } else { show(); } } /** * 播放/暫停 */ private void playOrPause() { if (videoView != null) if (videoView.isPlaying()) { videoView.pause(); } else { videoView.start(); } } }
自定義控制器類主要是最播放、暫停、播放時間、全屏的一些控制元件介面封裝;通過在MainActivity裡面的
new CustomMediaController();將VideoView傳進來進行一些操作。但播放的相關還是放在主介面裡面操作的。
可在裡面更改介面以適應需求。SeekBar樣式都將採用自定義的。增加擴充套件。
涉及的公共方法:
setVideoName();設定視訊名稱
當然還有什麼新增喜歡,收藏子類的。你們都可以自己新增。
三、ManActivity:主介面
/**
* class from 主介面
* Created by zqf
* Time 2017/7/25 15:43
*/
public class MainActivity extends Activity implements MediaPlayer.OnInfoListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener {
private String video_path = "http://baobab.wdjcdn.com/145076769089714.mp4";
private Uri mUri;
private ProgressBar pb;
private TextView downloadRateView, loadRateView;
private CustomMediaController mCustomMediaController;
private VideoView mVideoView;
public static long mCurrent_position = 0;//當前播放的位置
public static final int VIDEO_LAYOUT_ORIGIN = 0;//縮放參數,原始畫面大小0。
public static final int VIDEO_LAYOUT_SCALE = 1;//縮放參數,畫面全屏1。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//定義全屏引數
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
//獲得當前窗體物件
Window window = MainActivity.this.getWindow();
//設定當前窗體為全屏顯示
window.setFlags(flag, flag);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {
mUri = Uri.parse(video_path);//將地址轉化為Uri
mVideoView.setVideoURI(mUri);//設定播放視訊的地址
mCustomMediaController.show(5000);//設定顯示時間差
mVideoView.setMediaController(mCustomMediaController);//設定媒體控制器。
mVideoView.setVideoQuality(MediaPlayer.VIDEOQUALITY_HIGH);//設定畫質
mVideoView.requestFocus();//獲取焦點
mVideoView.setBufferSize(512 * 1024);//設定緩衝大小(單位Byte)
/**
* 監聽在有警告或錯誤資訊時呼叫。例如:開始緩衝、緩衝結束、下載速度變化。
*/
mVideoView.setOnInfoListener(this);
/**
* 監聽在網路視訊流緩衝變化時呼叫。
*/
mVideoView.setOnBufferingUpdateListener(this);
/**
* 視訊播放完成後呼叫。
*/
mVideoView.setOnCompletionListener(this);
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
/**
* 在視訊預處理完成後呼叫。在視訊預處理完成後被呼叫。
* 此時視訊的寬度、高度、寬高比資訊已經獲取到,
* 此時可呼叫seekTo讓視訊從指定位置開始播放。
*/
mp.setPlaybackSpeed(1.0f);
}
});
}
private void initView() {
mVideoView = (VideoView) findViewById(R.id.vitamio_video);
mCustomMediaController = new CustomMediaController(this, mVideoView, this);
mCustomMediaController.setVideoName("此處可以設定視訊名稱");
pb = (ProgressBar) findViewById(R.id.probar);
downloadRateView = (TextView) findViewById(R.id.download_rate);
loadRateView = (TextView) findViewById(R.id.load_rate);
}
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
//開始快取事,執行暫停播放、載入、下載、快取控制元件可見
if (mVideoView.isPlaying()) {
Log.e("Tag", what + "---快取flag----");
mVideoView.pause();
pb.setVisibility(View.VISIBLE);
downloadRateView.setVisibility(View.VISIBLE);
loadRateView.setVisibility(View.VISIBLE);
}
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
//快取完成,執行繼續播放;載入、下載、快取控制元件不可見
mVideoView.start();
pb.setVisibility(View.GONE);
downloadRateView.setVisibility(View.GONE);
loadRateView.setVisibility(View.GONE);
break;
case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED:
//快取時顯示下載速度
//此時下載速度應該實時獲取手機的網速。這以kb/s代替
Log.e("Tag", what + "----下載flag---" + extra);
downloadRateView.setText(extra + "kb/s" + "");
break;
}
return true;
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
loadRateView.setText("緩衝" + percent + "%");
Log.e("Tag", "+++++++" + percent);
}
@Override
protected void onResume() {
super.onResume();
Log.e("Tag", "onResume");
if (mCurrent_position != 0) {
mVideoView.seekTo(mCurrent_position);
mVideoView.start();
}
}
@Override
protected void onPause() {
super.onPause();
Log.e("Tag", "onPause");
mCurrent_position = mVideoView.getCurrentPosition();
Log.e("Tag", mCurrent_position + "");
if (mVideoView.isPlaying()) {
mVideoView.pause();
}
}
@Override
protected void onStop() {
super.onStop();
Log.e("Tag", "onStop");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e("Tag", "onRestoreInstanceState");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.e("Tag", "onSaveInstanceState");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("Tag", "onDestroy");
//停止視訊播放,並釋放資源。
mVideoView.stopPlayback();
mCurrent_position = 0;
}
/**
* 視訊播放完成後呼叫。
*
* @param mp the MediaPlayer that reached the end of the file
*/
@Override
public void onCompletion(MediaPlayer mp) {
mCurrent_position = 0;
}
/**
* getRequestedOrientation獲取橫豎屏標誌
* -1 || 1--->豎屏
* 0 --->橫屏
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Log.e("Tag", "返回鍵。。。。" + getRequestedOrientation());
int orient = getRequestedOrientation();
if (orient == -1 || orient == 1) {
finish();
} else if (orient == 0) {
//切換為豎屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
return false;
}
return super.onKeyDown(keyCode, event);
}
/**
* 螢幕切換時
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.e("Tag", newConfig.orientation + "");
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
//豎屏-->顯示原始畫面
if (mVideoView != null) {
mVideoView.setVideoLayout(VIDEO_LAYOUT_ORIGIN, 0);
}
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
//橫屏-->顯示全屏畫面
if (mVideoView != null) {
mVideoView.setVideoLayout(VIDEO_LAYOUT_SCALE, 0);
}
}
super.onConfigurationChanged(newConfig);
}
}
主介面裡面
一、
先看看Vitamio的VideoView都帶有什麼功能;
設定快取大小(單位Byte):
mVideoView.setBufferSize(512 * 1024);
監聽在有警告或錯誤資訊時呼叫。例如:開始緩衝、緩衝結束、下載速度變化:
mVideoView.setOnInfoListener(this);
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
//開始快取事,執行暫停播放、載入、下載、快取控制元件可見
if (mVideoView.isPlaying()) {
Log.e("Tag", what + "---快取flag----");
mVideoView.pause();
pb.setVisibility(View.VISIBLE);
downloadRateView.setVisibility(View.VISIBLE);
loadRateView.setVisibility(View.VISIBLE);
}
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
//快取完成,執行繼續播放;載入、下載、快取控制元件不可見
mVideoView.start();
pb.setVisibility(View.GONE);
downloadRateView.setVisibility(View.GONE);
loadRateView.setVisibility(View.GONE);
break;
case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED:
//快取時顯示下載速度
//此時下載速度應該實時獲取手機的網速。這以kb/s代替
Log.e("Tag", what + "----下載flag---" + extra);
downloadRateView.setText(extra + "kb/s" + "");
break;
}
return true;
}
監聽在網路視訊流緩衝變化時呼叫:
mVideoView.setOnBufferingUpdateListener(this);
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
loadRateView.setText("緩衝" + percent + "%");
Log.e("Tag", "+++++++" + percent);
}
........
二、手機Home切換到候後臺時我們需要在生命週期裡面記錄和恢復播放位置:
記錄:
@Override
protected void onPause() {
super.onPause();
Log.e("Tag", "onPause");
mCurrent_position = mVideoView.getCurrentPosition();
Log.e("Tag", mCurrent_position + "");
if (mVideoView.isPlaying()) {
mVideoView.pause();
}
}
恢復:
@Override
protected void onResume() {
super.onResume();
Log.e("Tag", "onResume");
if (mCurrent_position != 0) {
mVideoView.seekTo(mCurrent_position);
mVideoView.start();
}
}
當然我們還需要在ConfigurationChanged裡面改一下;
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.e("Tag", newConfig.orientation + "");
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
//豎屏-->顯示原始畫面
if (mVideoView != null) {
mVideoView.setVideoLayout(VIDEO_LAYOUT_ORIGIN, 0);
}
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
//橫屏-->顯示全屏畫面
if (mVideoView != null) {
mVideoView.setVideoLayout(VIDEO_LAYOUT_SCALE, 0);
}
}
super.onConfigurationChanged(newConfig);
}
差不多就這些,有些細節就沒貼出來。可以去github下來看看。歡迎各位star和fork。
參考的Vitamio官網地址:https://www.vitamio.org/