前言
VideoView是Android主要的視頻播放View,它其實是對MediaPlayer的再次封裝.如果你已經了解過MediaPlayer在使用VideoView是十分簡單的.如果你想先了解MediaPlayer可以參考我的博客:https://www.cnblogs.com/guanxinjing/p/11019662.html
在沒有復雜的要求下使用VideoView播放視頻是十分快速且方便的選擇.並且不需要苦惱視頻尺寸的計算(說到視頻尺寸計算,個人瞎折騰出了一個計算方法.雖然也可以將視頻適配的很完美,但是總的還是沒有VideoView內置的視頻尺寸適配厲害,有興趣的可以閱讀一下源代碼里VideoView的視頻適配,這個才是精華部分...)
實現流程
- 獲取權限
- 保持屏幕常亮
- xml布局里添加VideoView
- 初始化配置VideoView
- 播放視頻
- 暫停視頻
- 獲取播放時間信息
- 停止視頻
獲取權限
這個是播放本地視頻的demo,所以只添加了需要SD卡權限.另外VideoView是支持網絡視頻播放的如果你需要播放網絡視頻則還需要添加網絡權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
保持屏幕常亮
音視頻開發的基本操作,在xml的根布局上添加下面這個屬性
android:keepScreenOn="true"
xml布局里添加VideoView
<VideoView android:id="@+id/video_view" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/>
基本操作,但是這里有一個你可能會忽視的VideoView的高寬配置.了解這個你可以看出VideoView自帶的視頻尺寸適配還是十分完美的.
1.第一種當你android:layout_width="wrap_content" 和 android:layout_height="wrap_content" 都設置為wrap_content時,這個時候View適配視頻是一個在當前設備屏幕下最合適的一個比例. 雖然比例是最合適的但是View寬和高並不會一定鋪滿屏幕.這種方式適合在一些懸浮的小窗口播放時使用.
2.第二種就是滿足屏幕的一邊,滿足屏幕的一邊的意思是讓VideoView始終鋪滿設備屏幕的寬或者高, 這里有一個原則就是始終滿足最小的那個一個邊.
我們在豎屏的情況下首先要滿足寬,所以屬性應該配置成 android:layout_width="match_parent" android:layout_height="wrap_content"
反之如果我們在橫屏的情況就要滿足高度.所以屬性應該配置成 android:layout_width="wrap_content" android:layout_height="match_parent"
這樣我們的視頻才不會變形拉伸.重點!這種方式是我們最適用在視頻播放時的使用.(如果你是動態切換橫豎屏,我們也可以在代碼里重新設置VideoView的layout_width和layout_height)
3.第三種 layout_width和layout_height屬性都是match_parent,這個將無法避免視頻的變形拉伸,不建議這樣配置.
4.第四種 layout_width和layout_height屬性都自己設置固定大小的值.這個也將無法避免視頻的變形拉伸.如果,你還是一定需要用小窗口播放,這里可以給出一個思路:
就是在VideoView外面在套一個小窗口的黑色背景View,而你只要遵守第二條情況滿足這小窗口的黑色背景View的最短的一邊.就可以完美的適配視頻了.按自動比例縮小視頻,其他地方會變成電影一樣的黑邊效果.布局參考如下:
<View android:id="@+id/video_bg" android:layout_width="100dp" android:layout_height="50dp" android:background="@color/fontBlack1" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/> <VideoView android:id="@+id/video_view2" android:layout_width="wrap_content" android:layout_height="match_parent" app:layout_constraintTop_toTopOf="@id/video_bg" app:layout_constraintBottom_toBottomOf="@id/video_bg" app:layout_constraintLeft_toLeftOf="@id/video_bg" app:layout_constraintRight_toRightOf="@id/video_bg"/>
初始化配置VideoView
private void initVideoView(){ File file = new File(getExternalCacheDir(),"demo.mp4"); if (!file.exists()){ Toast.makeText(this, "視頻不存在", Toast.LENGTH_SHORT).show(); finish(); return; } mVideoView.setVideoPath(file.getAbsolutePath());//設置視頻文件 mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { //視頻加載完成,准備好播放視頻的回調 } }); mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { //視頻播放完成后的回調 } }); mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { //異常回調 return false;//如果方法處理了錯誤,則為true;否則為false。返回false或根本沒有OnErrorListener,將導致調用OnCompletionListener。 } }); mVideoView.setOnInfoListener(new MediaPlayer.OnInfoListener() { @Override public boolean onInfo(MediaPlayer mp, int what, int extra) { //信息回調 // what 對應返回的值如下 // public static final int MEDIA_INFO_UNKNOWN = 1; 媒體信息未知 // public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; 媒體信息視頻跟蹤滯后 // public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; 媒體信息\視頻渲染\開始 // public static final int MEDIA_INFO_BUFFERING_START = 701; 媒體信息緩沖啟動 // public static final int MEDIA_INFO_BUFFERING_END = 702; 媒體信息緩沖結束 // public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; 媒體信息網絡帶寬(703) // public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; 媒體-信息-壞-交錯 // public static final int MEDIA_INFO_NOT_SEEKABLE = 801; 媒體信息找不到 // public static final int MEDIA_INFO_METADATA_UPDATE = 802; 媒體信息元數據更新 // public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; 媒體信息不支持字幕 // public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; 媒體信息字幕超時 return false; //如果方法處理了信息,則為true;如果沒有,則為false。返回false或根本沒有OnInfoListener,將導致丟棄該信息。 } }); }
播放視頻
mVideoView.start();
注意,播放視頻時一定要確定setOnPreparedListener返回了已經視頻准備完成的回調.
暫停視頻
mVideoView.pause();
獲取播放時間信息
mVideoView.getDuration();//獲取視頻的總時長 mVideoView.getCurrentPosition();//獲取視頻的當前播放位置
這2個方法本質上還是調用MediaPlayer獲取的,獲取到時長后你可以配合Handler與SeekBar配合來實現自定義進度條功能.這里就不在啰嗦了.有興趣可以參考我的一個簡單的demo(雖然demo里是用MediaPlayer實現,但是原理一致):https://www.cnblogs.com/guanxinjing/p/11024195.html
停止釋放
mVideoView.stopPlayback();//停止播放視頻,並且釋放 mVideoView.suspend();//在任何狀態下釋放媒體播放器
兩個方法都有停止並且釋放內存的作用,但是stopPlayback()看底層代碼並沒有重置,所以應該只是釋放內存,但是沒有釋放配置資源(但是想不在配一次視頻路徑應該是需要調用mMediaPlayer.prepareAsync();方法,看不懂這個?可以參考我MediaPlayer入門的博客).
而suspend則更加徹底的釋放了所有配置信息和內存.
其他Api介紹
- mVideoView.canPause(); //是否可以暫停
- mVideoView.canSeekBackward(); //視頻是否可以向后調整播放位置
- mVideoView.canSeekForward(); //視頻是否可以向前調整播放位置
- mVideoView.getBufferPercentage(); //獲取視頻緩沖百分比
- mVideoView.resolveAdjustedSize(); //獲取自動解析后VideoView的大小
- mVideoView.resume(); //重新開始播放
- mVideoView.isPlaying(); //是否在播放中
- mVideoView.setMediaController(); //設置多媒體控制器
- mVideoView.onKeyDown(); //發送物理按鍵值
- mVideoView.setVideoURI(); //播放網絡視頻,參數為網絡地址
end