Android 視頻播放器 (四):使用ExoPlayer播放視頻

1、簡介

ExoPlayer是一個Android應用層的媒體播放器,它提供了一套可替換Android MediaPlayer的API,能夠播放本地或者是線上的音視頻資源。ExoPlayer支持一些Android MediaPlayer不支持的特性,好比適配DASH和SmoothStreaming的播放。和MediaPlayer不一樣的是,ExoPlayer很容易自定義和擴展,而且它能夠經過應用商店的應用程序更新來直接更新。html

如今在Android設備上播放視頻和音樂的應用是一個很熱門的應用,Android框架提供的MediaPlayer可使用不多的代碼量快速的實現播放音視頻的功能,並且它也提供了底層的API好比MediaCodec、AudioTrack和MediaDrm,它們一樣能夠建立自定義媒體播放器,而ExoPlayer是創建在底層音視頻API之上的開源的應用級媒體播放器。java

項目地址:https://github.com/google/ExoPlayerandroid

ExoPlayer系列文章:https://medium.com/google-exoplayergit

ExoPlayer開發文檔:https://exoplayer.dev/github

優勢

對於Android內置的MediaPlayer來講,ExoPlayer有如下幾個優勢:緩存

  1. 支持DASH和SmoothStreaming這兩種數據格式的資源,而MediaPlayer對這兩種數據格式都不支持。它還支持其它格式的數據資源,好比MP4, M4A, FMP4, WebM, MKV, MP3, Ogg, WAV, MPEG-TS, MPEG-PS, FLV and ADTS (AAC)等
  2. 支持高級的HLS特性,好比能正確的處理#EXT-X-DISCONTINUITY標籤
  3. 無縫鏈接,合併和循環播放多媒體的能力
  4. 和應用一塊兒更新播放器(ExoPlayer),由於ExoPlayer是一個集成到應用APK裏面的庫,你能夠決定你所想使用的ExoPlayer版本,而且能夠隨着應用的更新把ExoPlayer更新到一個最新的版本。
  5. 較少的關於設備的特殊問題,而且在不一樣的Android版本和設備上不多會有不一樣的表現。
  6. 在Android4.4(API level 19)以及更高的版本上支持Widevine通用加密
  7. 爲了符合你的開發需求,播放器支持自定義和擴展。其實ExoPlayer爲此專門作了設計,而且容許不少組件能夠被自定義的實現類替換。
  8. 使用官方的擴展功能能夠很快的集成一些第三方的庫,好比IMA擴展功能經過使用互動媒體廣告SDK能夠很容易地將視頻內容貨幣化(變現)

缺點

  1. 好比音頻在Android設備上的播放,ExoPlayer會比MediaPlayer消耗更多的電量。更多細節請參考文章:Battery consumption page

 2、ExoPlayer 使用

1.把ExoPlayer做爲一個依賴添加到你的項目

添加倉庫 第一步就是確保你在工程根目錄的build.gradle文件裏添加了Google和JCenter倉庫:安全

repositories {
     google()
     jcenter()
 }

添加ExoPlayer模塊 在你的app module 裏面的build.gradle文件夾裏添加一個ExoPlayer依賴。app

下面是ExoPlayer的全量包的依賴方式:框架

implementation 'com.google.android.exoplayer:exoplayer:2.X.X'

上面的2.x.x是選擇的版本。jvm

你也能夠只依賴你想要的模塊,來代替全量包。好比當你的app想要播放DASH格式的內容的時候,能夠只依賴Core,DASH和UI模塊的庫。

implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'

下面列舉了可以使用的模塊庫,添加一個ExoPlayer全量的依賴庫等同於把下面全部的依賴庫都分別添加進去。

  1. exoplayer-core: 核心功能(必須的).
  2. exoplayer-dash: 支持DASH內容.
  3. exoplayer-hls: 支持HLS內容.
  4. exoplayer-smoothstreaming: 支持SmoothStreaming內容.
  5. exoplayer-ui: ExoPlayer所使用的UI組件和資源.

