Como criar um cliente de navegador de mídia

Para concluir o design de cliente/servidor, você precisa criar um componente de atividade que contenha o código da IU, um MediaController associado e um MediaBrowser.

O MediaBrowser executa duas funções importantes: ele se conecta a um MediaBrowserService e, ao se conectar, cria o MediaController para a IU.

Observação : a implementação recomendada do MediaBrowser é MediaBrowserCompat, que é definida na Biblioteca de Suporte Media-Compat. Nesta página, o termo "MediaBrowser" se refere a uma instância de MediaBrowserCompat.

Conectar-se ao MediaBrowserService

Quando a atividade de cliente é criada, ela se conecta ao MediaBrowserService. Eles se completam e funcionam em harmonia. Modifique os callbacks do ciclo de vida da atividade da seguinte maneira:

  • onCreate() constrói um MediaBrowserCompat. Transmita o nome do MediaBrowserService e do MediaBrowserCompat.ConnectionCallback que você definiu.
  • onStart() se conecta ao MediaBrowserService. É aqui que entra a mágica do MediaBrowserCompat.ConnectionCallback. Se a conexão se completar, o callback de onConnect() criará o controlador de mídia, vinculará à sessão de mídia, vinculará os controles de IU ao MediaController e registrará o controlador para receber callbacks da sessão de mídia.
  • onResume() define o stream de áudio para que o app responda ao controle de volume no dispositivo.
  • onStop() desconecta o MediaBrowser e cancela o registro do MediaController.Callback quando a atividade for interrompida.

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

  }
}

Personalizar MediaBrowserCompat.ConnectionCallback

Quando sua atividade construir MediaBrowserCompat, você precisará criar uma instância de ConnectionCallback. Modifique o método onConnected() para recuperar o token de sessão de mídia do MediaBrowserService e use o token para criar um MediaControllerCompat.

Use o método de conveniência MediaControllerCompat.setMediaController() para salvar um link para o controlador. Isso permite o processamento de botões de mídia. Ela também permite que você chame MediaControllerCompat.getMediaController() para extrair o controlador ao criar os controles de transporte.

O exemplo de código a seguir mostra como modificar o 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
    }
  };

Conectar a IU ao controlador de mídia

O código de exemplo ConnectionCallback acima, inclui uma chamada para buildTransportControls() para detalhar a IU. Você precisará definir onClickListeners para os elementos de IU que controlam o player. Escolha o método MediaControllerCompat.TransportControls adequado para cada um.

O código será parecido com este, com um onClickListener para cada botão:

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

Os métodos TransportControls enviam callbacks para a sessão de mídia do serviço. Verifique se você definiu um método MediaSessionCompat.Callback correspondente para cada controle.

Fique sincronizado com a sessão de mídia

A IU precisa exibir o estado atual da sessão de mídia, conforme descrito pelo PlaybackState e pelos metadados. Ao criar os controles de transporte, você pode capturar o estado atual da sessão, exibi-lo na IU e ativar e desativar os controles de transporte com base no estado e nas ações disponíveis.

Para receber callbacks da sessão de mídia sempre que o estado ou os metadados mudarem, defina um MediaControllerCompat.Callback com estes dois 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) {}
  };

Registre o callback ao criar os controles de transporte (consulte o método buildTransportControls()) e cancele o registro quando a atividade for interrompida (no método de ciclo de vida do onStop()).

Desconectar quando a sessão de mídia for destruída

Se a sessão de mídia se tornar inválida, o callback onSessionDestroyed() será emitido. Quando isso acontece, a sessão não pode se tornar funcional novamente durante o ciclo de vida do MediaBrowserService. Embora as funções relacionadas a MediaBrowser possam continuar funcionando, um usuário não pode ver ou controlar a reprodução de uma sessão de mídia destruída, o que provavelmente diminuirá o valor do seu aplicativo.

Portanto, quando a sessão for destruída, você precisa se desconectar do MediaBrowserService chamando disconnect(). Isso garante que o serviço de navegador não tenha clientes vinculados e possa ser destruído pelo SO. Se você precisar se reconectar ao MediaBrowserService mais tarde (por exemplo, se o aplicativo quiser manter uma conexão permanente com o app de música), crie uma nova instância de MediaBrowser em vez de reutilizar a antiga.

O snippet de código abaixo demonstra uma implementação de callback que se desconecta do serviço de navegador quando a sessão de mídia é destruída:

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