效果
音訊播放,是比較常見或常用的功能,比如音樂播放器
、新聞播報
、聽書
等等,而恰巧如果你想自定義
一個音訊播放器的話,本文一定對你有幫助!
常用方法
- start() 開始播放
- pause() 暫停播放
- stop() 停止播放
- prepare() 資源準備
- prepareAsync() 非同步準備,不阻塞UI執行緒
- seekTo(int msec) 定位到指定位置,單位毫秒
- isLooping 是否迴圈播放
- isPlaying 播放狀態
- duration 總時長
- currentPosition 當前位置
- release() 資源釋放
Component Tree
具體的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("當前位置$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()
}
}
複製程式碼
主要 是一些播放器的監聽事件和按鈕操作事件。
著重介紹兩個:
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$minute" else "$minute"
val s: String = if (second < 10) "0$second" else "$second"
return "$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()
方法,不過我們可以手動把播放位置改到初始值,並呼叫播放。
/**
* 重新播放
*/
private fun audioRestart() {
mMediaPlayer?.run {
//定位到指定位置,單位毫秒
seekTo(0)
start()
seek_bar.progress = 0
tv_currentPosition.text = formatDuration(seek_bar!!.progress)
//如果是下一首,可以呼叫reset()重置,然後set新的資料來源
}
}
複製程式碼
如果是下一首,可以呼叫reset()
重置,然後set新的資料來源
。
資源回收
及時的回收,有利於更好的效能
。
override fun onDestroy() {
mAgentWeb.webLifeCycle.onDestroy()
super.onDestroy()
cancelTimer()
mMediaPlayer?.run {
stop()
release()
mMediaPlayer = null
}
}
複製程式碼
ok,到此就講解完了。
寫作不易,如果對你有用,點個贊吧 ^ - ^