MediaPlayerのインスタンスを生成する方法は、コンストラクタを使用するほかに「MediaPlayer#create(……)」メソッドを使用する方法があります。
// コンストラクタを使用するサンプル String path = ……; MediaPlayer player1 = new MediaPlayer(); player1.setDataSource(path); player1.prepare(); player1.start(); // createメソッドを使用するサンプル Uri uri = ……; MediaPlayer player2 = MediaPlayer.create(getApplicationContext(), uri); player2.start(); // prepare()は必要ない
「MediaPlayer#create(……)」は、リソースを指定する必要があります。生成済みのたMediaPlayerは指定したリソースをすでに読み込み済みであることなどから、「このインスタンスは、このリソースで固定」というような意気込みで使用するとよいかもしれません。今回のサンプルアプリは音楽を1曲ずつ次々に再生する音楽プレイヤーなので、前者の方法で初期化しています。
なお今回は使用していませんが、ネットワーク上の音楽ファイルをURLを指定してMediaPlayerから直接再生することも可能です。その場合「AndroidManifest.xml」に以下のパーミッションが必要です。
<uses-permission android:name="android.permission.INTERNET" />
前述した「MediaPlayer#prepre()」「MediaPlayer#prepareAsync()」メソッドは、同期と非同期の違いがありますが、MediaPlayerにリスナを設定することで、どちらでも読み込み完了イベントを通知します。以下はデータを非同期で読み込み、読み込み完了と同時に再生を行うサンプルです。
String path = ……; MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(path); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onCompletion(MediaPlayer mp) { mp.start(); } }); mediaPlayer.prepareAsync();
今回のサンプルアプリの場合、Activityだけの音楽プレイヤーでは同期で、サービスをリモートから制御するサンプルでは非同期で、データを読み込んでいるので参考にしてみてください。
音楽データは、アプリ内にリソースとして持つ場合や、ネットワーク上からダウンロードして再生する場合は、アプリからの指定やユーザーによる指定でデータの場所を特定できます。しかし、SDカードなどの外部ストレージ上にあるデータを再生するには、データを検索する方が便利です。
検索自体の全処理はサンプルアプリ内の「Item」というクラスを参照してもらうこととし、ここではコードを抜粋してポイントを説明します。
// ContentResolver を取得 ContentResolver cr = context.getContentResolver(); // 外部ストレージから音楽を検索 Cursor cur = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Audio.Media.IS_MUSIC + " = 1", null, null); if (cur != null) { …… // カーソルを閉じる cur.close(); }
外部ストレージから音楽ファイルを検索するには、上記のように「ContentResolver#query(……)」メソッドを使用して得た「Cursor」を用いて順次結果を取得すればいいのですが、取得し終えたら最後に「Cursor#close()」を呼び出さなければなりません。
「Cursor」で得られた結果をパスやファイル名でソートしているわけではないため、自分で都合の良いようにソートしなければなりません。
@Override public int compareTo(Object another) { if (another == null) { return 1; } Item item = (Item) another; int result = album.compareTo(item.album); if (result != 0) { return result; } return truck - item.truck; }
今回は「Item」クラスに「Comparable」インターフェイスを実装して、アルバム単位でソートすることにしてあります。
ソートに使用する曲情報は、以下のように取得します。
// 曲情報のカラムを取得 int artistColumn = cur.getColumnIndex(MediaStore.Audio.Media.ARTIST); int titleColumn = cur.getColumnIndex(MediaStore.Audio.Media.TITLE); int albumColumn = cur.getColumnIndex(MediaStore.Audio.Media.ALBUM); int durationColumn = cur.getColumnIndex(MediaStore.Audio.Media.DURATION); int idColumn = cur.getColumnIndex(MediaStore.Audio.Media._ID); int idTruck = cur.getColumnIndex(MediaStore.Audio.Media.TRACK); // リストに追加 do { items.add(new Item(cur.getLong(idColumn), cur.getString(artistColumn), cur.getString(titleColumn), cur.getString(albumColumn), cur.getInt(idTruck), cur.getLong(durationColumn))); } while (cur.moveToNext());
音楽プレイヤー画面に表示する情報やリモコンに渡す情報も併せて取得してItemクラスに保持しています。
今回のサンプルを動かすためには、外部ストレージに音楽ファイルを保存しておく必要があります。
実機ならUSB接続してコピーすればいいので、ここではエミュレータでの準備方法を説明します。SDカードイメージを作成して、そこに音楽データを書き込んで、エミュレータ起動時にマウントして、という手順もありますが、今回はもっと簡単な方法を紹介します。
エミュレータを起動している状態で、EclipseのADTの[DDMS]パースペクティブを開き、[File]タブを選択し、「/mnt/sdcard」を開きます。作ったばかりのAVDだと「/mnt/sdcard」にフォルダがないかもしれないので、その場合は「Music」などのフォルダを右上の[+]ボタンで作成し、ここに音楽ファイル(MP3を推奨)をエクスプローラからまとめてドラッグ&ドロップします。
コピーが完了したら、エミュレータの[Dev Tools]を起動して[Media Provider]を選択します。
画面上の[Scan SD card]をタップして、外部ストレージをスキャンすることで、前述の「ContentResolver#query(……)」メソッドで見つけられるようになります。
なお、Media Provider画面上にある[Insert 20 albums]は、きちんとタグ付けされたダミー音楽ファイルを20アルバム分生成してくれるボタンで、アルバムや音楽ファイルを大量に使用して検査したい場合に便利です。
冒頭の「状態遷移」の説明では、「MediaPlayerには、現在の状態を問い合わせるメソッドが提供されていない」と説明しましたが、現在再生中であるかどうかだけは「MediaPlayer#isPlaying()」で問い合わせ可能です。状態管理では、これを最大限に活用します。
再生、一時停止、停止のメソッドは、それぞれ「MediaPlayer#start()」「MediaPlayer#pause()」「MediaPlayer#stop()」です。
ここで、サンプルの音楽プレイヤー画面を、もう一度見てみましょう。
上からアーティスト名、アルバム名、曲タイトル、再生時間、各種コントロールです。コントロールをタップした際に、MediaPlayerの状態に応じてMediaPlayerを制御し、その結果を画面に反映しなければなりません。この処理は、「MediaPlayerActivity#onClick(View)」で行っているので、詳しくはサンプルアプリのソースコードを参照してください。
なお、ボタンをタップした際に点滅するのは、データ読み込み中の操作を防ぐためです。データ読み込み中も操作を受け付けるようにするには、キューなどを用意し、データ読み込み完了後にキューから取り出して処理をするというような工夫が必要です。
デバイスによってはメディア再生ボタンが付いていたり、Bluetoothあるいは有線のヘッドセットにメディア再生ボタンが付いている場合があります。
それらのボタンを押したら、以下のように再生または一時停止を行うように「Activity#onKeyDown(……)」をオーバーライドします。
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_HEADSETHOOK: onClick(mButtonPlayPause); return true; } return super.onKeyDown(keyCode, event); }
「MediaPlayer#setOnCompletionListener(MediaPlayer.OnCompletionListener)」を使うと、再生完了イベントを受け取れます。これにより連続再生を実現しているのが以下のコードです。
@Override public void onCompletion(MediaPlayer mp) { mHandler.post(new Runnable() { @Override public void run() { onClick(mButtonSkip); while (!mButtonPlayPause.isEnabled()) { try { Thread.sleep(100); } catch (InterruptedException e) { } } onClick(mButtonPlayPause); } }); }
スキップボタンをタップしたのと同じ処理を行い、次のデータの読み込みが完了するのを待ち、再生ボタンをタップしたのと同じ処理を行っています。
Activityが終了する際に、MediaPlayerインスタンスを解放します。
@Override protected void onPause() { super.onPause(); if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mChronometer.stop(); } }
「MediaPlayer#reset()」して「MediaPlayer#release()」することで、状態がIdleを経由してEndに遷移し、MediaPlayerを解放できます。
もし「MediaPlayer#prepareAsync()」で非同期でデータを読み込むようにしている場合、まれに「Preparing」状態で「MediaPlayer#reset()」を呼び出すことになるので、状態管理を十分に考慮して解放を遅延させるなどして回避するようにしてください。
次ページでは、MediaPlayerのリモート操作について解説します。
Copyright © ITmedia, Inc. All Rights Reserved.