Reproduzir mídia em segundo plano

É possível reproduzir mídia em segundo plano mesmo quando o app não está na tela, por exemplo, enquanto o usuário interage com outros apps.

Para fazer isso, incorpore o MediaPlayer em um serviço MediaBrowserServiceCompat e faça com que ele interaja com um MediaBrowserCompat em outra atividade.

Tenha cuidado ao implementar essa configuração de cliente e servidor. Há expectativas sobre como um player em execução em um serviço em segundo plano interage com o restante do sistema. Se o aplicativo não atender a essas expectativas, o usuário poderá ter uma experiência ruim. Consulte Criar um app de áudio para saber mais.

Esta página descreve instruções especiais para gerenciar uma MediaPlayer quando ela é implementada dentro de um serviço.

Executar de forma assíncrona

Como uma Activity, todo o trabalho em uma Service é feito em uma única linha de execução por padrão. Na verdade, quando você executa uma atividade e um serviço do mesmo aplicativo, eles usam a mesma linha de execução (a "linha de execução principal") por padrão.

Os serviços precisam processar intents de entrada rapidamente e nunca realizar cálculos demorados ao responder a elas. É necessário realizar qualquer trabalho pesado ou chamadas de bloqueio de forma assíncrona: de outra linha de execução implementada por você ou usando as diversas ferramentas do framework para processamento assíncrono.

Por exemplo, ao usar MediaPlayer na linha de execução principal, faça o seguinte:

Exemplo:

Kotlin

private const val ACTION_PLAY: String = "com.example.action.PLAY"

class MyService: Service(), MediaPlayer.OnPreparedListener {

    private var mMediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ...
        val action: String = intent.action
        when(action) {
            ACTION_PLAY -> {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer?.apply {
                    setOnPreparedListener(this@MyService)
                    prepareAsync() // prepare async to not block main thread
                }

            }
        }
        ...
    }

    /** Called when MediaPlayer is ready */
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mediaPlayer = ... // initialize it here
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

Processar erros assíncronos

Em operações síncronas, os erros são sinalizados com uma exceção ou um código de erro. No entanto, ao usar recursos assíncronos, notifique o aplicativo sobre erros de maneira adequada. No caso de um MediaPlayer, implemente um MediaPlayer.OnErrorListener e defina-o na instância MediaPlayer:

Kotlin

class MyService : Service(), MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    fun initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer?.setOnErrorListener(this)
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Quando um erro ocorre, a MediaPlayer muda para o estado Error. É necessário redefinir o objeto antes de usá-lo novamente. Para mais detalhes, consulte o diagrama de estado completo da classe MediaPlayer.

Usar wake locks

Ao tocar ou fazer streaming de música em segundo plano, use bloqueios de ativação para impedir que o sistema interfira na reprodução, por exemplo, fazendo o dispositivo entrar no modo de suspensão.

Um wake lock é um indicador para o sistema de que seu aplicativo está usando recursos que precisam permanecer disponíveis mesmo quando o smartphone está ocioso.

Para garantir que a CPU continue em execução enquanto o MediaPlayer é reproduzido, chame o método setWakeMode() ao inicializar o MediaPlayer. A MediaPlayer mantém o bloqueio especificado durante a reprodução e o libera quando é pausada ou interrompida:

Kotlin

mediaPlayer = MediaPlayer().apply {
    // ... other initialization here ...
    setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

Java

mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

No entanto, o wake lock adquirido nesse exemplo garante apenas que a CPU permaneça ativa. Se você estiver fazendo streaming de mídia on-line e estiver usando Wi-Fi, também mantenha um WifiLock, que precisa ser adquirido e liberado manualmente. Portanto, quando você começar a preparar a MediaPlayer com o URL remoto, crie e adquira o bloqueio de Wi-Fi.

Exemplo:

Kotlin

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

wifiLock.acquire()

Java

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

Quando você pausar ou interromper a mídia ou quando não precisar mais da rede, libere o bloqueio:

Kotlin

wifiLock.release()

Java

wifiLock.release();

Limpar

Como mencionado anteriormente, um objeto MediaPlayer pode consumir uma quantidade significativa de recursos do sistema. Por isso, mantenha-o apenas durante o tempo necessário e chame release() quando terminar de usá-lo. É importante chamar esse método de limpeza explicitamente, em vez de contar com a coleta de lixo do sistema, porque pode demorar até que o coletor de lixo recupere a MediaPlayer, já que ele é sensível apenas às necessidades de memória, não à falta de outros recursos relacionados à mídia. Sendo assim, caso você esteja usando um serviço, modifique sempre o método onDestroy() para liberar a MediaPlayer:

Kotlin

class MyService : Service() {

    private var mediaPlayer: MediaPlayer? = null
    // ...

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
    }
}

Java

public class MyService extends Service {
   MediaPlayer mediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mediaPlayer != null) mediaPlayer.release();
   }
}

Procure sempre outras oportunidades para liberar a MediaPlayer, não somente durante o desligamento. Por exemplo, se você acha que não conseguirá reproduzir mídia por um longo período (por exemplo, depois de perder a seleção de áudio), libere a MediaPlayer e crie-a de novo mais tarde. Por outro lado, se você espera interromper a reprodução por um período muito curto, é melhor manter a MediaPlayer para evitar a sobrecarga de criar e preparar de novo.

Saiba mais

O Jetpack Media3 é a solução recomendada para a reprodução de mídia no seu app. Saiba mais.

Estas páginas abrangem tópicos relacionados à gravação, ao armazenamento e à reprodução de áudio e vídeo: