Android MediaPlayer 音樂播放器

MediaPlayer Demo

在 Android 中要播放多媒體檔案,需要使用 MediaPlayer。MediaPlayer 可以播放 Video 及 Audio 檔。

簡介

這篇文章主要以如何製作一個簡易的音樂播放器,來瞭解 MediaPlayer 的使用方式。MediaPlayer 主要用來播放聲音 (Sound) 及視訊 (Video),另外有一個 AudioManager 則只用來播放音效檔(比較短的聲音片段)。

先瞭解多媒體檔來源有哪些,共3種來源:
  • Local resources:專案資源檔,即放置在 raw 目錄中的檔案,編譯時會同時加入 apk 檔中。
  • Internal URIs:內部資源路徑,放置在 sdcard 中的檔案。
  • External URLs:外部檔案位址,即串流檔,例如從網路即時下載串流播放的檔案。
Android 支援的多媒體檔案格式請查閱 Android Supported Media Formats

如何使用 MediaPlayer

首先要瞭解 MediaPlayer 的狀態,請參考它的狀態圖:
基本上只要照著狀態機圖的狀態改變去寫,就不會出錯;意思就是若程式的流程沒有符合狀態的改變,就可能出錯(好吧~我好像癈話了)。

一開始,當你 new 了一個 MediaPlayer 物件出來後 (或是呼叫 reset() 方法),就會產生一個閒置的物件,即 Idle。當我們呼叫 release() 方法時,則會釋放掉這個被佔用的資源,所以記住,建立時使用 new (或 create() 方法),結束時使用 release() 來釋放資源,這不能忘掉。

接下來要設定資料來源( setDataSource() ),這個方法會告訴 MediaPlayer 多媒檔的位置。接著呼叫 prepare() 方法預置,然後就可以呼叫 start() 方法來播放音樂。但 Prepared 會有緩衝,所以有時候不會立即播放,要等緩衝完成才會播放。這時候就會有一個相同但非同步的方法 prepareAsync() 來讓你使用。

播放狀態的改變,呼叫 start() 後開始播放,在播放中呼叫 pause() 會進入暫停狀態,這時只要再度呼叫 start() 即可回到播放狀態。

在播放狀態 (Started) 或暫停狀態 (Paused) 只要呼叫 stop() 就會進入停止狀態 (Stopped);或是音樂整個播完了 (PlaybackCompleted),也會自動跳到停止 (Stopped) 狀態。這時候要再度播放音樂只有一個方法,就是預置 (Prepared),看看 Stopped 狀態指出去的箭頭便知。

當你要知道音樂是否已經播完,可以去偵聽 onCompletion 事件。seekTo() 方法可以讓你將播放指針移動到指定的毫秒數。

這些都瞭解了,就能玩基本的播放、暫停、停止、下一首、上一首及自動下一首。以下範例不會使用 setDataSource() 及 prepare() ,而是使用另一個 create() 方法來取得 MediaPlayer 。

程式碼:
//MediaPlayer物件
private MediaPlayer mediaPlayer;
//儲存音樂清單
private LinkedList<Song> songList;
//音樂播放索引(播到哪一首)
private int index = 0;
//是否為暫停狀態
private boolean isPause;

private void doStop() {
  if (mediaPlayer != null) {
    isPause = false;
    mediaPlayer.stop();
    btnPlay.setText("Play");
  }
}

private void doPlay() {
  if (songList == null || songList.size() == 0) {
    return;
  }
  if (btnPlay.getText().toString().equals("Play")) {
    playing();
    btnPlay.setText("Pause");
  }else{
    isPause = true;
    mediaPlayer.pause();
    btnPlay.setText("Play");
  }
}

private void doNext() {
  if (songList == null || songList.size() == 0) {
    return;
  }
  if (index < songList.size() - 1) {
    index++;
    isPause = false;
    playing();
    btnPlay.setText("Pause");
  }
}

private void doPrev() {
  if (songList == null || songList.size() == 0) {
    return;
  }
  if (index > 0) {
    index--;
    isPause = false;
    playing();
    btnPlay.setText("Pause");
  }
}

