Android MediaPlayer音频播放器详解

具体的xml代码就不贴了,看一下组件树

<>初始化

==============================================================

/**

  • 初始化 及 资源准备

*/

private fun audioPrepare(path: String) {

mMediaPlayer = MediaPlayer().apply {

setDataSource(path)//支持文件、网络地址、uri

prepareAsync()//异步准备,不阻塞UI线程

isLooping = false//循环播放

}

initMediaPlayerListener()

}

setDataSource,设置数据源,支持本地文件、网络请求的地址、uri等,看一下源码:

  • setDataSource(FileDescriptor)

  • setDataSource(String)

  • setDataSource(Context, Uri)

  • setDataSource(FileDescriptor, long, long)

  • setDataSource(MediaDataSource)

如果是本地文件,注意读写权限。

prepareAsync() 异步准备,不阻塞UI线程

然后看一下调用的initMediaPlayerListener 方法

<>播放器监听事件及交互

=====================================================================

/**

  • 播放器监听事件

*/

private fun initMediaPlayerListener() {

mMediaPlayer?.setOnBufferingUpdateListener { mp, percent ->

LogUtil.i("缓冲进度$percent%")

}

mMediaPlayer?.setOnPreparedListener {

LogUtil.i("准备完成")

//在准备完成之后获取信息,否则会有异常

val duration = mMediaPlayer?.duration//时长

val currentPosition = mMediaPlayer?.currentPosition//当前位置

LogUtil.i("当前位置 c u r r e n t P o s i t i o n / 时长 currentPosition/时长 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()

}

}

主要 是一些播放器的监听事件和按钮操作事件。

https://blog.csdn.net/yechaoa

着重介绍两个:

<>1、setOnPreparedListener

注意,在获取资源时长的时候,需要在播放器准备完成之后获取,否则会有异常

Attempt to call getDuration in wrong state: mPlayer=0x7244676280, mCurrentState=4

error (-38, 0)

并会回调OnErrorListener

然后设置显示,并把时长赋值给seek_bar的最大值。

<>2、setOnSeekBarChangeListener

3个方法:

  • onProgressChanged 进度改变

  • onStartTrackingTouch 开始拖动

  • onStopTrackingTouch 停止拖动

我们需要在改变中改变后对当前播放时长进行更新,并在最后的位置进行播放操作。

如果程序上没有定位到指定播放位置这种操作的话,不要在onProgressChanged中执行播放操作,因为频繁的进度改变,频繁的调用播放,会有杂音

所以建议用户手动拖动来触发播放。

如果非要程序可以跳到指定位置播放的话,建议如下操作:

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()

}

手动赋值progress ,并调用播放。

<>格式化播放时间

这个获取时长返回的是毫秒,所以我们还需要对其格式化操作。

/**

  • 格式化播放时间

*/

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 m i n u t e " e l s e " minute" else " minute"else"minute"

val s: String = if (second < 10) "0 s e c o n d " e l s e " second" else " second"else"second"

return " m : m: m:s"

}

做了一个判断,不足两位数则前位补0。

<>开始播放

===============================================================

/**

  • 开始播放

*/

private fun audioStart() {

mMediaPlayer?.run {

if (!this.isPlaying) {

start()

startTimer()

}

}

}

因为没有播放中的回调接口,所以这里启动一个Timer获取当前位置并更新UI

<>Timer更新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)

}

}

这里要注意,非ui线程不能更新view,所以这里赋值给seek_bar,在seek_bar的onProgressChanged 回调中去更新。

<>暂停播放

===============================================================

/**

  • 暂停播放

*/

private fun audioPause() {

mMediaPlayer?.run {

if (this.isPlaying) {

pause()

cancelTimer()

}

}

}

同样,暂停的时候取消Timer,做到资源及时回收。

<>取消Timer

private fun cancelTimer() {

mTimer?.run {

cancel()

mTimer = null

}

}

<>暂停/继续 播放

===================================================================

/**

  • 暂停/继续 播放

*/

private fun audioToggle() {

mMediaPlayer?.run {

if (this.isPlaying) {

audioPause()

} else {

audioStart()

}

}

}

如果只有一个事件触发的话,可以这么来写。

<>重新播放

===============================================================

播放器并没有自带restart()方法,不过我们可以手动把播放位置改到初始值,并调用播放。

相关推荐
_小马快跑_9 分钟前
ConstraintLayout之layout_constraintDimensionRatio属性详解
android
百锦再1 小时前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗2 小时前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO2 小时前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade3 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下3 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
青春给了狗5 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu5 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋7 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin