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 >