連載
» 2012年03月28日 00時00分 公開

Androidアプリでマルチメディアを扱うための基礎知識Androidで動く携帯Javaアプリ作成入門(30)(2/3 ページ)

[緒方聡,株式会社イーフロー]

MediaPlayerクラスの初期化

 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カードイメージを作成して、そこに音楽データを書き込んで、エミュレータ起動時にマウントして、という手順もありますが、今回はもっと簡単な方法を紹介します。

図2 [DDMS]パースペクティブの[File]タブ 図2 [DDMS]パースペクティブの[File]タブ

エミュレータを起動している状態で、EclipseのADTの[DDMS]パースペクティブを開き、[File]タブを選択し、「/mnt/sdcard」を開きます。作ったばかりのAVDだと「/mnt/sdcard」にフォルダがないかもしれないので、その場合は「Music」などのフォルダを右上の[+]ボタンで作成し、ここに音楽ファイル(MP3を推奨)をエクスプローラからまとめてドラッグ&ドロップします。

コピーが完了したら、エミュレータの[Dev Tools]を起動して[Media Provider]を選択します。

図3 [Media Provider]でスキャンしておく 図3 [Media Provider]でスキャンしておく

画面上の[Scan SD card]をタップして、外部ストレージをスキャンすることで、前述の「ContentResolver#query(……)」メソッドで見つけられるようになります。

なお、Media Provider画面上にある[Insert 20 albums]は、きちんとタグ付けされたダミー音楽ファイルを20アルバム分生成してくれるボタンで、アルバムや音楽ファイルを大量に使用して検査したい場合に便利です。


MediaPlayerの再生・一時停止・停止

 冒頭の「状態遷移」の説明では、「MediaPlayerには、現在の状態を問い合わせるメソッドが提供されていない」と説明しましたが、現在再生中であるかどうかだけは「MediaPlayer#isPlaying()」で問い合わせ可能です。状態管理では、これを最大限に活用します。

 再生、一時停止、停止のメソッドは、それぞれ「MediaPlayer#start()」「MediaPlayer#pause()」「MediaPlayer#stop()」です。

 ここで、サンプルの音楽プレイヤー画面を、もう一度見てみましょう。

図4 音楽プレイヤーレイアウト 図4 音楽プレイヤーレイアウト

 上からアーティスト名、アルバム名、曲タイトル、再生時間、各種コントロールです。コントロールをタップした際に、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);
        }
    });
}

 スキップボタンをタップしたのと同じ処理を行い、次のデータの読み込みが完了するのを待ち、再生ボタンをタップしたのと同じ処理を行っています。

終了時のMediaPlayerのインスタンス解放

 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.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。