效果
音頻播放,是比較常見或常用的功能,比如音樂播放器、新聞播報(bào)、聽書等等,而恰巧如果你想自定義一個(gè)音頻播放器的話,本文一定對(duì)你有幫助!
常用方法
- start() 開始播放
- pause() 暫停播放
- stop() 停止播放
- prepare() 資源準(zhǔn)備
- prepareAsync() 異步準(zhǔn)備,不阻塞UI線程
- seekTo(int msec) 定位到指定位置,單位毫秒
- isLooping 是否循環(huán)播放
- isPlaying 播放狀態(tài)
- duration 總時(shí)長
- currentPosition 當(dāng)前位置
- release() 資源釋放
Component Tree
具體的xml代碼就不貼了,看一下組件樹
初始化
/** * 初始化 及 資源準(zhǔn)備 */ private fun audioPrepare(path: String) { mMediaPlayer = MediaPlayer().apply { setDataSource(path)//支持文件、網(wǎng)絡(luò)地址、uri prepareAsync()//異步準(zhǔn)備,不阻塞UI線程 isLooping = false//循環(huán)播放 } initMediaPlayerListener() }
setDataSource,設(shè)置數(shù)據(jù)源,支持本地文件、網(wǎng)絡(luò)請(qǐng)求的地址、uri等,看一下源碼:
- setDataSource(FileDescriptor)
- setDataSource(String)
- setDataSource(Context, Uri)
- setDataSource(FileDescriptor, long, long)
- setDataSource(MediaDataSource)
如果是本地文件,注意讀寫權(quán)限。
prepareAsync() 異步準(zhǔn)備,不阻塞UI線程
然后看一下調(diào)用的initMediaPlayerListener 方法
播放器監(jiān)聽事件及交互
/** * 播放器監(jiān)聽事件 */ private fun initMediaPlayerListener() { mMediaPlayer?.setOnBufferingUpdateListener { mp, percent -> LogUtil.i("緩沖進(jìn)度$percent%") } mMediaPlayer?.setOnPreparedListener { LogUtil.i("準(zhǔn)備完成") //在準(zhǔn)備完成之后獲取信息,否則會(huì)有異常 val duration = mMediaPlayer?.duration//時(shí)長 val currentPosition = mMediaPlayer?.currentPosition//當(dāng)前位置 LogUtil.i("當(dāng)前位置$currentPosition/時(shí)長$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("播放錯(cuò)誤") 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?) { //拖動(dòng)結(jié)束之后再設(shè)置,如果在onProgressChanged中設(shè)置會(huì)有雜音 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() } }
主要 是一些播放器的監(jiān)聽事件和按鈕操作事件。
https://blog.csdn.net/yechaoa
著重介紹兩個(gè):
1、setOnPreparedListener
注意,在獲取資源時(shí)長的時(shí)候,需要在播放器準(zhǔn)備完成之后獲取,否則會(huì)有異常:
Attempt to call getDuration in wrong state: mPlayer=0x7244676280, mCurrentState=4error (-38, 0)
并會(huì)回調(diào)OnErrorListener。
然后設(shè)置顯示,并把時(shí)長賦值給seek_bar的最大值。
2、setOnSeekBarChangeListener
3個(gè)方法:
- onProgressChanged 進(jìn)度改變
- onStartTrackingTouch 開始拖動(dòng)
- onStopTrackingTouch 停止拖動(dòng)
我們需要在改變中和改變后對(duì)當(dāng)前播放時(shí)長進(jìn)行更新,并在最后的位置進(jìn)行播放操作。
如果程序上沒有定位到指定播放位置這種操作的話,不要在onProgressChanged 中執(zhí)行播放操作,因?yàn)轭l繁的進(jìn)度改變,頻繁的調(diào)用播放,會(huì)有雜音。
所以建議用戶手動(dòng)拖動(dòng)來觸發(fā)播放。
如果非要程序可以跳到指定位置播放的話,建議如下操作:
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() }
手動(dòng)賦值progress ,并調(diào)用播放。
格式化播放時(shí)間
這個(gè)獲取時(shí)長返回的是毫秒,所以我們還需要對(duì)其格式化操作。
/** * 格式化播放時(shí)間 */ 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" }
做了一個(gè)判斷,不足兩位數(shù)則前位補(bǔ)0。
開始播放
/** * 開始播放 */ private fun audioStart() { mMediaPlayer?.run { if (!this.isPlaying) { start() startTimer() } } }
因?yàn)闆]有播放中的回調(diào)接口,所以這里啟動(dòng)一個(gè)Timer獲取當(dāng)前位置并更新UI
Timer更新UI
/** * 每隔一秒執(zhí)行一次,更新當(dāng)前播放時(shí)間 */ 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 回調(diào)中去更新。
暫停播放
/** * 暫停播放 */ private fun audioPause() { mMediaPlayer?.run { if (this.isPlaying) { pause() cancelTimer() } } }
同樣,暫停的時(shí)候取消Timer,做到資源及時(shí)回收。
取消Timer
private fun cancelTimer() { mTimer?.run { cancel() mTimer = null } }
暫停/繼續(xù) 播放
/** * 暫停/繼續(xù) 播放 */ private fun audioToggle() { mMediaPlayer?.run { if (this.isPlaying) { audioPause() } else { audioStart() } } }
如果只有一個(gè)事件觸發(fā)的話,可以這么來寫。
重新播放
播放器并沒有自帶restart()方法,不過我們可以手動(dòng)把播放位置改到初始值,并調(diào)用播放。
/** * 重新播放 */ private fun audioRestart() { mMediaPlayer?.run { //定位到指定位置,單位毫秒 seekTo(0) audioStart() seek_bar.progress = 0 tv_currentPosition.text = formatDuration(seek_bar!!.progress) //如果是下一首,可以調(diào)用reset()重置,然后set新的數(shù)據(jù)源 } }
如果是下一首,可以調(diào)用reset()重置,然后set新的數(shù)據(jù)源。
資源回收
及時(shí)的回收,有利于更好的性能。
override fun onDestroy() { mAgentWeb.webLifeCycle.onDestroy() super.onDestroy() cancelTimer() mMediaPlayer?.run { stop() release() mMediaPlayer = null } }
ok,到此就講解完了。
寫作不易,如果對(duì)你有用,點(diǎn)個(gè)贊吧 ^ – ^
版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。