【Kotlin】使用Exoplayer播放器播放影片

Use Exoplayer to play video with kotlin.

Posted by Tabaco on January 11, 2018

什麼是Exoplayer?

Android內提供的mediaPlayer只支援有限的格式,例如影片雖支持MP4,3GP,但如果要播放其他格式的影片還要進行相應的轉換。Exoplayer支援基於HTTP的動態自適應流 (DASH) 和SmoothStreaming,任何目前MediaPlayer 支持的視頻格式,同時它還支持 HTTP 直播(HLS),MP4,MP3,WebM,M4A,MPEG-TS 和 AAC。

相關的網站如下:

Exoplayer基本設定

  1. Exoplayer發布在Jcenter上,所以需要在專案根目錄的build.gralde包含jcenter repository。
    allprojects {
     repositories {
         jcenter()
     }
    }
    
  2. 添加Exoplayer的compile dependency到你app的build.gradle文件內。
    compile 'com.google.android.exoplayer:exoplayer:r2.X.X'
    
  3. 或者只添加需要的dependency,例如想要開發只支援DASH影片播放的APP,則只需要添加core、dash及UI即可。
    compile 'com.google.android.exoplayer:exoplayer-core:r2.X.X'
    compile 'com.google.android.exoplayer:exoplayer-dash:r2.X.X'
    compile 'com.google.android.exoplayer:exoplayer-ui:r2.X.X'
    

    其中2.X.X改成你專案需要的版本,在這篇是使用2.6.1。 Exoplayer可用的module包括如下:

    • exoplayer-core:Exoplayer核心功能,所以必須添加。
    • exoplayer-dash:支援DASH格式。
    • exoplayer-hls:支援HLS格式。
    • exoplayer-smoothstreaming:支援SmoothStreaming格式。
    • exoplayer-ui:UI相關資源。

    可到binary上查看更多資訊。

建立Exoplayer APP

  1. 在XML內新增Exoplayer的view(SimpleExoPlayerView),並為了顯示正在讀取影片,這在多新增一個ProgressBar

     <LinearLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools"
         android:id="@+id/video_linearlayout"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
         tools:context=".VideoViewActivity">
    
         <FrameLayout
             android:id="@+id/video_framelayout"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:keepScreenOn="true">
    
             <com.google.android.exoplayer2.ui.SimpleExoPlayerView
                 android:id="@+id/simpleExoPlayerView"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:focusable="true" />
    
             <ProgressBar
                 android:id="@+id/video_progressbar"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center"
                 android:visibility="invisible" />
         </FrameLayout>
     </LinearLayout>
    
  2. 初始化播放器

     private lateinit var videoFrameLayout: FrameLayout
     private lateinit var simpleExoPlayerView: SimpleExoPlayerView
     private lateinit var videoProgressbar: ProgressBar
     private lateinit var simpleExoplayer: SimpleExoPlayer
     private val url = "http://demos.webmproject.org/exoplayer/glass.mp4"
     private var mResumePosition = 0L
    
     private val bandwidthMeter by lazy {
             DefaultBandwidthMeter()
     }
    
     private val adaptiveTrackSelectionFactory by lazy {
             AdaptiveTrackSelection.Factory(bandwidthMeter)
     }
    
     private fun initializeExoplayer() {
         simpleExoplayer = ExoPlayerFactory.newSimpleInstance(
                 DefaultRenderersFactory(this),
                 DefaultTrackSelector(adaptiveTrackSelectionFactory),
                 DefaultLoadControl()
         )
    
         videoFrameLayout = findViewById(R.id.video_framelayout)
         simpleExoPlayerView = findViewById<SimpleExoPlayerView>(R.id.simpleExoPlayerView).apply {
             player = simpleExoplayer
         }
         videoProgressbar = findViewById(R.id.video_progressbar)
    
         simpleExoplayer.seekToDefaultPosition()
         simpleExoplayer.prepare(buildMediaSource(Uri.parse(url)))
         simpleExoplayer.playWhenReady = true
         simpleExoplayer.addListener(playerEventListener)
     }
    
  3. 準備Player

    在ExoPlayer中,任何播放媒體均由MediaSource來表示,如要播放一塊媒體,必須先創建一個相對應的MediaSource,然後將此對像傳遞給simpleExoplayer.prepare。 ExoPlayer庫為DASH(DashMediaSource),SmoothStreaming(SsMediaSource),HLS(HlsMediaSource)和常規媒體文件(ExtractorMediaSource)都提供了MediaSource的實現。以下程式碼為如何建立適合播放MP4文件的MediaSource。

     private fun buildMediaSource(uri: Uri) : MediaSource {
         val dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "ExoplayerSample"), bandwidthMeter)
         return ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
     }
    
  4. 監聽播放器的狀態

    讓影片尚未播放前顯示正在讀取影片,而在可以播放狀態下隱藏讀取狀態,讓使用者得知可以觀看影片,因此透過Player.EventListener的onPlayerStateChanged更改ProgressBar的狀態。

     private val playerEventListener by lazy {
             PlayerEventListener()
     }
    
     inner private class PlayerEventListener : Player.EventListener {
         override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {
         }
    
         override fun onSeekProcessed() {
         }
    
         override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {
         }
    
         override fun onPlayerError(error: ExoPlaybackException?) {
         }
    
         override fun onLoadingChanged(isLoading: Boolean) {
         }
    
         override fun onPositionDiscontinuity(reason: Int) {
         }
    
         override fun onRepeatModeChanged(repeatMode: Int) {
         }
    
         override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
         }
    
         override fun onTimelineChanged(timeline: Timeline?, manifest: Any?) {
         }
    
         override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
             when(playbackState) {
                 Player.STATE_BUFFERING -> {
                     videoProgressbar.visibility = View.VISIBLE
                 }
                 Player.STATE_READY -> {
                     videoProgressbar.visibility = View.INVISIBLE
                 }
             }
         }
     }
    
  5. 紀錄上次播放時間點

    無論是關閉手機螢幕或按HOME鍵,APP均會進入onPause狀態,為了使重新開啟APP時回到上次時間點,必須記錄上次影片時間點。而在APP進入onDestroy時釋放Exoplayer。

     override fun onResume() {
         simpleExoplayer.seekTo(mResumePosition)
         super.onResume()
     }
    
     override fun onPause() {
         pauseExoplayer()
         super.onPause()
     }
    
     override fun onDestroy() {
         releaseExoplayer()
         super.onDestroy()
     }
        
     private fun pauseExoplayer() {
         simpleExoplayer.playWhenReady = false
         mResumePosition = simpleExoplayer.currentPosition
     }
    
     private fun releaseExoplayer() {
         if (simpleExoplayer != null)
             simpleExoplayer.release()
     }
    

以上設定完就可以正常播放影片了。