2014年12月4日木曜日


Android Auto用の音楽再生アプリを作ってみました。まずは再生、曲送りと曲戻しのみ行う簡単なものから挑戦しました。再生操作にはMediaSessionクラスを使用します。

■通常の音楽再生アプリと違う点

Android Auto用の音楽再生アプリは車載デバイスのためのオーディオサービスを提供します。このサービスによって、車載デバイスはメディアデータの一覧を参照し、カーステレオなどから音楽を再生することができます。

■専用リソースファイルのメタデータ定義

AndoridManifestファイルにはapplicationタグ内に以下のように定義し、
<meta-data
    android:name="com.google.android.gms.car.application"
    android:resource="@xml/automotive_app_desc" />
別途、専用リソースファイル\res\xml\automotive_app_desc.xmlを用意します。

■専用リソースファイルの定義

AndroidManifestファイルで定義した専用リソースファイルautomotive_app_desc.xmlには以下のように記載します。音楽を再生するためのクラスを使用する場合は"media"を定義します。メッセージアプリを使用する場合は"notification"と定義します。
<automotiveapp/>
    <uses name="media"/>
</automotiveapp/>

■サービスの実装

AndroidManifestファイルで定義したMediaBrowserService継承サービスを実装します。onGetRoot()とonLoadChildren()の実装が必須です。

■MediaSessionクラスについて

音楽再生操作を行うため、MediaSessionクラスを使用しています。MediaSessionクラスは、一連のメディア再生処理を「セッション」とし、他のクラス(MediaControllerクラス、MediaController.TransportControlsクラスなど)と紐づいて再生操作を一括して管理することができます。 関連クラスを含め、具体的に下記のことができます。

  • 再生、一時停止や曲送りなどの再生操作
  • 再生情報の表示
  • 音量変更
  • 再生状態の変更やメディアボタンのイベントをコールバックで取得
なお、再生処理そのものはMediaPlayerクラスなどで行います。

■クラス変数とonCreate()の実装

MediaBrowserService継承サービス、AndroidAutoMediaServiceクラスを定義しました。再生操作にMediaSessionクラス、再生処理にMediaPlayerクラスを使用しています。他に再生データをリスト管理するmPlayingQueue、現在の再生インデックスを保持するmCurrentQueueIndexを定義しています。onCreate()ではMediaSessionクラスの生成およびコールバックの設定などを行っています。
public class AndroidAutoMediaService extends MediaBrowserService implements 
       OnPreparedListener, OnCompletionListener, OnErrorListener{
    private MediaSession mMediaSession;                 // MediaSessionクラス
    private MediaPlayer  mMediaPlayer;                  // MediaPlayerクラス
    private List<MediaSession.QueueItem> mPlayingQueue; // 再生キューリスト
    private int mCurrentQueueIndex;                     // 再生キューのインデックス
    private static final String MEDIA_ID_ROOT = "__ROOT__";

    @Override
    public void onCreate() {
        super.onCreate();

        // 再生リストのオブジェクトを作る
        mPlayingQueue = new ArrayList<MediaSession.QueueItem>();

        // MediaSessionを生成
        mMediaSession = new MediaSession(this, "MyMediaSession");
        setSessionToken(mMediaSession.getSessionToken());
        
        // コールバックを設定
        mMediaSession.setCallback(new MyMediaSessionCallback());

        // 再生キューの位置を初期化
        mCurrentQueueIndex = 0;
    }

■onGetRoot()とonLoadChildren()の実装

onGetRoot()とonLoadChildren()の実装をします。onGetRoot()について今回はROOTを固定で返していますが、引数のclientPackageNameおよびclientUidでクライアントを判断してnullを返すことで、特定のクライアント以外からの参照を許可しないようにすることもできます。onLoadChildren()はメディア項目に関する情報を返すメソッドで、引数のresultにデータを設定します。

MediaItemの種類としてFLAG_PLAYABLEとFLAG_BROWSABLEがあります。リスト選択によって再生を開始したい場合はFLAG_PLAYABLEを、リストの表示のみを行いたい場合はFLAG_BROWSABLEを設定してください。FLAG_PLAYABLE設定時にリスト選択をすると後述のMediaSessionクラスのコールバックメソッドonPlayFromMediaId()がコールされますので、そのタイミングで再生処理を行います。
    @Override
    public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
        Bundle rootHints) {
        return new BrowserRoot(MEDIA_ID_ROOT, null);
    }

    @Override
    public void onLoadChildren(String parentId, Result <List<MediaItem>> result) {
        List<MediaBrowser.MediaItem> mediaItems = new ArrayList<MediaBrowser.MediaItem>();

        // 再生データ情報の設定
        MediaDescription.Builder mdb1 = new MediaDescription.Builder();
        
        // 再生データ1
        final Bundle mediaBundle1 = new Bundle();
        mediaBundle1.putString("Path", "mnt/sdcard/M01.mp3"); // データパス
        mediaBundle1.putInt("Index", 0);                      // 曲のインデックス
        mdb1.setMediaId("MediaID01");                         // メディアID
        mdb1.setTitle("Title01");                             // タイトル
        mdb1.setSubtitle("SubTitle01");                       // サブタイトル
        mdb1.setExtras(mediaBundle1);

        // 再生データ2
        MediaDescription.Builder mdb2 = new MediaDescription.Builder();
        final Bundle mediaBundle2 = new Bundle();
        mediaBundle2.putString("Path", "mnt/sdcard/M02.mp3"); // データパス
        mediaBundle2.putInt("Index", 1);                      // 曲のインデックス
        mdb2.setMediaId("MediaID02");                         // メディアID
        mdb2.setTitle("Title02");                             // タイトル
        mdb2.setSubtitle("SubTitle02");                       // サブタイトル
        mdb2.setExtras(mediaBundle2);
        
        mediaItems.add(new MediaBrowser.MediaItem(mdb1.build(),
                MediaBrowser.MediaItem.FLAG_PLAYABLE));        
        mediaItems.add(new MediaBrowser.MediaItem(mdb2.build(),
                MediaBrowser.MediaItem.FLAG_PLAYABLE));

        // sendResult()をコールする前にdetach()のコールが必要
        result.detach();
        result.sendResult(mediaItems);

        // 再生キューをセッションに設定
        mPlayingQueue.add(new MediaSession.QueueItem(mdb1.build(), 0));
        mPlayingQueue.add(new MediaSession.QueueItem(mdb2.build(), 1));
        mMediaSession.setQueue(mPlayingQueue);
    }

