Lire des contenus multimédias en arrière-plan

Vous pouvez lire des contenus multimédias en arrière-plan, même lorsque votre application n'est pas à l'écran, par exemple lorsque l'utilisateur interagit avec d'autres applications.

Pour ce faire, vous devez intégrer MediaPlayer dans un service MediaBrowserServiceCompat et le faire interagir avec un MediaBrowserCompat dans une autre activité.

Soyez prudent lorsque vous implémentez cette configuration client et serveur. Il existe des attentes concernant la manière dont un lecteur exécuté dans un service d'arrière-plan interagit avec le reste du système. Si votre application ne répond pas à ces attentes, l'utilisateur peut avoir une mauvaise expérience. Pour en savoir plus, consultez Créer une application audio.

Cette page décrit les instructions spéciales pour gérer un MediaPlayer lorsque vous l'implémentez dans un service.

Exécuter de manière asynchrone

Comme pour un Activity, tout le travail dans un Service est effectué dans un seul thread par défaut. En fait, lorsque vous exécutez une activité et un service à partir de la même application, ils utilisent le même thread (le "thread principal") par défaut.

Les services doivent traiter rapidement les intents entrants et ne jamais effectuer de calculs longs lorsqu'ils y répondent. Vous devez effectuer tout travail intensif ou appel bloquant de manière asynchrone : soit à partir d'un autre thread que vous implémentez vous-même, soit à l'aide des nombreuses fonctionnalités du framework pour le traitement asynchrone.

Par exemple, lorsque vous utilisez MediaPlayer à partir de votre thread principal, vous devez :

Exemple :

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

Gérer les erreurs asynchrones

Pour les opérations synchrones, les erreurs sont signalées par une exception ou un code d'erreur. Toutefois, lorsque vous utilisez des ressources asynchrones, vous devez informer votre application des erreurs de manière appropriée. Dans le cas d'un MediaPlayer, vous implémentez un MediaPlayer.OnErrorListener et le définissez dans votre instance 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!
    }
}

Lorsqu'une erreur se produit, l'MediaPlayer passe à l'état Error (Erreur). Vous devez le réinitialiser avant de pouvoir l'utiliser à nouveau. Pour en savoir plus, consultez le diagramme d'état complet de la classe MediaPlayer.

Utiliser des wakelocks

Lorsque vous lisez ou diffusez de la musique en arrière-plan, vous devez utiliser des wakelocks pour empêcher le système d'interférer avec la lecture, par exemple en mettant l'appareil en veille.

Un wakelock est un signal envoyé au système indiquant que votre application utilise des fonctionnalités qui doivent rester disponibles même lorsque le téléphone est inactif.

Pour vous assurer que le processeur continue de fonctionner pendant la lecture de votre MediaPlayer, appelez la méthode setWakeMode() lorsque vous initialisez votre MediaPlayer. MediaPlayer : conserve le verrou spécifié pendant la lecture et le libère lorsque la lecture est mise en pause ou arrêtée.

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

Toutefois, le wakelock acquis dans cet exemple garantit uniquement que le processeur reste actif. Si vous diffusez du contenu multimédia sur le réseau et que vous utilisez le Wi-Fi, vous souhaiterez probablement également conserver un WifiLock, que vous devez acquérir et libérer manuellement. Ainsi, lorsque vous commencez à préparer le MediaPlayer avec l'URL distante, vous devez créer et acquérir la serrure Wi-Fi.

Exemple :

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

Lorsque vous mettez en pause ou arrêtez votre contenu multimédia, ou lorsque vous n'avez plus besoin du réseau, vous devez déverrouiller le verrou :

Kotlin

wifiLock.release()

Java

wifiLock.release();

Effectuer un nettoyage

Comme mentionné précédemment, un objet MediaPlayer peut consommer une quantité importante de ressources système. Vous ne devez donc le conserver que le temps nécessaire et appeler release() lorsque vous n'en avez plus besoin. Il est important d'appeler explicitement cette méthode de nettoyage plutôt que de s'appuyer sur la récupération de mémoire du système, car il peut s'écouler un certain temps avant que le récupérateur de mémoire ne récupère le MediaPlayer. En effet, il n'est sensible qu'aux besoins en mémoire et non au manque d'autres ressources liées aux contenus multimédias. Par conséquent, lorsque vous utilisez un service, vous devez toujours remplacer la méthode onDestroy() pour vous assurer de libérer 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();
   }
}

Vous devez toujours chercher d'autres occasions de libérer votre MediaPlayer, en plus de le libérer lors de l'arrêt. Par exemple, si vous prévoyez de ne pas pouvoir lire de contenu multimédia pendant une période prolongée (après avoir perdu la priorité audio, par exemple), vous devez absolument libérer votre MediaPlayer existant et le recréer ultérieurement. En revanche, si vous ne prévoyez d'arrêter la lecture que pendant une très courte période, vous devriez probablement conserver votre MediaPlayer pour éviter la surcharge liée à sa création et à sa préparation.

En savoir plus

Jetpack Media3 est la solution recommandée pour la lecture de contenus multimédias dans votre application. En savoir plus

Ces pages abordent des sujets liés à l'enregistrement, au stockage et à la lecture de contenus audio et vidéo :