除了這些模塊庫以外,ExoPlayer還有不少能夠提供額外功能的依賴於第三方庫的擴展模塊,能夠參考extensions directory來了解更多信息

2. 打開對 java 8 的支持

若是尚未設置支持java8,那麼你須要在全部依賴ExoPlayer的build.gradle文件裏打開對java8的支持,經過在Android域中添加如下代碼便可:

compileOptions {
   targetCompatibility JavaVersion.VERSION_1_8
}

記住若是你想在你的代碼裏用java8的特性,你須要添加下面額外的設置:

// For Java compilers:
 compileOptions {
   sourceCompatibility JavaVersion.VERSION_1_8
 }
 // For Kotlin compilers:
 kotlinOptions {
   jvmTarget = JavaVersion.VERSION_1_8
 }

3. 建立一個播放器

你可使用ExoPlayerFactory建立一個ExoPlayer對象。爲了避免同的需求,這個工廠類提供了一系列方法來建立ExoPlayer實例,可是在大多數狀況下,使用ExoPlayerFactory.newSimpleInstance方法就能夠了。這些方法會返回SimpleExoPlayer類型的對象,它繼承自ExoPlayer,而且添加了一些額外的高級的播放器功能。下面的代碼展現了怎麼建立一個SimpleExoPlayer對象的:
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(context);

應用裏面的某個線程必定能夠訪問ExoPlayer對象,在大多數狀況下它通常是應用的主線程,而且只有在應用的主線程裏才能使用ExoPlayer的UI組件和IMA擴展。

可以訪問ExoPlayer對象的線程能夠經過建立播放器實例的時候傳入一個Looper被明確的指定,若是沒有指定Looper,那麼建立player的線程的Looper會被使用,或者這個線程也沒有Looper,那麼應用的主線程的Looper會被使用。在全部的狀況下,可以訪問播放器的線程的Looer可以經過Player.getApplicationLooper獲取到。

4. 把這個播放器實例附着到一個View上

ExoPlayer庫提供了一個PlayerView,它封裝了一個PlayerControlView和一個可以渲染視頻的Surface。一個PlayerView能夠被加入到應用的佈局文件中去。能夠像這樣把一個player綁定到一個View上。
// 綁定播放器到View上
playerView.setPlayer(player);
若是你須要更加精確的控制播放器和渲染視頻的Surface,你可使用SimpleExoPlayer的setVideoSurfaceView、setVideoTextureView、setVideoSurfaceHolder和setVideoSurface方法分別的設置播放器的屬性SurfaceView、TextureView、SurfaceHolder和Surface。你還能夠把PlayerControlView來當成一個單獨的組件使用,或者實現自定義的播放控制類來和播放器進行直接交互。在播放的時候,setTextOutput和setId3Output能夠被用來接收字幕和ID3元數據輸出。

5. 準備播放器資源

在ExoPlayer裏每一種媒體資源都是被MediaSource來表明的。若是想播放一種媒體資源,你首先要爲它建立相應的MediaSource對象,而後把這個對象傳遞給ExoPlayer.prepare方法。ExoPlayer庫提供了多種MediaSource的實現類,好比表明DASH資源的DashMediaSource,表明SmoothStreaming資源的SsMediaSource,表明HLS資源的HlsMediaSource和表明通常的多媒體文件的ExtractorMediaSource。下面的代碼展現瞭如何爲播放MP4文件的播放器準備適合的MediaSource。

 //建立一個DataSource對象,經過它來下載多媒體數據
 DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context,
 Util.getUserAgent(context, "yourApplicationName"));
 //這是一個表明將要被播放的媒體的MediaSource
 MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
     .createMediaSource(mp4VideoUri);
 //使用資源準備播放器
 player.prepare(videoSource);

6. 控制播放器