■再生処理を実装

MediaPlayerクラスによる再生処理を行うメソッドを実装します。基本的なMediaPlayerの実装手順で問題ありません。再生データのパスはMediaSession.QueueItemで管理させています。
    private void playMusic() {
        // プレイヤー生成と準備(生成済みの場合はリセット)
        if (mMediaPlayer == null) {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnErrorListener(this);
        } else {
            mMediaPlayer.reset();
        }

        // 現在のキュー位置を元に再生データのパスを取得する
        MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentQueueIndex);
        String path = queueItem.getDescription().getExtras().getString("Path");

        // MediaPlayerのデータ設定と準備(非同期)
        try {
            mMediaPlayer.setDataSource(path);
            mMediaPlayer.prepareAsync();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        // 再生準備完了通知を受け、再生を行う
        if(mMediaPlayer != null) {
            mMediaPlayer.start();
        }
    }

■MediaSessionクラスのコールバック

MediaSessionのコールバックを下記のように実装します。リストを選択するとonPlayFromMediaId()、曲送りを選択するとonSkipToNext()、曲戻しを選択するとonSkipToPrevious()がコールされます。



    private final class MyMediaSessionCallback extends MediaSession.Callback {
        @Override
        public void onPlay() {
            playMusic();
        }

        // リスト選択時にコールされる
        @Override
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            // MediaIDを元に曲のインデックス番号を検索し、設定する。
            for(int i = 0; i < mPlayingQueue.size(); i++) {
                MediaSession.QueueItem queueItem = mPlayingQueue.get(i);
                MediaDescription md = queueItem.getDescription();
                if(mediaId.equals(md.getMediaId())) {
                    mCurrentQueueIndex = md.getExtras().getInt("Index");
                }
            }
            playMusic();
        }

        @Override
        public void onSkipToNext() {
            // 再生キューの位置を次へ
            mCurrentQueueIndex++;
            if (mCurrentQueueIndex >= mPlayingQueue.size()) {
                mCurrentQueueIndex = 0;
            }
            playMusic();
        }

        @Override
        public void onSkipToPrevious() {
            // 再生キューの位置を前へ
            mCurrentQueueIndex--;
            if (mCurrentQueueIndex < 0) {
                mCurrentQueueIndex = 0;
            }
            playMusic();
        }

■onDestroy()の実装

