建立媒體瀏覽器用戶端

如要完成用戶端/伺服器設計,您必須建立包含 UI 程式碼、相關聯的 MediaController 和 MediaBrowser 的活動元件。

MediaBrowser 會執行兩項重要功能:它會連線至 MediaBrowserService,連線時即會為您的使用者介面建立 MediaController。

注意事項: 建議採用的 MediaBrowser 實作方式為 MediaBrowserCompat,詳情請參閱 Media-Compat 支援資料庫。在這個頁面中,「MediaBrowser」是指 MediaBrowserCompat 的執行個體,

連線至 MediaBrowserService

用戶端活動建立完成後,就會連線至 MediaBrowserService。握手與舞蹈息息相關。 請修改活動的生命週期回呼,如下所示:

  • onCreate() 會建構 MediaBrowserCompat。傳入 MediaBrowserService 和您定義的 MediaBrowserCompat.ConnectionCallback 名稱。
  • onStart() 會連線至 MediaBrowserService。這就派上用場了 MediaBrowserCompat.ConnectionCallback 的強大魔力。如果連線成功,onConnect() 回呼會建立媒體控制器、將其連結至媒體工作階段、將 UI 控制項連結至 MediaController,並註冊控制器以接收來自媒體工作階段的回呼。
  • onResume() 會設定音訊串流,讓應用程式回應裝置上的音量控制項。
  • onStop() 會中斷 MediaBrowser 的連線,並在活動停止時取消註冊 MediaController.Callback。

Kotlin

class MediaPlayerActivity : AppCompatActivity() {

    private lateinit var mediaBrowser: MediaBrowserCompat

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        // Create MediaBrowserServiceCompat
        mediaBrowser = MediaBrowserCompat(
                this,
                ComponentName(this, MediaPlaybackService::class.java),
                connectionCallbacks,
                null // optional Bundle
        )
    }

    public override fun onStart() {
        super.onStart()
        mediaBrowser.connect()
    }

    public override fun onResume() {
        super.onResume()
        volumeControlStream = AudioManager.STREAM_MUSIC
    }

    public override fun onStop() {
        super.onStop()
        // (see "stay in sync with the MediaSession")
        MediaControllerCompat.getMediaController(this)?.unregisterCallback(controllerCallback)
        mediaBrowser.disconnect()
    }
}

Java

public class MediaPlayerActivity extends AppCompatActivity {
  private MediaBrowserCompat mediaBrowser;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ...
    // Create MediaBrowserServiceCompat
    mediaBrowser = new MediaBrowserCompat(this,
      new ComponentName(this, MediaPlaybackService.class),
        connectionCallbacks,
        null); // optional Bundle
  }

  @Override
  public void onStart() {
    super.onStart();
    mediaBrowser.connect();
  }

  @Override
  public void onResume() {
    super.onResume();
    setVolumeControlStream(AudioManager.STREAM_MUSIC);
  }

  @Override
  public void onStop() {
    super.onStop();
    // (see "stay in sync with the MediaSession")
    if (MediaControllerCompat.getMediaController(MediaPlayerActivity.this) != null) {
      MediaControllerCompat.getMediaController(MediaPlayerActivity.this).unregisterCallback(controllerCallback);
    }
    mediaBrowser.disconnect();

  }
}

自訂 MediaBrowserCompat.ConnectionCallback

當活動建構 MediaBrowserCompat 時,您必須建立 ConnectionCallback 的執行個體。修改其 onConnected() 方法,以便從 MediaBrowserService 擷取媒體工作階段符記,並使用符記建立 MediaControllerCompat。

使用便利的方法 MediaControllerCompat.setMediaController() 儲存控制器的連結。以便處理媒體按鈕。也可讓您在建構傳輸控制項時呼叫 MediaControllerCompat.getMediaController() 來擷取控制器。

以下程式碼範例說明如何修改 onConnected() 方法。

Kotlin

private val connectionCallbacks = object : MediaBrowserCompat.ConnectionCallback() {
    override fun onConnected() {

        // Get the token for the MediaSession
        mediaBrowser.sessionToken.also { token ->

            // Create a MediaControllerCompat
            val mediaController = MediaControllerCompat(
                    this@MediaPlayerActivity, // Context
                    token
            )

            // Save the controller
            MediaControllerCompat.setMediaController(this@MediaPlayerActivity, mediaController)
        }

        // Finish building the UI
        buildTransportControls()
    }

    override fun onConnectionSuspended() {
        // The Service has crashed. Disable transport controls until it automatically reconnects
    }

    override fun onConnectionFailed() {
        // The Service has refused our connection
    }
}

Java

private final MediaBrowserCompat.ConnectionCallback connectionCallbacks =
  new MediaBrowserCompat.ConnectionCallback() {
    @Override
    public void onConnected() {

      // Get the token for the MediaSession
      MediaSessionCompat.Token token = mediaBrowser.getSessionToken();

      // Create a MediaControllerCompat
      MediaControllerCompat mediaController =
        new MediaControllerCompat(MediaPlayerActivity.this, // Context
        token);

      // Save the controller
      MediaControllerCompat.setMediaController(MediaPlayerActivity.this, mediaController);

      // Finish building the UI
      buildTransportControls();
    }

    @Override
    public void onConnectionSuspended() {
      // The Service has crashed. Disable transport controls until it automatically reconnects
    }

    @Override
    public void onConnectionFailed() {
      // The Service has refused our connection
    }
  };

