轉(zhuǎn)載注明出處:
http://blog.csdn.net/u010181592/article/category/5893483
文章出自 我不只是看客/NotLooker的博客
先列出參考資料:
Vitamio 官網(wǎng):http://www.vitamio.org(不太穩(wěn)定,時(shí)常打不開) 農(nóng)民伯伯 博客:http://www.cnblogs.com/over140/category/409230.html(開發(fā)者之一,博客中有部分Vitamio中文API)
轉(zhuǎn)載注明出處:http://blog.csdn.net/u010181592/article/category/5893483
經(jīng)過準(zhǔn)備和第一個(gè)Demo之后,需要對(duì)視頻進(jìn)行更加精細(xì)的DIY來適合自己的項(xiàng)目。首先,視頻播放界面給大家直觀的感受就是 有進(jìn)度條,聲音,按鈕等的控制條了,Vitamio中提供了一個(gè)基礎(chǔ)的樣例,我們根據(jù)自己的需要進(jìn)行修改; 先來看一下Vitamio自帶的 MediaController 因?yàn)槠木壒?#xff0c;就不上源碼。只列出介紹和常用的函數(shù):
/** * A view containing controls for a MediaPlayer. Typically contains the buttons * like “Play/Pause” and a progress slider. It takes care of synchronizing the * controls with the state of the MediaPlayer. *
* The way to use this class is to a) instantiate it programatically or b) * create it in your xml layout. *
* a) The MediaController will create a default set of controls and put them in * a window floating above your application. Specifically, the controls will * float above the view specified with setAnchorView(). By default, the window * will disappear if left idle for three seconds and reappear when the user * touches the anchor view. To customize the MediaController’s style, layout and * controls you should extend MediaController and override the {#link * {@link #makeControllerView()} method. *
* b) The MediaController is a FrameLayout, you can put it in your layout xml * and get it through {@link #findViewById(int)}. *
* NOTES: In each way, if you want customize the MediaController, the SeekBar’s * id must be mediacontroller_progress, the Play/Pause’s must be * mediacontroller_pause, current time’s must be mediacontroller_time_current, * total time’s must be mediacontroller_time_total, file name’s must be * mediacontroller_file_name. And your resources must have a pause_button * drawable and a play_button drawable. *
* Functions like show() and hide() have no effect when MediaController is * created in an xml layout. */
博主的英文不是太愛好,看介紹也就能理解的意思大概是 控制器是一個(gè)包含了MediaPlayer(視頻播放核心組件)的視圖控件,會(huì)有常用的按鈕和功能。使用方法有2種: a)創(chuàng)建一個(gè)xml布局,MediaController將會(huì)把他們放到 漂浮在應(yīng)用上的窗口(看代碼后發(fā)現(xiàn)就是PopWindow) 默認(rèn)顯示時(shí)間3s,點(diǎn)擊視圖可控制顯示/消失 要自定義MediaController時(shí)需要繼承該類并覆蓋makeControllerView()方法 b)直接把它放到你的布局文件中 <com.test.myapplication.MyMediaController android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content"/> </com.test.myapplication.MyMediaController> 無論使用哪種方法, SeekBar的ID必須是mediacontroller_progress 播放/暫停的必須是 mediacontroller_pause 顯示當(dāng)前時(shí)間 mediacontroller_time_current 顯示總時(shí)長 mediacontroller_time_total 播放文件名 mediacontroller_file_name 而且必須有 pause_button和play_button 的資源(個(gè)人沒能理解這條,很多播放器設(shè)計(jì)已經(jīng)用點(diǎn)擊屏幕代替掉了開始暫停實(shí)體鍵,博主測(cè)試的時(shí)候發(fā)現(xiàn),即使自己的布局沒有開始按鈕,通過雙擊仍然可以暫停開始) 如果你使用第二種方法 那么hide()和show()無效;
下邊是常用方法的中文API,引用自農(nóng)民伯伯的博客
public void onFinishInflate() 從XML加載完所有子視圖后調(diào)用。初始化控制視圖(調(diào)用initControllerView方法,設(shè)置事件、綁定控件和設(shè)置默認(rèn)值)。
public void setAnchorView(View view) 設(shè)置MediaController綁定到一個(gè)視圖上。例如可以是一個(gè)VideoView對(duì)象,或者是你的activity的主視圖。 參數(shù) view 可見時(shí)綁定的視圖
public void setMediaPlayer(MediaPlayerControl player) 設(shè)置媒體播放器。并更新播放/暫停按鈕狀態(tài)。
public void setInstantSeeking(boolean seekWhenDragging) 設(shè)置用戶拖拽SeekBar時(shí)畫面是否跟著變化。(VPlayer默認(rèn)完成操作后再更新畫面)
public void show() 顯示MediaController。默認(rèn)顯示3秒后自動(dòng)隱藏。
public void show(int timeout) 顯示MediaController。在timeout毫秒后自動(dòng)隱藏。 參數(shù) timeout 超時(shí)時(shí)間,單位毫秒。為0時(shí)控制條的hide()將被調(diào)用。
public void setFileName(String name) 設(shè)置視頻文件名稱。
public void setInfoView(OutlineTextView v) 設(shè)置保存MediaController的操作信息。例如進(jìn)度改變時(shí)更新v。
public void setAnimationStyle(int animationStyle) 更改MediaController的動(dòng)畫風(fēng)格。 如果MediaController正在顯示,調(diào)用此方法將在下一次顯示時(shí)生效。 參數(shù) animationStyle 在MediaController顯示或隱藏時(shí)使用的動(dòng)畫風(fēng)格。設(shè)置-1為默認(rèn)風(fēng)格,0沒有動(dòng)畫,或設(shè)置一個(gè)明確的動(dòng)畫資源。
public boolean isShowing() 獲取MediaController是否已經(jīng)顯示。
public void hide() 隱藏MediaController。
public void setOnShownListener(OnShownListener l) 注冊(cè)一個(gè)回調(diào)函數(shù),在MediaController顯示后被調(diào)用。
public void setOnHiddenListener(OnHiddenListener l) 注冊(cè)一個(gè)回調(diào)函數(shù),在MediaController隱藏后被調(diào)用。
public boolean onTouchEvent(MotionEvent event) 調(diào)用show()并返回true。
public boolean onTrackballEvent(MotionEvent ev) 調(diào)用show()并返回false。
public void setEnabled(boolean enabled) 設(shè)置MediaController的可用狀態(tài)。包括進(jìn)度條和播放/暫停按鈕。
受保護(hù)方法 protected View makeControllerView() 創(chuàng)建控制播放的布局視圖。子類可重寫此方法創(chuàng)建自定義視圖。
例如我們要實(shí)現(xiàn)如下一個(gè)視頻播放器: 它有一個(gè)自定義的標(biāo)題欄,并且把文件名放到了視頻的頂端; 在標(biāo)題欄的左邊有一個(gè)返回按鈕,右邊分別由電池電量顯示和時(shí)間信息; 很多人開始做的時(shí)候會(huì)做這種布局會(huì)把標(biāo)題欄寫在播放界面VideoView的布局上,就像這樣: 這樣標(biāo)題欄和控制器的邏輯就是分開的,有2個(gè)弊端 : - 1 在監(jiān)聽手指點(diǎn)擊屏幕相應(yīng)事件中你不能完美的使 標(biāo)題欄和控制欄同時(shí)出現(xiàn)/消失,在點(diǎn)擊時(shí)總要判斷控制欄的show/hide - 2 當(dāng)你需要在控制器增加功能性按鈕(如清晰度設(shè)置,劇集選擇時(shí),你就要重寫控制器或者直接去修改庫中的源代碼,總感覺第二種行為不太好)
既然以后對(duì)控制器的要求會(huì)更高不如從現(xiàn)在開始就直接自己定義,方便以后改寫; so ~ 重寫開始: 首先看一下布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="34dp"android:background="#77000000"><ImageButtonandroid:id="@+id/mediacontroller_top_back"android:layout_width="50dp"android:layout_height="match_parent"android:layout_alignParentStart="true"android:background="@null"android:src="@drawable/btn_nav_back_n"/><TextViewandroid:id="@+id/mediacontroller_file_name"style="@style/MediaController_Text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="5dp"android:layout_toRightOf="@+id/mediacontroller_top_back"android:ellipsize="marquee"android:singleLine="true"android:text="file name"/><TextViewandroid:id="@+id/mediacontroller_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_marginRight="5dp"android:text="17:22"android:textColor="#ffffff"android:textSize="15sp"/><ImageViewandroid:id="@+id/mediacontroller_imgBattery"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginRight="5dp"android:layout_toLeftOf="@+id/mediacontroller_time"android:gravity="center_vertical"android:src="@drawable/battery"/><TextViewandroid:id="@+id/mediacontroller_Battery"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginRight="-10dp"android:layout_toLeftOf="@+id/mediacontroller_imgBattery"android:gravity="center_vertical"android:text="45%"android:textColor="#ffffff"android:textSize="15sp"/></RelativeLayout><RelativeLayoutandroid:id="@+id/rl_med"android:layout_width="match_parent"android:layout_height="50dp"android:layout_alignParentBottom="true"android:background="#77000000"><ImageButtonandroid:id="@+id/mediacontroller_play_pause"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="5dp"android:background="@drawable/mediacontroller_button"android:contentDescription="@string/mediacontroller_play_pause"android:src="@drawable/mediacontroller_pause"/><TextViewandroid:id="@+id/mediacontroller_time_current"style="@style/MediaController_Text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="5dp"android:layout_toRightOf="@id/mediacontroller_play_pause"android:text="33:33:33"/><TextViewandroid:id="@+id/mediacontroller_time_total"style="@style/MediaController_Text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_marginRight="5dp"android:text="33:33:33"/><SeekBarandroid:id="@+id/mediacontroller_seekbar"style="@style/MediaController_SeekBar"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_toLeftOf="@id/mediacontroller_time_total"android:layout_toRightOf="@id/mediacontroller_time_current"android:focusable="true"android:max="1000"/></RelativeLayout>
</RelativeLayout>
` 把需要顯示的標(biāo)題欄 和控制器整合到了一起,這樣可以完美配合; 下面是自己的控制器類 MyMediaController:
public class MyMediaController extends MediaController {private GestureDetector mGestureDetector;
private ImageButton img_back;//返回鍵
private ImageView img_Battery;//電池電量顯示
private TextView textViewTime;//時(shí)間提示
private TextView textViewBattery;//文字顯示電池
private VideoView videoView;
private Activity activity;
private Context context;
private int controllerWidth = 0;//設(shè)置mediaController高度為了使橫屏?xí)rtop顯示在屏幕頂端//返回監(jiān)聽
private View.OnClickListener backListener = new View.OnClickListener() {public void onClick(View v) {if(activity != null){activity.finish();}}
};
//videoview 用于對(duì)視頻進(jìn)行控制的等,activity為了退出
public MyMediaController(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() {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()));img_Battery = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_imgBattery", "id", context.getPackageName()));img_back.setOnClickListener(backListener);textViewBattery = (TextView)v.findViewById(getResources().getIdentifier("mediacontroller_Battery", "id", context.getPackageName()));textViewTime = (TextView)v.findViewById(getResources().getIdentifier("mediacontroller_time", "id", context.getPackageName()));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;// 處理手勢(shì)結(jié)束switch (event.getAction() & MotionEvent.ACTION_MASK) {case MotionEvent.ACTION_UP:break;}return super.onTouchEvent(event);
}private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {@Overridepublic boolean onSingleTapUp(MotionEvent e) {return false;}@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) {//當(dāng)收拾結(jié)束,并且是單擊結(jié)束時(shí),控制器隱藏/顯示toggleMediaControlsVisiblity();return super.onSingleTapConfirmed(e);}@Overridepublic boolean onDown(MotionEvent e) {return true;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {return super.onScroll(e1, e2, distanceX, distanceY);}//雙擊暫停或開始@Overridepublic boolean onDoubleTap(MotionEvent e) {playOrPause();return true;}
}public void setTime(String time){if (textViewTime != null)textViewTime.setText(time);
}
//顯示電量,
public void setBattery(String stringBattery){if(textViewTime != null && img_Battery != null){textViewBattery.setText( stringBattery + "%");int battery = Integer.valueOf(stringBattery);if(battery < 15)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_15));if(battery < 30 && battery >= 15)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_15));if(battery < 45 && battery >= 30)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_30));if(battery < 60 && battery >= 45)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_45));if(battery < 75 && battery >= 60)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_60));if(battery < 90 && battery >= 75)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_75));if(battery > 90 )img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_90));}
}
//隱藏/顯示
private void toggleMediaControlsVisiblity(){if (isShowing()) {hide();} else {show();}
}
//播放與暫停
private void playOrPause(){if (videoView != null)if (videoView.isPlaying()) {videoView.pause();} else {videoView.start();}
}
} ` 因?yàn)槭褂玫氖亲远x的mediaController 當(dāng)顯示后,mediaController會(huì)鋪滿屏幕,所以VideoView的點(diǎn)擊事件會(huì)被攔截,所以重寫控制器的手勢(shì)事件,將全部的操作全部寫在控制器中, 另外,因?yàn)辄c(diǎn)擊事件被控制器攔截,無法傳遞到下層的VideoView,所以 原來的單機(jī)隱藏會(huì)失效,作為代替,在手勢(shì)監(jiān)聽中onSingleTapConfirmed()添加自定義的隱藏/顯示, 下邊是Activity代碼 public class PlayActivity extends Activity implements Runnable{
private VideoView mVideoView;
private MediaController mMediaController;
private MyMediaController myMediaController;String path1 = Environment.getExternalStorageDirectory() + "/Download/B.mp4";private static final int TIME = 0;
private static final int BATTERY = 1;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case TIME:myMediaController.setTime(msg.obj.toString());break;case BATTERY:myMediaController.setBattery(msg.obj.toString());break;}}
};@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//定義全屏參數(shù)int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;//獲得當(dāng)前窗體對(duì)象Window window = PlayActivity.this.getWindow();//設(shè)置當(dāng)前窗體為全屏顯示window.setFlags(flag, flag);//設(shè)置視頻解碼監(jiān)聽if (!io.vov.vitamio.LibsChecker.checkVitamioLibs(this))return;setContentView(R.layout.activity_play);mVideoView = (VideoView) findViewById(R.id.surface_view);mVideoView.setVideoPath(path1);mMediaController = new MediaController(this);myMediaController = new MyMediaController(this,mVideoView,this);mVideoView.setMediaController(myMediaController);//mVideoView.setMediaController(mMediaController);mVideoView.setVideoQuality(MediaPlayer.VIDEOQUALITY_HIGH);//高畫質(zhì)mMediaController.show(5000);mVideoView.requestFocus();registerBoradcastReceiver();new Thread(this).start();}
@Override
public void onConfigurationChanged(Configuration newConfig) {if (mVideoView != null){mVideoView.setVideoLayout(VideoView.VIDEO_LAYOUT_SCALE, 0);}super.onConfigurationChanged(newConfig);
}@Override
protected void onDestroy() {super.onDestroy();try {unregisterReceiver(batteryBroadcastReceiver);} catch (IllegalArgumentException ex) {}
}private BroadcastReceiver batteryBroadcastReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if(Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())){//獲取當(dāng)前電量int level = intent.getIntExtra("level", 0);//電量的總刻度int scale = intent.getIntExtra("scale", 100);//把它轉(zhuǎn)成百分比//tv.setText("電池電量為"+((level*100)/scale)+"%");Message msg = new Message();msg.obj = (level*100)/scale+"";msg.what = BATTERY;mHandler.sendMessage(msg);}}
};public void registerBoradcastReceiver() {//注冊(cè)電量廣播監(jiān)聽IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);registerReceiver(batteryBroadcastReceiver, intentFilter);}@Override
public void run() {// TODO Auto-generated method stubwhile (true) {//時(shí)間讀取線程SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");String str = sdf.format(new Date());Message msg = new Message();msg.obj = str;msg.what = TIME;mHandler.sendMessage(msg);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}
}
因?yàn)橐挂曨l全屏才能達(dá)到最佳效果。直接在Activity中設(shè)置了全屏,為了達(dá)到只能橫向旋轉(zhuǎn)切換效果,在AndroidMainfest.xml中對(duì)Activity的屬性這么設(shè)置 android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="sensorLandscape"
在4.0 以后的系統(tǒng)中 原來的android:configChanges=orientation|keyboardHidden 將不會(huì)起作用,需要增加screenSize才可以正常的調(diào)用重繪函數(shù)
android:screenOrientation 屬性相關(guān)的值以及功能如下:
screenOrientationFunction unspecified 默認(rèn)值,由系統(tǒng)來選擇方向。它的使用策略,以及由于選擇時(shí)特定的上下文環(huán)境,可能會(huì)因 為設(shè)備的差異而不同。 user 使用用戶當(dāng)前首選的方向。 behind 使用Activity堆棧中與該Activity之下的那個(gè)Activity的相同的方向。 landscape 橫向顯示(寬度比高度要大) portrait 縱向顯示(高度比寬度要大) reverseLandscape 與正常的橫向方向相反顯示,在API Level 9中被引入。 reversePortrait 與正常的縱向方向相反顯示,在API Level 9中被引入。 sensorLandscape 橫向顯示,但是基于設(shè)備傳感器,既可以是按正常方向顯示,也可以反向顯示,在API Level 9中被引入。 sensorPortrait 縱向顯示,但是基于設(shè)備傳感器,既可以是按正常方向顯示,也可以反向顯示,在API Level 9中被引入。 sensor 顯示的方向是由設(shè)備的方向傳感器來決定的。顯示方向依賴與用戶怎樣持有設(shè)備;當(dāng)用戶旋轉(zhuǎn)設(shè)備時(shí),顯示的方向會(huì)改變。但是,默認(rèn)情況下,有些設(shè)備不會(huì)在所有的四個(gè)方向上都旋轉(zhuǎn),因此要允許在所有的四個(gè)方向上都能旋轉(zhuǎn),就要使用fullSensor屬性值。 fullSensor 顯示的方向(4個(gè)方向)是由設(shè)備的方向傳感器來決定的,除了它允許屏幕有4個(gè)顯示方向之外,其他與設(shè)置為“sensor”時(shí)情況類似,不管什么樣的設(shè)備,通常都會(huì)這么做。例如,某些設(shè)備通常不使用縱向倒轉(zhuǎn)或橫向反轉(zhuǎn),但是使用這個(gè)設(shè)置,還是會(huì)發(fā)生這樣的反轉(zhuǎn)。這個(gè)值在API Level 9中引入。 nosensor 屏幕的顯示方向不會(huì)參照物理方向傳感器。傳感器會(huì)被忽略,所以顯示不會(huì)因用戶移動(dòng)設(shè)備而旋轉(zhuǎn)。除了這個(gè)差別之外,系統(tǒng)會(huì)使用與“unspecified”設(shè)置相同的策略來旋轉(zhuǎn)屏幕的方向。
注意:在給這個(gè)屬性設(shè)置的值是“l(fā)andscape”或portrait的時(shí)候,要考慮硬件對(duì)Activity運(yùn)行的方向要求。正因如此,這些聲明的值能夠被諸如Google Play這樣的服務(wù)所過濾,以便應(yīng)用程序只能適用于那些支持Activity所要求的方向的設(shè)備。例如,如果聲明了“l(fā)andscape”、“reverseLandscape”、或“sensorLandscape”,那么應(yīng)用程序就只能適用于那些支持橫向顯示的設(shè)備。但是,還應(yīng)該使用元素來明確的聲明應(yīng)用程序所有的屏幕方向是縱向的還是橫行的。例如:。這個(gè)設(shè)置由Google Play提供的純粹的過濾行為,并且在設(shè)備僅支持某個(gè)特定的方向時(shí),平臺(tái)本身并不控制應(yīng)用程序是否能夠被按照。
這樣一個(gè)自定義的控制欄和標(biāo)題欄就完成了
Github Demo:WHPlayer
結(jié)束,下一篇將講述 如何添加,滑動(dòng)調(diào)節(jié)亮度,聲音,快進(jìn)快退等
總結(jié)
以上是生活随笔 為你收集整理的视频框架 Vitamio 使用教程+部分心得 (三) 视频控制器MediaController + 部分中文API 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。