メディアボタンへの応答

メディアボタンとは、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 8.0(API レベル 26)より前の場合は、アクティブなメディア セッションへのイベントの送信が試みられます。アクティブなメディア セッションが複数ある場合は、停止中のもの以外の、再生準備中(バッファリング / 接続)、再生中、一時停止中のメディア セッションが選択されます(詳細については、アクティブなメディア セッションでのメディアボタンの処理をご覧ください)。アクティブなセッションがない場合は、最後にアクティブだったセッションへのイベントの送信が試みられます(詳細については、メディアボタンを使用した非アクティブなメディア セッションの再起動をご覧ください)。このロジックを次の図に示します。

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

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

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

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() が呼び出されます。

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

まとめ

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

さらに、サポートする予定の Android バージョンに応じて、以下の要件も満たす必要があります。

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

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

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

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