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, definido na Biblioteca de Suporte Media-Compat. Nesta página, o termo "MediaBrowser" se refere a uma instância do 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.
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()
   
}
}
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. Ele também permite chamar 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().

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
   
}
}
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 a opção apropriada MediaControllerCompat.TransportControls para cada um.

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

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)
}
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. Certifique-se de que você tenha definido uma MediaSessionCompat.Callback 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:

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

   
override fun onMetadataChanged(metadata: MediaMetadataCompat?) {}

   
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {}
}
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 onSessionDestroyed() é emitido um callback. Quando isso acontece, a sessão não pode se tornar funcional novamente durante o ciclo de vida da MediaBrowserService. Embora as funções relacionadas a MediaBrowser podem continuar funcionando, um usuário não pode ver nem 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ê deve se desconectar do MediaBrowserService ao chamar disconnect() Isso garante que o serviço do navegador não tenha clientes vinculados e podem ser destruídos pela SO. Se você precisar se reconectar ao MediaBrowserService mais tarde (por exemplo, se seu 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 a seguir demonstra uma implementação de callback que desconecta do serviço do navegador quando a sessão de mídia é destruída:

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