將 UI 連結至媒體控制器

在上方的 ConnectionCallback 程式碼範例中,加入對 buildTransportControls() 的呼叫來縮小 UI。您必須為控製播放器的 UI 元素設定 onClickListener。請為每個選項選擇適當的 MediaControllerCompat.TransportControls 方法。

程式碼看起來會像這樣,每個按鈕都有一個 onClickListener:

Kotlin

fun buildTransportControls() {
    val mediaController = MediaControllerCompat.getMediaController(this@MediaPlayerActivity)
    // Grab the view for the play/pause button
    playPause = findViewById<ImageView>(R.id.play_pause).apply {
        setOnClickListener {
            // Since this is a play/pause button, you'll need to test the current state
            // and choose the action accordingly

            val pbState = mediaController.playbackState.state
            if (pbState == PlaybackStateCompat.STATE_PLAYING) {
                mediaController.transportControls.pause()
            } else {
                mediaController.transportControls.play()
            }
        }
    }

    // Display the initial state
    val metadata = mediaController.metadata
    val pbState = mediaController.playbackState

    // Register a Callback to stay in sync
    mediaController.registerCallback(controllerCallback)
}

Java

void buildTransportControls()
{
  // Grab the view for the play/pause button
  playPause = (ImageView) findViewById(R.id.play_pause);

  // Attach a listener to the button
  playPause.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      // Since this is a play/pause button, you'll need to test the current state
      // and choose the action accordingly

      int pbState = MediaControllerCompat.getMediaController(MediaPlayerActivity.this).getPlaybackState().getState();
      if (pbState == PlaybackStateCompat.STATE_PLAYING) {
        MediaControllerCompat.getMediaController(MediaPlayerActivity.this).getTransportControls().pause();
      } else {
        MediaControllerCompat.getMediaController(MediaPlayerActivity.this).getTransportControls().play();
      }
  });

  MediaControllerCompat mediaController = MediaControllerCompat.getMediaController(MediaPlayerActivity.this);

  // Display the initial state
  MediaMetadataCompat metadata = mediaController.getMetadata();
  PlaybackStateCompat pbState = mediaController.getPlaybackState();

  // Register a Callback to stay in sync
  mediaController.registerCallback(controllerCallback);
}
}

TransportControls 方法會將回呼傳送至服務的媒體工作階段。請確認您已為每個控制項定義對應的 MediaSessionCompat.Callback 方法。

與媒體工作階段保持同步

使用者介面應顯示媒體工作階段的目前狀態,如播放狀態和中繼資料所述。建立傳輸控制項時,您可以擷取工作階段的目前狀態、在 UI 中顯示,並根據狀態和可用動作啟用及停用傳輸控制項。

如要在每次狀態或中繼資料變更時接收來自媒體工作階段的回呼,請使用以下兩種方法定義 MediaControllerCompat.Callback

Kotlin

private var controllerCallback = object : MediaControllerCompat.Callback() {

    override fun onMetadataChanged(metadata: MediaMetadataCompat?) {}

    override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {}
}

Java

MediaControllerCompat.Callback controllerCallback =
  new MediaControllerCompat.Callback() {
    @Override
    public void onMetadataChanged(MediaMetadataCompat metadata) {}

    @Override
    public void onPlaybackStateChanged(PlaybackStateCompat state) {}
  };

請在建構傳輸控制項時註冊回呼 (請參閱 buildTransportControls() 方法),並在活動停止時取消註冊 (在活動的 onStop() 生命週期方法中)。

在媒體工作階段刪除時中斷連線

如果媒體工作階段失效,系統會發出 onSessionDestroyed() 回呼。發生這種情況時,工作階段無法在 MediaBrowserService 的生命週期內再次運作。雖然與 MediaBrowser 相關的函式可能可以繼續運作,但使用者無法從遭刪除的媒體工作階段查看或控製播放功能,因而可能降低應用程式的價值。

因此,刪除工作階段時,您必須呼叫 disconnect() 來中斷與 MediaBrowserService 的連線。這樣做可確保瀏覽器服務沒有繫結的用戶端,且可由 OS 刪除。如果您需要稍後重新連線至 MediaBrowserService (例如,應用程式要維持與媒體應用程式的永久連線),請建立 MediaBrowser執行個體,而不是重複使用舊的執行個體。

下列程式碼片段示範在刪除媒體工作階段時,與瀏覽器服務中斷連線的回呼實作方式:

Kotlin

private var controllerCallback = object : MediaControllerCompat.Callback() {
    override fun onSessionDestroyed() {
      mediaBrowser.disconnect()
      // maybe schedule a reconnection using a new MediaBrowser instance
    }
}

Java

MediaControllerCompat.Callback controllerCallback =
  new MediaControllerCompat.Callback() {
    @Override
    public void onSessionDestroyed() {
      mediaBrowser.disconnect();
      // maybe schedule a reconnection using a new MediaBrowser instance
    }
  };