メディアボタンへの応答

メディアボタンとは、Android デバイスや周辺デバイスにあるハードウェア ボタンのことです。たとえば、Bluetooth ヘッドセットの一時停止 / 再生ボタンなどです。ユーザーがメディアボタンを押すと、そのボタン特有の「キーコード」を含む KeyEvent が Android により生成されます。メディアボタン KeyEvent のキーコードは、KEYCODE_MEDIA で始まる名前(たとえば、KEYCODE_MEDIA_PLAY)の定数です。

アプリは、次の 3 つの場合にメディアボタン イベントを、次の優先順位で処理できる必要があります。

  • アプリの UI アクティビティが表示されている場合
  • UI アクティビティが非表示で、アプリのメディア セッションがアクティブの場合
  • UI アクティビティが非表示で、アプリのメディア セッションが非アクティブであり再起動を必要とする場合

フォアグラウンド アクティビティでのメディアボタンの処理

フォアグラウンド アクティビティは、onKeyDown() メソッドでメディアボタン キーイベントを受け取ります。実行中の Android のバージョンに応じて、システムは次の 2 つの方法でイベントをメディア コントローラに転送します。

  • Android 5.0(API レベル 21)以降を実行している場合は、FLAG_HANDLES_MEDIA_BUTTONS MediaBrowserCompat.ConnectionCallback.onConnected を呼び出します。これにより、メディア コントローラの dispatchMediaButtonEvent() が自動的に呼び出され、キーコードがメディア セッション コールバックに変換されます。
  • Android 5.0(API レベル 21)より前のバージョンでは、イベントを自分で処理するように onKeyDown() を変更する必要があります。(詳しくは、アクティブなメディア セッションでのメディアボタンの処理をご覧ください)。次のコード スニペットは、キーコードをインターセプトして dispatchMediaButtonEvent() を呼び出す方法を示しています。イベントが処理されたことを示すために、true を返してください。

    Kotlin

        fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                return super.onKeyDown(keyCode, event)
            }
            when (keyCode) {
                KeyEvent.KEYCODE_MEDIA_PLAY -> {
                    yourMediaController.dispatchMediaButtonEvent(event)
                    return true
                }
            }
            return super.onKeyDown(keyCode, event)
        }
        

    Java

        @Override
        boolean onKeyDown(int keyCode, KeyEvent event) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  return super.onKeyDown(keyCode, event);
                }
                switch (keyCode) {
                  case KeyEvent.KEYCODE_MEDIA_PLAY:
                          yourMediaController.dispatchMediaButtonEvent(event);
                          return true;
                }
                return super.onKeyDown(keyCode, event);
        }
        

メディア セッションの検索

フォアグラウンド アクティビティがイベントを処理しない場合、Android はそれを処理できるメディア セッションの検出を試みます。繰り返しになりますが、メディア セッションを検索するには、Android のバージョンに応じて 2 つの方法があります。

  • Android 8.0(API レベル 26)以降を実行している場合、システムは、ローカルで音声を再生した MediaSession を持つ最後のアプリを探します。セッションがまだアクティブな場合、Android はセッションに直接イベントを送信します。それ以外の場合は、セッションがアクティブでなく、メディアボタン レシーバーがある場合、Android はイベントをレシーバに送信します。レシーバはセッションが再起動され、イベントを受信できます。(詳しくは、メディアボタンを使用して非アクティブなメディア セッションを再開するをご覧ください)。セッションにメディアボタン レシーバーがない場合、システムはメディアボタン イベントを破棄し、何も起こりません。ロジックを次の図に示します。

  • Android 8.0(API レベル 26)より前では、システムはアクティブなメディア セッションにイベントを送信しようとします。アクティブなメディア セッションが複数ある場合、Android は、停止しているメディア セッションではなく、再生(バッファリング/接続)、再生中、または一時停止の準備ができているメディア セッションを選択しようとします。(詳しくは、アクティブなメディア セッションにおけるメディアボタンの処理をご覧ください)。アクティブなセッションがない場合、Android は最後にアクティブなセッションにイベントを送信しようとします。(詳しくは、メディアボタンを使用して非アクティブなメディア セッションを再開するをご覧ください)。このロジックを次の図に示します。

アクティブなメディア セッションでのメディアボタンの処理

Android 5.0(API レベル 21)以降では、onMediaButtonEvent() を呼び出すことで、アクティブなメディア セッションにメディアボタン イベントを自動的にディスパッチします。デフォルトでは、このコールバックによって、KeyEvent はキーコードに対応する適切なメディア セッション コールバック メソッドに変換されます。