private void playing(){
  if (mediaPlayer != null && !isPause) {
    mediaPlayer.release();
    mediaPlayer = null;
  }
  if (mediaPlayer == null) {
    long id = songList.get(index).getId();
   Uri songUri = ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
   mediaPlayer = MediaPlayer.create(this, songUri);
   mediaPlayer.setOnCompletionListener(this);
}
  mediaPlayer.start();
  txtSongName.setText("曲目: " + songList.get(index).getTitle() +
                      "\n專輯: " + songList.get(index).getAlbum() +
                     "\n(" + (index + 1) + "/" + songList.size() + ")");
}

@Override
public void onCompletion(MediaPlayer mp) {
  doNext();
}

@Override
protected void onDestroy() {
  super.onDestroy();
  if (mediaPlayer != null) {
    mediaPlayer.release();
   mediaPlayer = null;
  }
}
以上就是音樂播放器的基本功能。

使用 Content Resolver 取得音樂檔

使用ContentResolver可以取得 /sdcard/music 中的音樂檔案。

程式碼如下:
private void getMusics(){
  songList = new LinkedList<Song>();
  ContentResolver contentResolver = getContentResolver();
  Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
  Cursor cursor = contentResolver.query(uri, null, null, null, null);
  if (cursor == null) {
    Log.d("=======>", "查詢錯誤");
} else if (!cursor.moveToFirst()) {
    Log.d("=======>", "沒有媒體檔");
} else {
    int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
    int albumColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.AudioColumns.ALBUM);
   do {
      long thisId = cursor.getLong(idColumn);
      String thisTitle = cursor.getString(titleColumn);
      String thisAlbum = cursor.getString(albumColumn);
      Log.d("=======>", "id: " + thisId + ", title: " + thisTitle);
      Song song = new Song();
      song.setId(thisId);
      song.setTitle(thisTitle);
      song.setAlbum(thisAlbum);
     
      songList.add(song);
   } while (cursor.moveToNext());
  }
  txtSongName.setText("共有 " + songList.size() + " 首歌曲");
}
接著就能以 id 來取得其 uri 的資料,送給 MediaPlayer 來使用。程式碼如下:
if (mediaPlayer == null) {
  long id = songList.get(index).getId();
  Uri songUri = ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

  mediaPlayer = MediaPlayer.create(this, songUri);
  mediaPlayer.setOnCompletionListener(this);
}
mediaPlayer.start();

範例檔下載:MediaPlayerDemo.zip

補充

要把音樂上傳到模擬器上,請在命令列中輸入指令。(請保持模擬器的開啟,且只有一個,兩個以上請使用 -s 指定模擬器)

先建立 music 目錄

adb shell
cd sdcard
ls (查看是否已有music目錄,若有,下一步就不用做)
mkdir music (建立 music 目錄)
exit (離開回到c:/>)

開始上傳

adb push "D:\music\AAA\song.mp3" /sdcard/music
這樣就會把 song.mp3 上傳到 music 目錄,記得要用雙引號包住,防止中文及空白會發生的錯誤。

參考資料

本文網址:http://blog.tonycube.com/2012/12/mediaplayer.html
Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀

5 則留言

  1. 作者已經移除這則留言。

    回覆刪除
  2. 我有一個問題,首先我是利用listview來播放音樂的(當中有十首歌曲),我想問一下,如果我想直接播放第2首歌曲(case0 to case1),可以先不用停止case0正在播放的音樂,而直接播放case1的歌曲嗎???

    回覆刪除
    回覆
    1. 把範例中的 doNext() 或 doPrev() 拿來改就可以了。
      private void playTrack(int track) {
      index = track;
      isPause = false;
      playing();
      btnPlay.setText("Pause");
      }

      等於把原本的 index上下移動,改成直接指定,這個 track 的值就是你的清單按下去後的索引值。上面的程式碼要自己試試看吧,我不確定能不能跑。

      刪除
  3. 如果想撥放mov檔
    有什麼辦法嗎

    回覆刪除
    回覆
    1. 不支援播放 mov
      https://developer.android.com/guide/appendix/media-formats.html
      可以去github上找看看

      刪除

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。