回應媒體按鈕事件

媒體按鈕是 Android 裝置和其他週邊裝置提供的硬體按鈕,例如藍牙耳機上的暫停/播放按鈕。當使用者按下媒體按鈕時,Android 會產生 KeyEvent,其中包含可識別按鈕的按鍵程式碼。媒體按鈕 KeyEvents 的鍵碼是開頭為 KEYCODE_MEDIA 的常數 (例如 KEYCODE_MEDIA_PLAY)。

應用程式應能以下列優先順序處理三種情況:媒體按鈕事件:

  • 當畫面上顯示應用程式的 UI 活動時
  • 隱藏 UI 活動,且應用程式的媒體工作階段處於啟用狀態時
  • 隱藏 UI 活動,且應用程式的媒體工作階段閒置且需要重新啟動時

處理前景活動中的媒體按鈕

前景活動會在 onKeyDown() 方法中收到媒體按鈕鍵事件。視執行中的 Android 版本而定,系統可透過兩種方式將事件轉送至媒體控制器:

  • 如果使用的是 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 版本而定,您可以透過兩種方式搜尋媒體工作階段:

  • 如果執行的是 Android 8.0 (API 級別 26) 以上版本,系統會嘗試使用 MediaSession 在本機播放音訊,來找出最後一個應用程式。如果工作階段仍在進行中,Android 會直接將事件傳送至該工作階段。否則,如果工作階段不在使用中,且具有媒體按鈕接收器,Android 會傳送事件給接收器,接收端不僅會重新啟動工作階段,也能接收事件。(詳情請參閱使用媒體按鈕重新啟動已停用的媒體工作階段)。 如果工作階段沒有媒體按鈕接收器,系統會捨棄媒體按鈕事件,也不會有任何反應。相關邏輯如以下圖表所示:

  • 在 Android 8.0 (API 級別 26) 以下版本中,系統會嘗試將事件傳送至進行中的媒體工作階段。如有多個執行中的媒體工作階段,Android 會嘗試選擇準備播放 (緩衝/連線)、播放或已暫停的媒體工作階段,而不是已停止的媒體工作階段。(詳情請參閱「處理使用中的媒體工作階段中的媒體按鈕」)。如果沒有使用中的工作階段,Android 會嘗試將事件傳送至最近使用的工作階段。(詳情請參閱使用媒體按鈕重新啟動已停用的媒體工作階段)。 相關邏輯如下圖所示:

在播放中的媒體工作階段中處理媒體按鈕

在 Android 5.0 (API 級別 21) 以上版本中,Android 會透過呼叫 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 以下版本的系統中使用媒體按鈕,您必須在資訊清單中加入 MediaButtonReceiver,並使用 MEDIA_BUTTON 意圖篩選器。

<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) 以上版本中,設定空值媒體按鈕接收器來停用這項行為:

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 新增至應用程式的資訊清單,以靜態方式建立