1. 程式人生 > 其它 >Android MediaPlayer音訊播放器詳解

Android MediaPlayer音訊播放器詳解

技術標籤:Android 知識點Android DemoMediaPlayer音訊播放器播放器setDataSourceandroid

效果

在這裡插入圖片描述
音訊播放,是比較常見或常用的功能,比如音樂播放器、新聞播報、聽書等等,而恰巧如果你想自定義一個音訊播放器的話,本文一定對你有幫助!

常用方法

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

如果是本地檔案,注意讀寫許可權。

然後看一下呼叫的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()
        }
    }

主要 是一些播放器的監聽事件和按鈕操作事件。

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$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,到此就講解完了。

寫作不易,如果對你有用,點個贊吧 ^ - ^