ExoPlayer简易播放器

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.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.Player.Listener
import com.google.android.exoplayer2.ui.StyledPlayerView

class 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)
// Bind the player to the view.
playerView.player = player

// back
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 {
// Build the media item.
val mediaItem: MediaItem = MediaItem.fromUri(it)

// Set the media item to be played.
player?.setMediaItem(mediaItem)

// Prepare the player.
player?.prepare()
printPlayerTimeLine("player->prepare:")

// Start the playback.
player?.play()
// calPlayerTimeLine("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>
作者

Dench

发布于

2023-03-16

更新于

2023-03-16

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×