一旦播放器準備就緒,就能夠調用player的方法進行播放了,例如調用setPlayWhenReady能夠開始和暫停播放。不一樣的seekTo方法能夠在媒體資源裏進行搜索,setRepeatMode控制了多媒體如何循環播放,setShuffleModeEnabled控制了是否打亂播放列表,setPlaybackParameters用來調整播放的速度和音調。

若是player和PlayerView或者是PlayerControlView進行綁定了,那麼用戶和這些控件的交互將會調用player相對應的方法。

7. 監聽播放器事件

改變播放狀態或者是播放錯誤的事件將會被註冊的Player.EventListener對象接收,很容易咱們就能夠註冊一個接收這種事件的監聽。

 // 添加一個監聽來接收播放器事件.
 player.addListener(eventListener);

若是你只是對一部分事件感興趣,那麼你能夠繼承Player.DefaultEventListener而不是實現Player.EventListener,這樣會讓你只實現你想要的方法。

當使用SimpleExoPlayer的時候,也能夠給player設置一些額外的監聽。好比addVideoListener方法容許你獲取到視頻渲染相關的事件,它能夠幫助你調整UI佈局(渲染視頻的Surface的長寬比)。addAnalyticsListener方法容許你接收更加詳細的事件,它有助於你分析一些東西。

8. 釋放播放器

當不在須要播放的時候釋放掉播放器是很是重要的,以便釋放掉有限的資源好比視頻解碼器供其它應用使用。釋放掉播放器能夠經過調用ExoPlayer.release實現。

9. MediaSource(播放資源)

在ExoPlayer裏每一種媒體資源都是被MediaSource來表明的。ExoPlayer庫提供了多種MediaSource的實現類,好比表明DASH資源的DashMediaSource,表明SmoothStreaming資源的SsMediaSource,表明HLS資源的HlsMediaSource和表明通常的多媒體文件的ExtractorMediaSource。你能夠參考main demo app的PlayerActivity類看一下怎麼實例化這四種MediaSource。

除了上面所描述的MediaSource實現類以外,ExoPlayer也提供了ConcatenatingMediaSource,ClippingMediaSource,LoopingMediaSource和MergingMediaSource。經過組合這些MediaSource的實現類能夠實現更加複雜的播放功能。一些經常使用的使用功能會在下面描述。須要注意的是下面描述的是以視頻播放爲示例的,可是它們一樣適用於音頻的播放,以及適用任何所支持的媒體類型的播放。

10. Playlists(播放列表)

使用ConcatenatingMediaSource支持播放列表,它能夠連續的播放多種MediaSource資源。下面的例子展現了怎麼實現由兩個videos組成的playlists。

 MediaSource firstSource = new ExtractorMediaSource.Factory(...).createMediaSource(firstVideoUri);
 MediaSource secondSource = new ExtractorMediaSource.Factory(...).createMediaSource(secondVideoUri);
 //先播放第一個視頻,再播放第二個視頻
 ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSource, secondSource);

 

鏈接資源的轉換是無縫的。這種鏈接不要求是相同格式的資源(例如能夠把包含480P H264視頻文件和包含720P VP9的視頻文件很好地鏈接在一塊兒)。它們甚至能夠是不一樣的類型(好比能夠將一個視頻和一個純音頻流很好地鏈接在一塊兒)。而且在一個鏈接裏一個類型的MediaSource能夠被屢次使用。

在一個ConcatenatingMediaSource裏能夠經過添加,刪除和移動MediaSource動態地修改播放列表。一樣在播放視頻以前或者是正在播放的過程當中能夠經過調用相應的ConcatenatingMediaSource方法動態修改播放列表。播放器會正確地自動處理這些動態修改。例如正在播放的MediaSource被移動了,播放不會中斷而且播放完成後會自動播放它後面的一個MediaSource資源。若是正在播放的MediaSource被刪除了,播放器會自動移動到第一個存在的後繼者去播放,若是沒有後繼者的話,播放器將會轉到結束的狀態。

