轉載注明出處:
http://blog.csdn.net/u010181592/article/category/5893483
文章出自 我不只是看客/NotLooker的博客
先列出參考資料:
Vitamio 官網:http://www.vitamio.org(不太穩定,時常打不開) 農民伯伯 博客:http://www.cnblogs.com/over140/category/409230.html(開發者之一,博客中有部分Vitamio中文API)
轉載注明出處:http://blog.csdn.net/u010181592/article/category/5893483
經過準備和第一個Demo之后,需要對視頻進行更加精細的DIY來適合自己的項目。首先,視頻播放界面給大家直觀的感受就是 有進度條,聲音,按鈕等的控制條了,Vitamio中提供了一個基礎的樣例,我們根據自己的需要進行修改; 先來看一下Vitamio自帶的 MediaController 因為篇幅的緣故,就不上源碼。只列出介紹和常用的函數:
/** * 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. */
博主的英文不是太愛好,看介紹也就能理解的意思大概是 控制器是一個包含了MediaPlayer(視頻播放核心組件)的視圖控件,會有常用的按鈕和功能。使用方法有2種: a)創建一個xml布局,MediaController將會把他們放到 漂浮在應用上的窗口(看代碼后發現就是PopWindow) 默認顯示時間3s,點擊視圖可控制顯示/消失 要自定義MediaController時需要繼承該類并覆蓋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 顯示當前時間 mediacontroller_time_current 顯示總時長 mediacontroller_time_total 播放文件名 mediacontroller_file_name 而且必須有 pause_button和play_button 的資源(個人沒能理解這條,很多播放器設計已經用點擊屏幕代替掉了開始暫停實體鍵,博主測試的時候發現,即使自己的布局沒有開始按鈕,通過雙擊仍然可以暫停開始) 如果你使用第二種方法 那么hide()和show()無效;
下邊是常用方法的中文API,引用自農民伯伯的博客
public void onFinishInflate() 從XML加載完所有子視圖后調用。初始化控制視圖(調用initControllerView方法,設置事件、綁定控件和設置默認值)。
public void setAnchorView(View view) 設置MediaController綁定到一個視圖上。例如可以是一個VideoView對象,或者是你的activity的主視圖。 參數 view 可見時綁定的視圖
public void setMediaPlayer(MediaPlayerControl player) 設置媒體播放器。并更新播放/暫停按鈕狀態。
public void setInstantSeeking(boolean seekWhenDragging) 設置用戶拖拽SeekBar時畫面是否跟著變化。(VPlayer默認完成操作后再更新畫面)
public void show() 顯示MediaController。默認顯示3秒后自動隱藏。
public void show(int timeout) 顯示MediaController。在timeout毫秒后自動隱藏。 參數 timeout 超時時間,單位毫秒。為0時控制條的hide()將被調用。
public void setFileName(String name) 設置視頻文件名稱。
public void setInfoView(OutlineTextView v) 設置保存MediaController的操作信息。例如進度改變時更新v。
public void setAnimationStyle(int animationStyle) 更改MediaController的動畫風格。 如果MediaController正在顯示,調用此方法將在下一次顯示時生效。 參數 animationStyle 在MediaController顯示或隱藏時使用的動畫風格。設置-1為默認風格,0沒有動畫,或設置一個明確的動畫資源。
public boolean isShowing() 獲取MediaController是否已經顯示。
public void hide() 隱藏MediaController。
public void setOnShownListener(OnShownListener l) 注冊一個回調函數,在MediaController顯示后被調用。
public void setOnHiddenListener(OnHiddenListener l) 注冊一個回調函數,在MediaController隱藏后被調用。
public boolean onTouchEvent(MotionEvent event) 調用show()并返回true。
public boolean onTrackballEvent(MotionEvent ev) 調用show()并返回false。
public void setEnabled(boolean enabled) 設置MediaController的可用狀態。包括進度條和播放/暫停按鈕。
受保護方法 protected View makeControllerView() 創建控制播放的布局視圖。子類可重寫此方法創建自定義視圖。
例如我們要實現如下一個視頻播放器: 它有一個自定義的標題欄,并且把文件名放到了視頻的頂端; 在標題欄的左邊有一個返回按鈕,右邊分別由電池電量顯示和時間信息; 很多人開始做的時候會做這種布局會把標題欄寫在播放界面VideoView的布局上,就像這樣: 這樣標題欄和控制器的邏輯就是分開的,有2個弊端 : - 1 在監聽手指點擊屏幕相應事件中你不能完美的使 標題欄和控制欄同時出現/消失,在點擊時總要判斷控制欄的show/hide - 2 當你需要在控制器增加功能性按鈕(如清晰度設置,劇集選擇時,你就要重寫控制器或者直接去修改庫中的源代碼,總感覺第二種行為不太好)
既然以后對控制器的要求會更高不如從現在開始就直接自己定義,方便以后改寫; 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>
` 把需要顯示的標題欄 和控制器整合到了一起,這樣可以完美配合; 下面是自己的控制器類 MyMediaController:
public class MyMediaController extends MediaController {private GestureDetector mGestureDetector;
private ImageButton img_back;//返回鍵
private ImageView img_Battery;//電池電量顯示
private TextView textViewTime;//時間提示
private TextView textViewBattery;//文字顯示電池
private VideoView videoView;
private Activity activity;
private Context context;
private int controllerWidth = 0;//設置mediaController高度為了使橫屏時top顯示在屏幕頂端//返回監聽
private View.OnClickListener backListener = new View.OnClickListener() {public void onClick(View v) {if(activity != null){activity.finish();}}
};
//videoview 用于對視頻進行控制的等,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;// 處理手勢結束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) {//當收拾結束,并且是單擊結束時,控制器隱藏/顯示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();}
}
} ` 因為使用的是自定義的mediaController 當顯示后,mediaController會鋪滿屏幕,所以VideoView的點擊事件會被攔截,所以重寫控制器的手勢事件,將全部的操作全部寫在控制器中, 另外,因為點擊事件被控制器攔截,無法傳遞到下層的VideoView,所以 原來的單機隱藏會失效,作為代替,在手勢監聽中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);//定義全屏參數int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;//獲得當前窗體對象Window window = PlayActivity.this.getWindow();//設置當前窗體為全屏顯示window.setFlags(flag, flag);//設置視頻解碼監聽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);//高畫質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())){//獲取當前電量int level = intent.getIntExtra("level", 0);//電量的總刻度int scale = intent.getIntExtra("scale", 100);//把它轉成百分比//tv.setText("電池電量為"+((level*100)/scale)+"%");Message msg = new Message();msg.obj = (level*100)/scale+"";msg.what = BATTERY;mHandler.sendMessage(msg);}}
};public void registerBoradcastReceiver() {//注冊電量廣播監聽IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);registerReceiver(batteryBroadcastReceiver, intentFilter);}@Override
public void run() {// TODO Auto-generated method stubwhile (true) {//時間讀取線程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();}}
}
因為要使視頻全屏才能達到最佳效果。直接在Activity中設置了全屏,為了達到只能橫向旋轉切換效果,在AndroidMainfest.xml中對Activity的屬性這么設置 android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="sensorLandscape"
在4.0 以后的系統中 原來的android:configChanges=orientation|keyboardHidden 將不會起作用,需要增加screenSize才可以正常的調用重繪函數
android:screenOrientation 屬性相關的值以及功能如下:
screenOrientationFunction unspecified 默認值,由系統來選擇方向。它的使用策略,以及由于選擇時特定的上下文環境,可能會因 為設備的差異而不同。 user 使用用戶當前首選的方向。 behind 使用Activity堆棧中與該Activity之下的那個Activity的相同的方向。 landscape 橫向顯示(寬度比高度要大) portrait 縱向顯示(高度比寬度要大) reverseLandscape 與正常的橫向方向相反顯示,在API Level 9中被引入。 reversePortrait 與正常的縱向方向相反顯示,在API Level 9中被引入。 sensorLandscape 橫向顯示,但是基于設備傳感器,既可以是按正常方向顯示,也可以反向顯示,在API Level 9中被引入。 sensorPortrait 縱向顯示,但是基于設備傳感器,既可以是按正常方向顯示,也可以反向顯示,在API Level 9中被引入。 sensor 顯示的方向是由設備的方向傳感器來決定的。顯示方向依賴與用戶怎樣持有設備;當用戶旋轉設備時,顯示的方向會改變。但是,默認情況下,有些設備不會在所有的四個方向上都旋轉,因此要允許在所有的四個方向上都能旋轉,就要使用fullSensor屬性值。 fullSensor 顯示的方向(4個方向)是由設備的方向傳感器來決定的,除了它允許屏幕有4個顯示方向之外,其他與設置為“sensor”時情況類似,不管什么樣的設備,通常都會這么做。例如,某些設備通常不使用縱向倒轉或橫向反轉,但是使用這個設置,還是會發生這樣的反轉。這個值在API Level 9中引入。 nosensor 屏幕的顯示方向不會參照物理方向傳感器。傳感器會被忽略,所以顯示不會因用戶移動設備而旋轉。除了這個差別之外,系統會使用與“unspecified”設置相同的策略來旋轉屏幕的方向。
注意:在給這個屬性設置的值是“landscape”或portrait的時候,要考慮硬件對Activity運行的方向要求。正因如此,這些聲明的值能夠被諸如Google Play這樣的服務所過濾,以便應用程序只能適用于那些支持Activity所要求的方向的設備。例如,如果聲明了“landscape”、“reverseLandscape”、或“sensorLandscape”,那么應用程序就只能適用于那些支持橫向顯示的設備。但是,還應該使用元素來明確的聲明應用程序所有的屏幕方向是縱向的還是橫行的。例如:。這個設置由Google Play提供的純粹的過濾行為,并且在設備僅支持某個特定的方向時,平臺本身并不控制應用程序是否能夠被按照。
這樣一個自定義的控制欄和標題欄就完成了
Github Demo:WHPlayer
結束,下一篇將講述 如何添加,滑動調節亮度,聲音,快進快退等
總結
以上是生活随笔 為你收集整理的视频框架 Vitamio 使用教程+部分心得 (三) 视频控制器MediaController + 部分中文API 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。