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