今天學習使用 MediaPlayer 來播放 mp3 檔案
首先要建立 MediaPlayer 類別
這邊的想法是,建構類別時,把完成後 UI 要做的事情當作 CallBack 傳進來
然後在播放器的完成事件裡觸發它。
其它為相關的方法,後面會一一討論。

class CusMediaPlayer(ctx: Context, val callBack: () -> Unit) {
    var mediaPlayer: MediaPlayer = MediaPlayer.create(ctx, R.raw.cc)
    init {
        mediaPlayer.setOnCompletionListener {
            callBack.invoke()
    fun seekToProgress(progress: Int) {...}
    fun getDuration(): Int {...}
    fun getCurrentPosition(): Int {...}
    fun adjustVolume(value: Float) {...}
    fun playOrPauseMusic(): String {...}
    fun stopMusic(): String {...}

綁定音樂資源

首先我們要在 res 資料夾中先新增一個命名為 raw 的資料夾,並把要使用的 mp3 檔案放進去
接著就可以使用 MediaPlayer.create 方法建立 player

var mediaPlayer: MediaPlayer = MediaPlayer.create(ctx, R.raw.cc)

開始與暫停播放

由於播放和暫停鈕是同一個按鈕,我們希望在同一個方法處理它
可以使用 isPlaying 來判斷是否正在播放
最後順便把按鈕應該顯示的文字回傳回去

fun playOrPauseMusic(): String {
    if (mediaPlayer.isPlaying) {
        mediaPlayer.pause()
        return "Play"
    } else {
        mediaPlayer.start()
        return "Pause"

停止音樂時,首先要把播放的進度使用 seekTo() 方法回歸到 0 (以毫秒計算)
接著呼叫 stop() 方法停止
由於 MediaPlayer 也有自己類似生命週期的定義,所以在 stop() 後會發現其他方法如 start()、pause()...等會失效,此時就要使用 prepare() 方法重新準備好它。

fun stopMusic(): String {
    mediaPlayer.seekTo(0)
    mediaPlayer.stop()
    mediaPlayer.prepare()
    return "Play"

呼叫 setVolume() 方法來設定,有分左聲道和右聲道,100% 為 1f

fun adjustVolume(value: Float) {
    mediaPlayer.setVolume(value / 100f, value / 100f)

在 MainActivity 設定 SeekBar 的 setOnSeekBarChangeListener,會在拉動 SeekBar 時觸發。

 seekBarVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                cusMediaPlayer.adjustVolume(progress.toFloat())
            override fun onStartTrackingTouch(seekBar: SeekBar?) {}
            override fun onStopTrackingTouch(seekBar: SeekBar?) {}

設定音樂進度條

一樣是在 onProgressChanged 事件中去呼叫事件,實際是呼叫自訂類別裡的
seekTo() 方法來達成
這邊要注意到我們需要一個 flag 來判斷是不是正在做 "拉動" 這個動作,因為後面我們會使用 Runnable 來讓進度條自動跟著音樂的進度前進,如果沒有做這個判斷的話,這邊就會在沒拉動的時候也不斷觸發。

 seekBarProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onStopTrackingTouch(seekBar: SeekBar?) {
                isSeeking = false
            override fun onStartTrackingTouch(seekBar: SeekBar?) {
                isSeeking = true
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                if (isSeeking) {
                    cusMediaPlayer.seekToProgress(progress)
fun seekToProgress(progress: Int) {
    mediaPlayer.seekTo(progress)

讓進度條跟著音樂進度更新

首先在 MainActivity 中,先把進度條的總長度設定成跟音樂的總毫秒數一樣多

seekBarProgress.max = cusMediaPlayer.getDuration()
fun getDuration(): Int {
    return mediaPlayer.duration

接著寫一個更新進度條的方法,思路為在 runnable 中呼叫 runnable,達成類似 Timer 的效果
也就是每 500 毫秒更新一次進度條

private fun asyncProgressBar() {
    handler = Handler()
    runnable = Runnable {
        seekBarProgress.progress = cusMediaPlayer.getCurrentPosition()
        handler.postDelayed(runnable, 500)
    runnable.run()
fun getCurrentPosition(): Int {
    return mediaPlayer.currentPosition

後續動畫和錄音部分會放在下篇討論。