Detailed Android MediaPlayer audio player

effect

Insert picture description here
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 xmlcode will not be posted, look at the component tree
Insert picture description here

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 initMediaPlayerListenermethod 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.

https://blog.csdn.net/yechaoa

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_barthe 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 onProgressChangedperform 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 progressand 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 Timerget 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_barin 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 ^-^

Guess you like

Origin blog.csdn.net/yechaoa/article/details/112294145