11. Clipping a video(剪輯視頻)

ClippingMediaSource能夠被用來剪輯視頻,這樣能夠只播放它的一部分。下面的例子展現了怎麼剪輯了一個視頻,從第5s開始播放,到第10s結束播放。

MediaSource videoSource = new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
 // 從第5s開始剪輯到第10s
 ClippingMediaSource clippingSource = new ClippingMediaSource(videoSource, /* startPositionUs= */ 5_000_000, /* endPositionUs= */ 10_000_000);

若是隻是從資源的開始進行剪輯,那麼結束的位置能夠被設置爲C.TIME_END_OF_SOURCE。爲了只剪輯到特定的持續時間,有一個構造函數能夠接收一個durationUs參數。

當從一個視頻文件的開始進行剪輯的時候,若是可能的話,儘可能把開始位置和關鍵幀對齊。若是開始位置沒有和關鍵幀對齊,那麼在開始播放以前播放器須要解碼而後丟棄掉前一個關鍵幀到開始位置之間的數據,這樣使 在開始播放的時候,這將會產生一小段延遲,包括當播放器將ClippingMediaSource做爲播放列表的一部分播放或循環播放時。

12. Looping a video(視頻循環播放)

若是想無限循環播放,最好使用ExoPlayer.setRepeatMode而不是LoopingMediaSource。

使用LoopingMediaSource一個視頻能夠被無縫的循環必定次數。下面的例子展現了怎麼播放一個視頻兩次:

 MediaSource source = new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
 // Plays the video twice.
 LoopingMediaSource loopingSource = new LoopingMediaSource(source, 2);

13. Side-loading a subtitle file(側載一個字幕文件)

給一個視頻文件和一個分開的字幕文件,MergingMediaSource能夠用來把它們合併成一個單獨的資源來播放。

 // 建立一個視頻的 MediaSource.
 MediaSource videoSource = new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
 // 建立一個字幕的 MediaSource.
 Format subtitleFormat = Format.createTextSampleFormat(id, // 一個軌道的標誌,能夠爲空
     MimeTypes.APPLICATION_SUBRIP, // The mime type. Must be set correctly.
     selectionFlags, // 軌道的選擇標誌
     language); // 字幕的語言,能夠爲空
 MediaSource subtitleSource = new SingleSampleMediaSource.Factory(...) .createMediaSource(subtitleUri, subtitleFormat, C.TIME_UNSET);
 // 播放帶有字幕的視頻
 MergingMediaSource mergedSource = new MergingMediaSource(videoSource, subtitleSource);

14. 高級組合

爲了更多高級的功能有可能更進一步地合併組合的MediaSource。假若有兩個視頻A和B,下面的例子展現了怎麼一塊兒使用LoopingMediaSource和ConcatenatingMediaSource來播放A、A、B序列。

 MediaSource firstSource = new ExtractorMediaSource.Factory(...).createMediaSource(firstVideoUri);
 MediaSource secondSource = new ExtractorMediaSource.Factory(...).createMediaSource(secondVideoUri);
 // 播放第一個視頻兩次
 LoopingMediaSource firstSourceTwice = new LoopingMediaSource(firstSource, 2);
 // 播放第一個視頻兩次,而後再播放第二個視頻
 ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSourceTwice, secondSource);

下面這個例子也能夠實現這個效果,這說明了不止有一種方法來實現相同的效果。

MediaSource firstSource = new ExtractorMediaSource.Builder(firstVideoUri, ...).build();
 MediaSource secondSource = new ExtractorMediaSource.Builder(secondVideoUri, ...).build();
 // 播放第一個視頻兩次,而後再播放第二個視頻
 ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSource, firstSource, secondSource);

15. 軌道選擇

