Cómo crear actividad de clientes para el navegador 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 cambie su estado o sus 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).

Cómo desconectarte cuando finalice la sesión multimedia

Si la sesión multimedia deja de ser válida, se emite la devolución de llamada onSessionDestroyed(). Cuando eso sucede, la sesión no puede volver a funcionar durante la vida útil de MediaBrowserService. Aunque es posible que las funciones relacionadas con MediaBrowser sigan funcionando, un usuario no puede ver ni controlar la reproducción desde una sesión multimedia destruida, lo que probablemente disminuirá el valor de tu aplicación.

Por lo tanto, cuando se destruye la sesión, debes desconectarte de MediaBrowserService llamando a disconnect(). Esto garantiza que el servicio de navegador no tenga clientes vinculados y que el SO pueda destruirlo. Si necesitas volver a conectarte a MediaBrowserService más adelante (por ejemplo, si tu aplicación desea mantener una conexión persistente a la app de música), crea una instancia nueva de MediaBrowser en lugar de volver a usar la anterior.

En el siguiente fragmento de código, se muestra una implementación de devolución de llamada que se desconecta del servicio del navegador cuando se destruye la sesión multimedia:

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