終了処理を実装します。
    @Override
    public void onDestroy() {
        super.onDestroy();
        // MediaSessionの終了処理
        mMediaSession.setCallback(null);
        mMediaSession.release();
        
        // MediaPlayerの終了処理
        if(mMediaPlayer != null) {
            mMediaPlayer.setOnPreparedListener(null);
            mMediaPlayer.setOnCompletionListener(null);
            mMediaPlayer.setOnErrorListener(null);
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }
今回は割愛しましたが、MediaSessionクラスを正確に動作させるためには、setPlaybackState()にて再生状態や停止状態などを操作に合わせて設定する必要があります。

MediaSessioinおよびその関連クラスについては下記を参照してください。
http://developer.android.com/reference/android/media/session/package-summary.html

Android Auto用音楽再生アプリの作り方


Android Auto用の音楽再生アプリを作ってみました。まずは再生、曲送りと曲戻しのみ行う簡単なものから挑戦しました。再生操作にはMediaSessionクラスを使用します。

■通常の音楽再生アプリと違う点

Android Auto用の音楽再生アプリは車載デバイスのためのオーディオサービスを提供します。このサービスによって、車載デバイスはメディアデータの一覧を参照し、カーステレオなどから音楽を再生することができます。

■専用リソースファイルのメタデータ定義

AndoridManifestファイルにはapplicationタグ内に以下のように定義し、
<meta-data
    android:name="com.google.android.gms.car.application"
    android:resource="@xml/automotive_app_desc" />
別途、専用リソースファイル\res\xml\automotive_app_desc.xmlを用意します。

■専用リソースファイルの定義

AndroidManifestファイルで定義した専用リソースファイルautomotive_app_desc.xmlには以下のように記載します。音楽を再生するためのクラスを使用する場合は"media"を定義します。メッセージアプリを使用する場合は"notification"と定義します。
<automotiveapp/>
    <uses name="media"/>
</automotiveapp/>

■サービスの実装

AndroidManifestファイルで定義したMediaBrowserService継承サービスを実装します。onGetRoot()とonLoadChildren()の実装が必須です。

■MediaSessionクラスについて

音楽再生操作を行うため、MediaSessionクラスを使用しています。MediaSessionクラスは、一連のメディア再生処理を「セッション」とし、他のクラス(MediaControllerクラス、MediaController.TransportControlsクラスなど)と紐づいて再生操作を一括して管理することができます。 関連クラスを含め、具体的に下記のことができます。

  • 再生、一時停止や曲送りなどの再生操作
  • 再生情報の表示
  • 音量変更
  • 再生状態の変更やメディアボタンのイベントをコールバックで取得
なお、再生処理そのものはMediaPlayerクラスなどで行います。

■クラス変数とonCreate()の実装

MediaBrowserService継承サービス、AndroidAutoMediaServiceクラスを定義しました。再生操作にMediaSessionクラス、再生処理にMediaPlayerクラスを使用しています。他に再生データをリスト管理するmPlayingQueue、現在の再生インデックスを保持するmCurrentQueueIndexを定義しています。onCreate()ではMediaSessionクラスの生成およびコールバックの設定などを行っています。
public class AndroidAutoMediaService extends MediaBrowserService implements 
       OnPreparedListener, OnCompletionListener, OnErrorListener{
    private MediaSession mMediaSession;                 // MediaSessionクラス
    private MediaPlayer  mMediaPlayer;                  // MediaPlayerクラス
    private List<MediaSession.QueueItem> mPlayingQueue; // 再生キューリスト
    private int mCurrentQueueIndex;                     // 再生キューのインデックス
    private static final String MEDIA_ID_ROOT = "__ROOT__";

    @Override
    public void onCreate() {
        super.onCreate();

        // 再生リストのオブジェクトを作る
        mPlayingQueue = new ArrayList<MediaSession.QueueItem>();

        // MediaSessionを生成
        mMediaSession = new MediaSession(this, "MyMediaSession");
        setSessionToken(mMediaSession.getSessionToken());
        
        // コールバックを設定
        mMediaSession.setCallback(new MyMediaSessionCallback());

        // 再生キューの位置を初期化
        mCurrentQueueIndex = 0;
    }

■onGetRoot()とonLoadChildren()の実装

onGetRoot()とonLoadChildren()の実装をします。onGetRoot()について今回はROOTを固定で返していますが、引数のclientPackageNameおよびclientUidでクライアントを判断してnullを返すことで、特定のクライアント以外からの参照を許可しないようにすることもできます。onLoadChildren()はメディア項目に関する情報を返すメソッドで、引数のresultにデータを設定します。

MediaItemの種類としてFLAG_PLAYABLEとFLAG_BROWSABLEがあります。リスト選択によって再生を開始したい場合はFLAG_PLAYABLEを、リストの表示のみを行いたい場合はFLAG_BROWSABLEを設定してください。FLAG_PLAYABLE設定時にリスト選択をすると後述のMediaSessionクラスのコールバックメソッドonPlayFromMediaId()がコールされますので、そのタイミングで再生処理を行います。
    @Override
    public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
        Bundle rootHints) {
        return new BrowserRoot(MEDIA_ID_ROOT, null);
    }

    @Override
    public void onLoadChildren(String parentId, Result <List<MediaItem>> result) {
        List<MediaBrowser.MediaItem> mediaItems = new ArrayList<MediaBrowser.MediaItem>();

        // 再生データ情報の設定
        MediaDescription.Builder mdb1 = new MediaDescription.Builder();
        
        // 再生データ1
        final Bundle mediaBundle1 = new Bundle();
        mediaBundle1.putString("Path", "mnt/sdcard/M01.mp3"); // データパス
        mediaBundle1.putInt("Index", 0);                      // 曲のインデックス
        mdb1.setMediaId("MediaID01");                         // メディアID
        mdb1.setTitle("Title01");                             // タイトル
        mdb1.setSubtitle("SubTitle01");                       // サブタイトル
        mdb1.setExtras(mediaBundle1);

        // 再生データ2
        MediaDescription.Builder mdb2 = new MediaDescription.Builder();
        final Bundle mediaBundle2 = new Bundle();
        mediaBundle2.putString("Path", "mnt/sdcard/M02.mp3"); // データパス
        mediaBundle2.putInt("Index", 1);                      // 曲のインデックス
        mdb2.setMediaId("MediaID02");                         // メディアID
        mdb2.setTitle("Title02");                             // タイトル
        mdb2.setSubtitle("SubTitle02");                       // サブタイトル
        mdb2.setExtras(mediaBundle2);
        
        mediaItems.add(new MediaBrowser.MediaItem(mdb1.build(),
                MediaBrowser.MediaItem.FLAG_PLAYABLE));        
        mediaItems.add(new MediaBrowser.MediaItem(mdb2.build(),
                MediaBrowser.MediaItem.FLAG_PLAYABLE));

        // sendResult()をコールする前にdetach()のコールが必要
        result.detach();
        result.sendResult(mediaItems);

        // 再生キューをセッションに設定
        mPlayingQueue.add(new MediaSession.QueueItem(mdb1.build(), 0));
        mPlayingQueue.add(new MediaSession.QueueItem(mdb2.build(), 1));
        mMediaSession.setQueue(mPlayingQueue);
    }

■再生処理を実装

MediaPlayerクラスによる再生処理を行うメソッドを実装します。基本的なMediaPlayerの実装手順で問題ありません。再生データのパスはMediaSession.QueueItemで管理させています。
    private void playMusic() {
        // プレイヤー生成と準備(生成済みの場合はリセット)
        if (mMediaPlayer == null) {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnErrorListener(this);
        } else {
            mMediaPlayer.reset();
        }

        // 現在のキュー位置を元に再生データのパスを取得する
        MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentQueueIndex);
        String path = queueItem.getDescription().getExtras().getString("Path");

        // MediaPlayerのデータ設定と準備(非同期)
        try {
            mMediaPlayer.setDataSource(path);
            mMediaPlayer.prepareAsync();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        // 再生準備完了通知を受け、再生を行う
        if(mMediaPlayer != null) {
            mMediaPlayer.start();
        }
    }

■MediaSessionクラスのコールバック

MediaSessionのコールバックを下記のように実装します。リストを選択するとonPlayFromMediaId()、曲送りを選択するとonSkipToNext()、曲戻しを選択するとonSkipToPrevious()がコールされます。



    private final class MyMediaSessionCallback extends MediaSession.Callback {
        @Override
        public void onPlay() {
            playMusic();
        }

        // リスト選択時にコールされる
        @Override
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            // MediaIDを元に曲のインデックス番号を検索し、設定する。
            for(int i = 0; i < mPlayingQueue.size(); i++) {
                MediaSession.QueueItem queueItem = mPlayingQueue.get(i);
                MediaDescription md = queueItem.getDescription();
                if(mediaId.equals(md.getMediaId())) {
                    mCurrentQueueIndex = md.getExtras().getInt("Index");
                }
            }
            playMusic();
        }

        @Override
        public void onSkipToNext() {
            // 再生キューの位置を次へ
            mCurrentQueueIndex++;
            if (mCurrentQueueIndex >= mPlayingQueue.size()) {
                mCurrentQueueIndex = 0;
            }
            playMusic();
        }

        @Override
        public void onSkipToPrevious() {
            // 再生キューの位置を前へ
            mCurrentQueueIndex--;
            if (mCurrentQueueIndex < 0) {
                mCurrentQueueIndex = 0;
            }
            playMusic();
        }

■onDestroy()の実装

終了処理を実装します。
    @Override
    public void onDestroy() {
        super.onDestroy();
        // MediaSessionの終了処理
        mMediaSession.setCallback(null);
        mMediaSession.release();
        
        // MediaPlayerの終了処理
        if(mMediaPlayer != null) {
            mMediaPlayer.setOnPreparedListener(null);
            mMediaPlayer.setOnCompletionListener(null);
            mMediaPlayer.setOnErrorListener(null);
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }
今回は割愛しましたが、MediaSessionクラスを正確に動作させるためには、setPlaybackState()にて再生状態や停止状態などを操作に合わせて設定する必要があります。

MediaSessioinおよびその関連クラスについては下記を参照してください。
http://developer.android.com/reference/android/media/session/package-summary.html
Related Posts Plugin for WordPress, Blogger...