Android 5.0(API レベル 21)より前では、Android は ACTION_MEDIA_BUTTON アクションを持つインテントをブロードキャストすることにより、メディアボタン イベントを処理します。これらのインテントをインターセプトするには、アプリで BroadcastReceiver を登録する必要があります。MediaButtonReceiver クラスは、この目的のために特別に設計されています。これは Android media-compat ライブラリの便利なクラスで、ACTION_MEDIA_BUTTON を処理して、受信したインテントを適切な MediaSessionCompat.Callback メソッド呼び出しに変換します。

MediaButtonReceiver は存続期間の短い BroadcastReceiver で、受信インテントを、メディア セッションを管理するサービスに転送します。Android 5.0 より前のシステムでメディアボタンを使用する場合は、MEDIA_BUTTON インテント フィルタを指定して、マニフェストに MediaButtonReceiver を含める必要があります。

<receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
   <intent-filter>
     <action android:name="android.intent.action.MEDIA_BUTTON" />
   </intent-filter>
 </receiver>

BroadcastReceiver により、インテントはサービスに転送されます。インテントを解析し、メディア セッションへのコールバックを生成するには、サービスの onStartCommand()MediaButtonReceiver.handleIntent() メソッドを含めます。これにより、キーコードが適切なセッション コールバック メソッドに変換されます。

Kotlin

private val mediaSessionCompat: MediaSessionCompat = ...

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    MediaButtonReceiver.handleIntent(mediaSessionCompat, intent)
    return super.onStartCommand(intent, flags, startId)
}

Java

private MediaSessionCompat mediaSessionCompat = ...;

 public int onStartCommand(Intent intent, int flags, int startId) {
   MediaButtonReceiver.handleIntent(mediaSessionCompat, intent);
   return super.onStartCommand(intent, flags, startId);
 }

メディアボタンを使用した非アクティブなメディア セッションの再起動

Android により、最後にアクティブだったメディア セッションの特定ができた場合は、ACTION_MEDIA_BUTTON インテントがマニフェストに登録されたコンポーネント(サービスや BroadcastReceiver など)に送信され、そのセッションの再起動が試みられます。

これにより、UI が表示されていない状態(ほとんどの音声アプリが該当するケース)でアプリの再生を再開できます。

MediaSessionCompat を使用すると、この動作は自動的に有効になります。Android フレームワークの MediaSession またはサポート ライブラリ 24.0.0 ~ 25.1.1 を使用する場合は、setMediaButtonReceiver を呼び出して、メディアボタンが非アクティブなメディア セッションを再開できるようにする必要があります。

Android 5.0(API レベル 21)以降では、null のメディアボタン レシーバを設定することで、この動作を無効にできます。

Kotlin

// Create a MediaSessionCompat
mediaSession = MediaSessionCompat(context, LOG_TAG)
mediaSession.setMediaButtonReceiver(null)

Java

// Create a MediaSessionCompat
mediaSession = new MediaSessionCompat(context, LOG_TAG);
mediaSession.setMediaButtonReceiver(null);

メディアボタン ハンドラのカスタマイズ

onMediaButtonEvent() のデフォルトの動作では、キーコードが抽出され、メディア セッションの現在の状態とサポートされているアクションのリストから、呼び出されるメソッドが決定されます。たとえば、KEYCODE_MEDIA_PLAY なら onPlay() が呼び出されます。

すべてのアプリで一貫したメディアボタンのエクスペリエンスを提供するには、デフォルトの動作を使用し、特定の目的のみに逸脱しないようにする必要があります。メディアボタンにカスタム処理が必要な場合は、コールバックの onMediaButtonEvent() メソッドをオーバーライドし、intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) を使用して KeyEvent を抽出し、自分でイベントを処理して、true を返します。

まとめ

すべてのバージョンの Android でメディアボタン イベントを適切に処理するには、メディア セッションの作成時に FLAG_HANDLES_MEDIA_BUTTONS を指定する必要があります。

また、サポートする予定の Android のバージョンによっては、次の要件も満たす必要があります。

Android 5.0 以降で実行する場合:

  • メディア コントローラ onConnected() コールバックから MediaControllerCompat.setMediaController() を呼び出す
  • メディアボタンで非アクティブなセッションを再開できるようにするには、setMediaButtonReceiver() を呼び出して PendingIntent を渡すことで、MediaButtonReceiver を動的に作成します。

Android 5.0 より前のシステムで実行する場合:

  • アクティビティの onKeyDown() をオーバーライドしてメディアボタンを処理する
  • MediaButtonReceiver をアプリのマニフェストに追加して静的に作成する