effect
Audio playback, it is more common or frequently used functions, such as 音乐播放器
, 新闻播报
, 听书
etc., and if you happen to want 自定义
an audio player, then this article will certainly be helpful for you!
Common method
- start() start playing
- pause() Pause playback
- stop() stop playing
- prepare() resource preparation
- prepareAsync() prepares asynchronously without blocking the UI thread
- seekTo(int msec) seek to the specified position in milliseconds
- isLooping whether to loop
- isPlaying playing status
- duration total time
- currentPosition current position
- release() Resource release
Component Tree
The specific xml
code will not be posted, look at the component tree
initialization
/**
* 初始化 及 资源准备
*/
private fun audioPrepare(path: String) {
mMediaPlayer = MediaPlayer().apply {
setDataSource(path)//支持文件、网络地址、uri
prepareAsync()//异步准备,不阻塞UI线程
isLooping = false//循环播放
}
initMediaPlayerListener()
}
setDataSource
, Set the data source, support local files, network request addresses, uri, etc., look at the source code:
- setDataSource(FileDescriptor)
- setDataSource(String)
- setDataSource(Context, Uri)
- setDataSource(FileDescriptor, long, long)
- setDataSource(MediaDataSource)
If it is a local file, pay attention to 读写
permissions.
prepareAsync()
Asynchronous preparation without blocking the UI thread
Then look at the initMediaPlayerListener
method called
Player monitor events and interaction
/**
* 播放器监听事件
*/
private fun initMediaPlayerListener() {
mMediaPlayer?.setOnBufferingUpdateListener {
mp, percent ->
LogUtil.i("缓冲进度$percent%")
}
mMediaPlayer?.setOnPreparedListener {
LogUtil.i("准备完成")
//在准备完成之后获取信息,否则会有异常
val duration = mMediaPlayer?.duration//时长
val currentPosition = mMediaPlayer?.currentPosition//当前位置
LogUtil.i("当前位置$currentPosition/时长$duration")
tv_currentPosition.text = formatDuration(currentPosition!!)
tv_duration.text = formatDuration(duration!!)
seek_bar.max = duration
}
mMediaPlayer?.setOnCompletionListener {
LogUtil.i("播放完毕")
}
mMediaPlayer?.setOnErrorListener {
mp, what, extra ->
LogUtil.i("播放错误")
return@setOnErrorListener true
}
mMediaPlayer?.setOnSeekCompleteListener {
LogUtil.i("定位完成")
}
seek_bar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
tv_currentPosition.text = formatDuration(seekBar!!.progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
//拖动结束之后再设置,如果在onProgressChanged中设置会有杂音
mMediaPlayer?.seekTo(seekBar!!.progress)
tv_currentPosition.text = formatDuration(seekBar!!.progress)
}
})
btn_start.setOnClickListener {
audioStart()
}
btn_pause.setOnClickListener {
audioPause()
}
btn_seek.setOnClickListener {
seek_bar.progress = (seek_bar.max * 0.8).roundToInt()
mMediaPlayer?.seekTo(seek_bar!!.progress)
tv_currentPosition.text = formatDuration(seek_bar!!.progress)
audioStart()
}
btn_restart.setOnClickListener {
audioRestart()
}
}
Mainly some player monitoring events and button operation events.
Focus on two:
1 、 setOnPreparedListener
Note that when acquiring resources 时长
, they need to be 准备完成
acquired after the player , otherwise there will be 异常
:
Attempt to call getDuration in wrong state: mPlayer=0x7244676280, mCurrentState=4
error (-38, 0)
And will call back OnErrorListener
.
Then set the display, and assign the duration to seek_bar
the maximum value.
2、setOnSeekBarChangeListener
3 methods:
- onProgressChanged progress changed
- onStartTrackingTouch to start dragging
- onStopTrackingTouch Stop dragging
We need to update the current in 改变中
and , and perform the playback operation at the last position.改变后
播放时长
If 指定播放位置
this kind of operation is not located in the program , do not onProgressChanged
perform the playback operation in it, because frequent progress changes and frequent calls to play will occur 杂音
.
Therefore, it is recommended that users manually drag to trigger playback.
If the program can jump to the specified position to play, the following operations are recommended:
btn_seek.setOnClickListener {
seek_bar.progress = (seek_bar.max * 0.8).roundToInt()
mMediaPlayer?.seekTo(seek_bar!!.progress)
tv_currentPosition.text = formatDuration(seek_bar!!.progress)
audioStart()
}
Assign values manually progress
and call play.
Format playback time
This acquisition time returns yes 毫秒
, so we also need to 格式化
operate on it.
/**
* 格式化播放时间
*/
private fun formatDuration(duration: Int): String {
val d = duration / 1000
val minute = d / 60
val second = d % 60
val m: String = if (minute < 10) "0$minute" else "$minute"
val s: String = if (second < 10) "0$second" else "$second"
return "$m:$s"
}
A judgment is made, and 0 is added to the leading digit if it is less than two digits.
Start playing
/**
* 开始播放
*/
private fun audioStart() {
mMediaPlayer?.run {
if (!this.isPlaying) {
start()
startTimer()
}
}
}
Because there is no 播放中
callback interface, here is a start to Timer
get the current position and更新UI
Timer update UI
/**
* 每隔一秒执行一次,更新当前播放时间
*/
private fun startTimer() {
mTimer = Timer().apply {
schedule(object : TimerTask() {
override fun run() {
//非ui线程不能更新view,所以这里赋值给seek_bar,在seek_bar的事件中去更新
seek_bar.progress = mMediaPlayer!!.currentPosition
//tv_currentPosition.text = formatDuration(mMediaPlayer!!.currentPosition)
}
}, 0, 1000)
}
}
It should be noted here that the ui线程
view cannot be updated, so it is assigned here and updated seek_bar
in the onProgressChanged callback of seek_bar.
Pause playback
/**
* 暂停播放
*/
private fun audioPause() {
mMediaPlayer?.run {
if (this.isPlaying) {
pause()
cancelTimer()
}
}
}
Similarly, cancel when suspended Timer
, so that resources can be recovered in time.
Cancel Timer
private fun cancelTimer() {
mTimer?.run {
cancel()
mTimer = null
}
}
Pause/resume playback
/**
* 暂停/继续 播放
*/
private fun audioToggle() {
mMediaPlayer?.run {
if (this.isPlaying) {
audioPause()
} else {
audioStart()
}
}
}
If only one event is triggered, you can write it like this.
Replay
The player does not have its own restart()
method, but we can manually change the playback position to the initial value and call the playback.
/**
* 重新播放
*/
private fun audioRestart() {
mMediaPlayer?.run {
//定位到指定位置,单位毫秒
seekTo(0)
audioStart()
seek_bar.progress = 0
tv_currentPosition.text = formatDuration(seek_bar!!.progress)
//如果是下一首,可以调用reset()重置,然后set新的数据源
}
}
If it is the next song, you can call reset()
reset and then set the new one 数据源
.
Recycle
Timely recovery is conducive to better 性能
.
override fun onDestroy() {
mAgentWeb.webLifeCycle.onDestroy()
super.onDestroy()
cancelTimer()
mMediaPlayer?.run {
stop()
release()
mMediaPlayer = null
}
}
Ok, this is the end of the explanation.
Writing is not easy, if it is useful to you, please like it ^-^