ExoPlayer简易播放器 一个简单的基于ExoPlayer 的播放器。ExoPlayer官网: https://exoplayer.dev/  使用ExoPlayer版本:2.18.2
实现功能:通过url播放视频,简易自定义controller,监听播放器状态变化,首帧时间打印,错误信息打印。
如果需要深层次的UI定制,建议不要用exo_ui库下面的布局,用SurfaceView和TextureView完全自定义。具体代码如下:
1.播放器Fragment 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 import  android.os.Bundleimport  android.util.Logimport  android.view.LayoutInflaterimport  android.view.Viewimport  android.view.ViewGroupimport  androidx.fragment.app.Fragmentimport  com.google.android.exoplayer2.ExoPlayerimport  com.google.android.exoplayer2.MediaItemimport  com.google.android.exoplayer2.PlaybackExceptionimport  com.google.android.exoplayer2.Playerimport  com.google.android.exoplayer2.Player.Listenerimport  com.google.android.exoplayer2.ui.StyledPlayerViewclass  ExoPlayerFragment  : Fragment () {    companion  object  {         fun  newInstance (url: String )  : ExoPlayerFragment {             val  args = Bundle()             args.putString(EXO_URI, url)             val  fragment = ExoPlayerFragment()             fragment.arguments = args             return  fragment         }         private  const  val  TAG = "ExoPlayerFragment"          private  const  val  EXO_URI = "EXO_URI"      }     private  var  videoUri: String? = null      private  var  player: ExoPlayer? = null      private  val  listener = object  : Listener {         override  fun  onEvents (player: Player , events: Player .Events )   {             super .onEvents(player, events)             Log.d(TAG, "onEvents->${events} :" )         }         override  fun  onPlaybackStateChanged (playbackState: Int )   {             val  stateString: String = when  (playbackState) {                 ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE"                  ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING"                  ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY"                  ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED"                  else  -> "UNKNOWN_STATE"              }             Log.d(TAG, "onPlaybackStateChanged: state=$stateString " )             super .onPlaybackStateChanged(playbackState)             printPlayerTimeLine("onPlaybackStateChanged:" )         }         override  fun  onRenderedFirstFrame ()   {             super .onRenderedFirstFrame()             printPlayerTimeLine("onRenderedFirstFrame:" )         }         override  fun  onPlayerError (error: PlaybackException )   {             super .onPlayerError(error)             printPlayerTimeLine("onPlayerError:" )             error.printStackTrace()         }     }     override  fun  onCreateView (          inflater: LayoutInflater ,         container: ViewGroup ?,         savedInstanceState: Bundle ?     )  : View? {        videoUri = arguments?.getString(EXO_URI)         val  root = inflater.inflate(R.layout.exo_player_fragment, container, false )         val  playerView = root.findViewById<StyledPlayerView>(R.id.styled_player_view)         initPlayerView(playerView)         return  root     }     override  fun  onViewCreated (view: View , savedInstanceState: Bundle ?)   {         super .onViewCreated(view, savedInstanceState)         startPlay()     }     private  fun  initPlayerView (playerView: StyledPlayerView )   {         Log.d(TAG, "initPlayerView: " )         player = ExoPlayer.Builder(requireContext()).build()         player?.addListener(listener)                  playerView.player = player                  playerView.findViewById<View>(R.id.back).setOnClickListener {             fragmentManager?.popBackStack()             val  ft = fragmentManager?.beginTransaction()             ft?.remove(this @ExoPlayerFragment )             ft?.commitAllowingStateLoss()         }     }     private  var  prepareTime: Long  = 0L      private  fun  printPlayerTimeLine (method: String )   {         val  c = System.currentTimeMillis()         val  duration = if  (prepareTime == 0L ) 0  else  c - prepareTime         prepareTime = c         Log.i(TAG, "printPlayerTimeLine: method=$method , duration=$duration , uri=${videoUri} " )     }     private  fun  startPlay ()   {         Log.d(TAG, "startPlay: " )         videoUri?.let {                          val  mediaItem: MediaItem = MediaItem.fromUri(it)                          player?.setMediaItem(mediaItem)                          player?.prepare()             printPlayerTimeLine("player->prepare:" )                          player?.play()         }     }     override  fun  onDestroy ()   {         player?.release()         super .onDestroy()     } } 
 
2.播放器布局 exo_player_fragment.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0"  encoding="utf-8" ?> <FrameLayout  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:background ="@color/black"      android:clickable ="true" >     <com.google.android.exoplayer2.ui.StyledPlayerView           android:id ="@+id/styled_player_view"          android:layout_width ="match_parent"          android:layout_height ="match_parent"          android:keepScreenOn ="true"          app:animation_enabled ="false"          app:controller_layout_id ="@layout/custom_player_control_view"          app:use_controller ="true"  /> </FrameLayout > 
 
3.自定义的controller布局 custom_player_control_view.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 <?xml version="1.0"  encoding="utf-8" ?> <androidx.constraintlayout.widget.ConstraintLayout  xmlns:android ="http://schemas.android.com/apk/res/android"      xmlns:app ="http://schemas.android.com/apk/res-auto"      xmlns:tools ="http://schemas.android.com/tools"      android:layout_width ="match_parent"      android:layout_height ="match_parent"      android:layout_gravity ="bottom"      android:background ="#00000000"      android:layoutDirection ="ltr"      android:orientation ="vertical"      tools:targetApi ="28" >     <ImageView           android:id ="@+id/back"          android:layout_width ="42dp"          android:layout_height ="42dp"          android:layout_marginStart ="16dp"          android:layout_marginTop ="16dp"          android:background ="#33000000"          android:scaleType ="centerInside"          android:src ="@mipmap/back"          app:layout_constraintStart_toStartOf ="parent"          app:layout_constraintTop_toTopOf ="parent"  />     <LinearLayout           android:id ="@id/exo_bottom_bar"          android:layout_width ="match_parent"          android:layout_height ="wrap_content"          android:background ="#33000000"          android:gravity ="center_vertical"          android:orientation ="horizontal"          android:paddingTop ="4dp"          android:paddingBottom ="4dp"          app:layout_constraintBottom_toBottomOf ="parent"          app:layout_constraintStart_toStartOf ="parent" >         <ImageButton               android:id ="@id/exo_prev"              style ="@style/ExoStyledControls.Button.Center.Previous"              android:padding ="8dp"  />         <ImageButton               android:id ="@id/exo_play_pause"              style ="@style/ExoStyledControls.Button.Center.PlayPause"              android:padding ="8dp"  />         <ImageButton               android:id ="@id/exo_next"              style ="@style/ExoStyledControls.Button.Center.Next"              android:padding ="8dp"  />         <TextView               android:id ="@id/exo_position"              android:layout_width ="wrap_content"              android:layout_height ="wrap_content"              android:includeFontPadding ="false"              android:paddingLeft ="4dp"              android:paddingRight ="4dp"              android:textColor ="#FFBEBEBE"              android:textSize ="14sp"              android:textStyle ="bold"  />         <com.google.android.exoplayer2.ui.DefaultTimeBar               android:id ="@id/exo_progress"              android:layout_width ="0dp"              android:layout_height ="26dp"              android:layout_weight ="1"  />         <TextView               android:id ="@id/exo_duration"              android:layout_width ="wrap_content"              android:layout_height ="wrap_content"              android:includeFontPadding ="false"              android:paddingLeft ="4dp"              android:paddingRight ="4dp"              android:textColor ="#FFBEBEBE"              android:textSize ="14sp"              android:textStyle ="bold"  />     </LinearLayout >  </androidx.constraintlayout.widget.ConstraintLayout >