軌道選擇決定了哪個可用的媒體軌道能夠被播放器的渲染器播放。軌道選擇由TrackSelector負責,不管何時建立一個ExoPlayer實例,都要給它提供一個TrackSelector對象。

 DefaultTrackSelector trackSelector = new DefaultTrackSelector();
 SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(context, trackSelector);

DefaultTrackSelector是一個靈活的TrackSelector,適合更多使用場景。當使用一個DefaultTrackSelector的時候,經過修改它的參數能夠控制哪個tracks被它選擇,這種選擇能夠在播放前完成。例以下面的代碼告訴選擇器將視頻軌道限制爲SD,而且若是音頻軌道只有一個就選擇一個德語的音頻軌道。

 trackSelector.setParameters(trackSelector.buildUponParameters().setMaxVideoSizeSd().setPreferredAudioLanguage("deu"));

這是一個基於約束的軌道選擇的例子,在這個例子中,在不知道實際可用軌道的狀況下指定約束。可使用參數指定許多不一樣類型的約束。參數還能夠用來從可用的軌道中選擇特定的軌道。有關詳細信息,請參閱DefaultTrackSelectorParameters和ParametersBuilderParametersBuilder文檔。

16. 發送消息給組件

能夠向ExoPlayer組件發送消息。這些消息可使用createMessage建立,而後使用PlayerMessage.send發送。默認狀況下,消息會盡快在播放線程上傳遞,可是這是能夠自定義的經過設置另外一個回調線程(使用PlayerMessage.setHandler)或指定一個傳遞消息的播放位置(使用PlayerMessage.setPosition)。經過ExoPlayer發送消息能夠確保操做的執行順序與在播放器上執行的任何其餘操做一致。

大多數ExoPlayer的開箱即用渲染器都支持在播放期間更改配置的消息。例如,音頻渲染器接受消息來設置音量,而視頻渲染器接受消息來設置Surface。這些消息應當在播放線程上傳遞,以確保線程安全。

17.自定義播放器

與Android的MediaPlayer相比,ExoPlayer的主要優點之一是可以自定義和擴展播放器,以更好地適應開發人員的用例。ExoPlayer庫是專門爲此而設計的,它定義了許多接口和抽象基類,可使應用程序開發人員可以輕鬆替換庫提供的默認實現。下面是一些用於構建自定義組件的用例:

Renderer——您可能想要實現一個自定義Renderer來處理庫默認不支持的媒體類型。

TrackSelector——實現自定義TrackSelector容許應用程序開發人員更改MediaSource暴露tracks的方式。它會被每一個可用的渲染器選擇使用。

LoadControl—實現自定義LoadControl容許應用程序開發人員更改播放器的緩衝策略。

Extractor——若是您須要支持目前該庫不支持的容器格式,請考慮實現一個定製的Extractor類,而後能夠將其與ExtractorMediaSource一塊兒用於播放該類型的媒體。

MediaSource——若是您但願以自定義的方式獲取媒體樣本以提供給渲染程序,或者但願實現自定義的MediaSource組合行爲,那麼實現自定義的MediaSource類多是最好的選擇。

DataSource——ExoPlayer的upstream包已經包含了許多不一樣用例的DataSource實現。您可能但願實現本身的DataSource,以另外一種方式加載數據,例如經過自定義協議、使用自定義HTTP堆棧或從自定義持久緩存加載數據。

在構建自定義組件時,咱們建議以下:

若是自定義組件須要嚮應用程序報告事件,咱們建議使用與現有ExoPlayer組件相同的模型,其中事件監聽器和Handler一塊兒傳遞給組件的構造函數。

咱們建議自定義組件使用與現有ExoPlayer組件相同的模型,以容許應用程序在播放期間進行從新配置,如Sending messages to components所說的。爲此,您應該實現一個ExoPlayerComponent並在其handleMessage方法中接收配置更改。您的應用程序應該經過調用外部播放器的sendMessages和blockingSendMessages方法來傳遞配置更改。

相關文章
相關標籤/搜索