textureview 缩放_用MediaPlayer+TextureView封装一个完美实现全屏、小窗口的视频播放器...
原標題:用MediaPlayer+TextureView封裝一個完美實現全屏、小窗口的視頻播放器
本文作者
本文由xiaoyanger授權發布。
xiaoyanger的博客地址:
http://www.jianshu.com/u/25c3b13f87ce
貼個簡易的效果圖:
1
為什么使用TextureView
在Android總播放視頻可以直接使用VideoView,VideoView是通過繼承自SurfaceView來實現的。
SurfaceView的大概原理就是在現有View的位置上創建一個新的Window,內容的顯示和渲染都在新的Window中。這使得SurfaceView的繪制和刷新可以在單獨的線程中進行,從而大大提高效率。
但是呢,由于SurfaceView的內容沒有顯示在View中而是顯示在新建的Window中, 使得SurfaceView的顯示不受View的屬性控制,不能進行平移,縮放等變換,也不能放在其它RecyclerView或ScrollView中,一些View中的特性也無法使用。
TextureView是在4.0(API level 14)引入的,與SurfaceView相比,它不會創建新的窗口來顯示內容。它是將內容流直接投放到View中,并且可以和其它普通View一樣進行移動,旋轉,縮放,動畫等變化。TextureView必須在硬件加速的窗口中使用。
TextureView被創建后不能直接使用,必須要在它被它添加到ViewGroup后,待SurfaceTexture準備就緒才能起作用(看TextureView的源碼,TextureView是在繪制的時候創建的內部SurfaceTexture)。
通常需要給TextureView設置監聽器SurfaceTextuListener:
SurfaceTexture的準備就緒、大小變化、銷毀、更新等狀態變化時都會回調相對應的方法。當TextureView內部創建好SurfaceTexture后,在監聽器的onSurfaceTextureAvailable方法中,用SurfaceTexture來關聯MediaPlayer,作為播放視頻的圖像數據來源。
SurfaceTexture作為數據通道,把從數據源(MediaPlayer)中獲取到的圖像幀數據轉為GL外部紋理,交給TextureVeiw作為View heirachy中的一個硬件加速層來顯示,從而實現視頻播放功能。
2
MediaPlayer介紹
MediaPlayer是Android原生的多媒體播放器,可以用它來實現本地或者在線音視頻的播放,同時它支持https和rtsp ( https://developer.android.google.cn/guide/topics/media/media-formats.html)。
MediaPlayer定義了各種狀態,可以理解為是它的生命周期。
這個狀態圖描述了MediaPlayer的各種狀態,以及主要方法調用后的狀態變化。
MediaPlayer的相關方法及監聽接口:
方法
介紹
狀態
setDataSource
設置數據源
Initialized
prepare
準備播放,同步
Preparing —> Prepared
prepareAsync
準備播放,異步
Preparing —> Prepared
start
開始或恢復播放
Started
pause
暫停
Paused
stop
停止
Stopped
seekTo
到指定時間點位置
PrePared/Started
reset
重置播放器
Idle
setAudioStreamType
設置音頻流類型
--
setDisplay
設置播放視頻的Surface
--
setVolume
設置聲音
--
getBufferPercentage
獲取緩沖半分比
--
getCurrentPosition
獲取當前播放位置
--
getDuration
獲取播放文件總時間
--
內部回調接口
介紹
狀態
OnPreparedListener
準備監聽
Preparing ——>Prepared
OnVideoSizeChangedListener
視頻尺寸變化監聽
--
OnInfoListener
指示信息和警告信息監聽
--
OnCompletionListener
播放完成監聽
PlaybackCompleted
Listener
播放錯誤監聽
Error
OnBufferingUpdateListener
緩沖更新監聽
--
MediaPlayer在直接new出來之后就進入了Idle狀態,此時可以調用多個重載的setDataSource()方法從idle狀態進入Initialized狀態(如果調用setDataSource()方法的時候,MediaPlayer對象不是出于Idle狀態,會拋異常,可以調用reset()方法回到Idle狀態)。
調用prepared()方法和preparedAsync()方法進入Prepared狀態,prepared()方法直接進入Parpared狀態,preparedAsync()方法會先進入PreParing狀態,播放引擎準備完畢后會通過OnPreparedListener.onPrepared()回調方法通知Prepared狀態。
在Prepared狀態下就可以調用start()方法進行播放了,此時進入started()狀態,如果播放的是網絡資源,Started狀態下也會自動調用客戶端注冊的OnBufferingUpdateListener.OnBufferingUpdate()回調方法,對流播放緩沖的狀態進行追蹤。
pause()方法和start()方法是對應的,調用pause()方法會進入Paused狀態,調用start()方法重新進入Started狀態,繼續播放。
stop()方法會使MdiaPlayer從Started、Paused、Prepared、PlaybackCompleted等狀態進入到Stoped狀態,播放停止。
當資源播放完畢時,如果調用了setLooping(boolean)方法,會自動進入Started狀態重新播放,如果沒有調用則會自動調用客戶端播放器注冊的OnCompletionListener.OnCompletion()方法,此時MediaPlayer進入PlaybackCompleted狀態,在此狀態里可以調用start()方法重新進入Started狀態。
封裝考慮
MediaPlayer的方法和接口比較多,不同的狀態調用各個方法后狀態變化情況也比較復雜。
播放相關的邏輯只與MediaPlayer的播放狀態和調用方法相關,而界面展示和UI操作很多時候都需要根據自己項目來定制。
參考原生的VideoView,為了解耦和方便定制,把MediaPlayer的播放邏輯和UI界面展示及操作相關的邏輯分離。我是把MediaPlayer直接封裝到NiceVideoPlayer中,各種UI狀態和操作反饋都封裝到NiceVideoPlayerController里面。如果需要根據不同的項目需求
來修改播放器的功能,就只重寫NiceVideoPlayerController就可以了。
3
NiceVideoPlayer
首先,需要一個FrameLayout容器mContainer,里面有兩層內容,第一層就是展示播放視頻內容的TextureView,第二層就是播放器控制器mController。
那么自定義一個NiceVideoPlayer繼承自FrameLayout,將mContainer添加到當前控件:
添加setUp方法來配置播放的視頻資源路徑(本地/網絡資源):
public void setUp(String url, Map headers) { mUrl = url; mHeaders = headers;}
用戶要在mController中操作才能播放,因此需要在播放之前設置好mController:
用戶在自定義好自己的控制器后通過setController這個方法設置給播放器進行關聯。
觸發播放時,NiceVideoPlayer將展示視頻圖像內容的mTextureView添加到mContainer中(在mController的下層),同時初始化mMediaPlayer,待mTextureView的數據通道SurfaceTexture準備就緒后就可以打開播放器:
打開播放器調用prepareAsync()方法后,mMediaPlayer進入準備狀態,準備就緒后就可以開始:
NiceVideoPlayer的這些邏輯已經實現視頻播放了,操作相關以及UI展示的邏輯需要在控制器NiceVideoPlayerController中來實現。
但是呢,UI的展示和反饋都需要依據播放器當前的播放狀態,所以需要給播放器定義一些常量來表示它的播放狀態:
播放視頻時,mMediaPlayer準備就緒(Prepared)后沒有馬上進入播放狀態,中間有一個時間延遲時間段,然后開始渲染圖像。所以將Prepared——>“開始渲染”中間這個時間段定義為STATE_PREPARED。
如果是播放網絡視頻,在播放過程中,緩沖區數據不足時mMediaPlayer內部會停留在某一幀畫面以進行緩沖。
正在緩沖時,mMediaPlayer可能是在正在播放也可能是暫停狀態,因為在緩沖時如果用戶主動點擊了暫停,就是處于STATE_BUFFERING_PAUSED,所以緩沖有STATE_BUFFERING_PLAYING和STATE_BUFFERING_PAUSED兩種狀態,緩沖結束后,恢復播放或暫停。
mController.setControllerState(mPlayerState, mCurrentState),mCurrentState表示當前播放狀態,mPlayerState表示播放器的全屏、小窗口,正常三種狀態。
定義好播放狀態后,開始暫停等操作邏輯也需要根據播放狀態調整:
reStart()方法是暫停時繼續播放調用。
4
全屏、小窗口播放的實現
可能最能想到實現全屏的方式就是把當前播放器的寬高給放大到屏幕大小,同時隱藏除播放器以外的其他所有UI,并設置成橫屏模式。
但是這種方式有很多問題,比如在列表(ListView或RecyclerView)中,除了放大隱藏外,還需要去計算滑動多少距離才剛好讓播放器與屏幕邊緣重合,退出全屏的時候還需要滑動到之前的位置,這樣實現邏輯不但繁瑣,而且和外部UI偶合嚴重,后面改動維護起來非常困難(我曾經就用這種方式被坑了無數道)。
分析能不能有其他更好的實現方式呢?
整個播放器由mMediaPalyer+mTexutureView+mController組成,要實現全屏或小窗口播放,我們只需要挪動播放器的展示界面mTexutureView和控制界面mController即可。
并且呢我們在上面定義播放器時,已經把mTexutureView和mController一起添加到mContainer中了,所以只需要將mContainer從當前視圖中移除,并添加到全屏和小窗口的目標視圖中即可。
那么怎么確定全屏和小窗口的目標視圖呢?
我們知道每個Activity里面都有一個android.R.content,它是一個FrameLayout,里面包含了我們setContentView的所有控件。
既然它是一個FrameLayout,我們就可以將它作為全屏和小窗口的目標視圖。
我們把從當前視圖移除的mContainer重新添加到android.R.content中,并且設置成橫屏。這個時候還需要注意android.R.content是不包括ActionBar和狀態欄的,所以要將Activity設置成全屏模式,同時隱藏ActionBar。
退出全屏也就很簡單了,將mContainer從android.R.content中移除,重新添加到當前視圖,并恢復ActionBar、清除全屏模式就行了。
進入小窗口播放和退出小窗口的實現原理就和全屏功能一樣了,只需要修改它的寬高參數:
這里有個特別需要注意的一點:
當mContainer移除重新添加后,mContainer及其內部的mTextureView和mController都會重繪,mTextureView重繪后,會重新new一個SurfaceTexture,并重新回調onSurfaceTextureAvailable方法,這樣mTextureView的數據通道SurfaceTexture發生了變化,但是mMediaPlayer還是持有原先的mSurfaceTexut,所以在切換全屏之前要保存之前的mSufaceTexture,當切換到全屏后重新調用onSurfaceTextureAvailable時,將之前的mSufaceTexture重新設置給mTexutureView。這樣就保證了切換時視頻播放的無縫銜接。
NiceVideoPlayerControl
為了解除NiceVideoPlayer和NiceVideoPlayerController的耦合,把NiceVideoPlayer的一些功能性和判斷性方法抽象到NiceVideoPlayerControl接口中。
NiceVideoPlayer實現這個接口即可。
NiceVideoPlayerManager
同一界面上有多個視頻,或者視頻放在ReclerView或者ListView的容器中,要保證同一時刻只有一個視頻在播放,其他的都是初始狀態,所以需要一個NiceVideoPlayerManager來管理播放器,主要功能是保存當前已經開始了的播放器。
采用單例,同時,onBackPressed供Activity中用戶按返回鍵時調用。
NiceVideoPlayer的start方法以及onCompleted需要修改一下,保證開始播放一個視頻時要先釋放掉之前的播放器;同時自己播放完畢,要將NiceVideoPlayerManager中的mNiceVideoPlayer實例置空,避免內存泄露。
NiceVideoPlayerController
播放控制界面上,播放、暫停、播放進度、緩沖動畫、全屏/小屏等觸發都是直接調用播放器對應的操作的。需要注意的就是調用之前要判斷當前的播放狀態,因為有些狀態下調用播放器的操作可能引起錯誤(比如播放器還沒準備就緒,就去獲取當前的播放位置)。
播放器在觸發相應功能的時候都會調用NiceVideoPlayerController的setControllerState(int playerState, int playState)這個方法來讓用戶修改UI。
不同項目都可能定制不同的控制器(播放操作界面),這里我就不詳細分析實現邏輯了,大致功能就類似騰訊視頻的熱點列表中的播放器。
其中全屏模式下橫向滑動改變播放進度、左側上下滑動改變亮度,右側上下滑動改變亮度等功能代碼中并未實現,有需要的可以直接參考節操播放器(https://github.com/lipangit/JieCaoVideoPlayer),只需要在Controller的onInterceptTouchEvent中處理就行了(后續會添加上去)。
代碼有點長,就不貼了,需要的直接下載源碼。
https://github.com/xiaoyanger0825/NiceVieoPlayer
5
使用
在RecyclerView或者ListView中使用時,需要監聽itemView的detached:
在ItemViewdetach窗口時,需要釋放掉itemView內部的播放器。
效果圖
6
最后
整個功能有參考節操播放器,但是自己這樣封裝和節操播放器還是有很大差異:一是分離了播放功能和控制界面,定制只需修改控制器即可。二是全屏/小窗口沒有新建一個播放器,只是挪動了播放界面和控制器,不用每個視頻都需要新建兩個播放器,也不用同步狀態。
MediaPlayer有很多格式不支持,后面會考慮用IjkPlayer或者ExoPlayer封裝。
如果有錯誤和更好的建議都請提出,源碼已上傳GitHub,歡迎Star,謝謝!。
源碼:
https://github.com/xiaoyanger0825/NiceVieoPlayer
如果你有想學習的文章直接留言,我會整理征稿。如果你有好的文章想和大家分享歡迎投稿,直接向我投遞文章鏈接即可。返回搜狐,查看更多
責任編輯:
總結
以上是生活随笔為你收集整理的textureview 缩放_用MediaPlayer+TextureView封装一个完美实现全屏、小窗口的视频播放器...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sqlerver 字符串转整型_mssq
- 下一篇: php创蓝253四要素认证_PHP下基于