Cómo crear un cliente de exploración multimedia

Para completar el diseño de cliente-servidor, debes compilar un componente de actividad que contenga tu código de IU, un MediaController asociado y un MediaBrowser.

El MediaBrowser realiza dos funciones importantes: se conecta a un MediaBrowserService y, al conectarse, crea el MediaController para la IU.

Nota: La implementación recomendada de MediaBrowser es MediaBrowserCompat, que se define en la biblioteca de compatibilidad media-compat. En esta página, el término "MediaBrowser" hace referencia a una instancia de MediaBrowserCompat.

Conéctate al MediaBrowserService

Cuando se crea tu actividad de cliente, se conecta al MediaBrowserService. Esto implica un protocolo de enlace. Modifica las devoluciones de llamada del ciclo de vida de la actividad de la siguiente manera:

  • onCreate() construye un MediaBrowserCompat. Pasa el nombre de tu MediaBrowserService y el MediaBrowserCompat.ConnectionCallback que definiste.
  • onStart() se conecta al MediaBrowserService. Aquí es donde entra en juego la magia de MediaBrowserCompat.ConnectionCallback. Si la conexión es exitosa, la devolución de llamada onConnect() crea el controlador multimedia, lo vincula a la sesión multimedia, vincula los controles de tu IU al MediaController y registra el controlador para recibir devoluciones de llamada de la sesión multimedia.
  • onResume() configura la transmisión de audio para que tu app responda al control de volumen del dispositivo.
  • onStop() desconecta el MediaBrowser y anula el registro de MediaController.Callback cuando se detiene tu actividad.

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();

      }
    }
    

Personaliza MediaBrowserCompat.ConnectionCallback

Cuando tu actividad construye MediaBrowserCompat, debes crear una instancia de ConnectionCallback. Modifica su método onConnected() a fin de recuperar el token de la sesión multimedia del MediaBrowserService y usa el token para crear un MediaControllerCompat.

Usa el método de conveniencia MediaControllerCompat.setMediaController() para guardar un vínculo al controlador. Esto permite administrar los botones de medios. También te permite llamar a MediaControllerCompat.getMediaController() para recuperar el controlador cuando compilas los controles de transporte.

En el siguiente ejemplo de código, se muestra cómo modificar el método 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
        }
      };
    

Conecta tu IU al controlador multimedia

En el código de muestra de ConnectionCallback anterior, incluye una llamada a buildTransportControls() para completar tu IU. Deberás configurar onClickListeners para los elementos de la IU que controlan el reproductor. Elige el método MediaControllerCompat.TransportControls apropiado para cada uno.

Tu código tendrá un aspecto similar al siguiente, con un onClickListener para cada botón:

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);
    }
    }
    

Los métodos TransportControls envían devoluciones de llamada a la sesión multimedia de tu servicio. Asegúrate de haber definido un método MediaSessionCompat.Callback correspondiente para cada control.

Mantén la sincronización con la sesión multimedia

La IU debe mostrar el estado actual de la sesión multimedia, tal como lo describen su PlaybackState y Metadata. Cuando creas los controles de transporte, puedes tomar el estado actual de la sesión, mostrarlo en la IU y habilitar o inhabilitar los controles de transporte en función del estado y sus acciones disponibles.

Para recibir devoluciones de llamada de la sesión multimedia cada vez que cambia el estado o los metadatos, define un MediaControllerCompat.Callback con estos dos métodos:

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) {}
      };
    

Registra la devolución de llamada cuando compiles los controles de transporte (consulta el método buildTransportControls()) y anula el registro cuando se detenga la actividad (en el método del ciclo de vida onStop() de la actividad).