构建媒体浏览器客户端

要完成客户端/服务器设计,您必须构建一个包含 UI 代码的活动组件、一个关联的 MediaController 和一个 MediaBrowser。

MediaBrowser 执行两个重要功能:它连接到 MediaBrowserService,并在连接后为您的 UI 创建 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 元素设置 onClickListeners。为每个元素选择合适的 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 应显示媒体会话的当前状态,如其 PlaybackState 和 Metadata 所述。创建传输控件时,您可以获取会话的当前状态,将其显示在 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 断开连接。这确保浏览器服务没有绑定客户端,并且 可以被操作系统销毁。如果您以后需要重新连接到 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
    }
  };