Gérer la priorité audio

Deux applications Android ou plus peuvent lire du contenu audio sur le même flux de sortie simultanément, et le système mélange tout. Bien que ce soit techniquement impressionnant, cela peut être très agaçant pour un utilisateur. Pour éviter tous les d'une application musicale en même temps, Android introduit l'idée du contenu audio sélection. Une seule application peut conserver la priorité audio à la fois.

Lorsque votre application doit diffuser du contenu audio, elle doit demander la priorité audio. Lorsqu'il a il peut diffuser du son. Cependant, une fois la priorité audio acquise, jusqu'à ce que vous ayez fini de jouer. Une autre application peut demander la sélection, ce qui préempte votre mise en attente sur la priorité audio. Dans ce cas, votre application doit se mettre en pause. ou baisser le volume pour permettre aux utilisateurs d'entendre plus facilement la nouvelle source audio.

Avant Android 12 (niveau d'API 31), la priorité audio n'est pas gérée par le système. Donc, tandis que les développeurs d'applications sont encouragés à respecter les directives Si une application continue de fonctionner à un volume élevé même après avoir perdu la priorité audio sur un appareil exécutant Android 11 (niveau d'API 30) ou une version antérieure, le système ne peut pas l'empêcher. Cependant, ce comportement de l'application nuit à l'expérience utilisateur et peut souvent conduire aux utilisateurs de désinstaller l'application défaillante.

Une application audio bien conçue doit gérer la priorité audio en suivant ces consignes:

  • Appelez requestAudioFocus() juste avant de commencer à jouer et vérifiez que l'appel renvoie AUDIOFOCUS_REQUEST_GRANTED Appelez requestAudioFocus() dans le rappel onPlay() de votre session multimédia.

  • Lorsqu'une autre application se concentre sur le son, arrête ou interrompt la lecture, ou diminue (c'est-à-dire réduire) le volume.

  • Lorsque la lecture s'arrête (par exemple, lorsque l'application n'a rien d'autre à lire) abandonne la priorité audio. Votre application ne doit pas abandonner la priorité audio si l'utilisateur suspend la lecture, mais peut la reprendre plus tard.

  • Utilisez AudioAttributes pour décrire le type de contenu audio lu par votre application. Par exemple, pour les applications qui diffusent de la voix, préciser CONTENT_TYPE_SPEECH

La priorité audio est gérée différemment selon la version d'Android est en cours d'exécution:

Android 12 (niveau d'API 31) ou version ultérieure
Le ciblage audio est géré par le système. Le système force la lecture audio à partir d'un pour faire disparaître l'application en fondu lorsqu'une autre application demande la priorité audio. Le système et coupe également le son de la lecture audio à la réception d'un appel.
Android 8.0 (niveau d'API 26) à Android 11 (niveau d'API 30)
Le ciblage audio n'est pas géré par le système, mais inclut des modifications Lancement à partir d'Android 8.0 (niveau d'API 26).
Android 7.1 (niveau d'API 25) ou version antérieure
Le ciblage audio n'est pas géré par le système, mais les applications le gèrent à l'aide de la requestAudioFocus() et abandonAudioFocus()

Priorité audio sur Android 12 ou version ultérieure

Une appli multimédia ou de jeu qui utilise la priorité audio ne doit pas lire de contenu audio après avoir perdu le focus. Dans Android 12 (niveau d'API 31) ou version ultérieure, le système applique cette comportemental. Lorsqu'une application demande la priorité audio alors qu'une autre est ciblée et le système force la fermeture en fondu de l'application. L'ajout du paramètre Le fondu enchaîné offre une transition plus fluide lors du passage d'une application à une autre.

Ce comportement se produit lorsque les conditions suivantes sont remplies:

  1. La première application en cours de lecture répond à tous ces critères:

  2. Une deuxième application demande la priorité audio avec AudioManager.AUDIOFOCUS_GAIN.

Lorsque ces conditions sont remplies, le système audio quitte la première application en fondu. Au fin du fondu, le système informe la première application qu'une perte de mise au point est détectée. L'application le son des joueurs reste coupé jusqu'à ce que l'application demande à nouveau la priorité audio.

Comportements de ciblage audio existants

Vous devez également être conscient de ces autres cas qui impliquent un changement dans l'audio le focus.

Atténuation automatique

Diminution automatique (réduction temporaire du volume audio d'une application pour que l'autre peut être entendue clairement) a été introduite dans Android 8.0 (niveau d'API 26).

En laissant le système implémenter la diminution, vous n'avez pas à implémenter votre application.

L'atténuation automatique se produit également lorsqu'une notification audio attire l'attention depuis une application de jeu. Le début de la lecture de la notification est synchronisé au bout de la rampe d'atténuation.

L'atténuation automatique se produit lorsque les conditions suivantes sont remplies:

  1. La première application en cours de lecture répond à tous ces critères:

  2. Une deuxième application demande la priorité audio avec AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK

Lorsque ces conditions sont remplies, le système audio bloque tous les joueurs actifs de la première application tandis que la deuxième est ciblée. Lorsque la deuxième application abandonne se concentrer, cela les éloigne. La première application n'est pas notifiée lorsqu'elle perd son focus. et n'a rien à faire.

Notez que la diminution automatique n'est pas effectuée lorsque l'utilisateur écoute contenu vocal, car l'utilisateur pourrait manquer une partie du programme. Par exemple : le guidage vocal pour les itinéraires routiers ne sont pas inclinés.

Couper le son de la lecture audio en cours pour les appels téléphoniques entrants

Certaines applications ne se comportent pas correctement et continuent de lire le contenu audio lors des appels téléphoniques. Cette situation oblige l'utilisateur à trouver et à couper le son de l'application en cause ou à la quitter dans pour entendre leur appel. Pour éviter cela, le système peut couper le son des autres pendant un appel entrant. Le système appelle cette fonctionnalité lorsqu'un lorsqu'un appel téléphonique entrant est reçu et qu'une application remplit les conditions suivantes:

  • L'application dispose de l'AudioAttributes.USAGE_MEDIA ou Attribut d'utilisation AudioAttributes.USAGE_GAME.
  • L'appli a bien demandé la priorité audio (tout gain de concentration) et la lecture est en cours audio.

Si la lecture d'une application se poursuit pendant l'appel, le son de la lecture est coupé jusqu'à ce que l'icône à la fin de l'appel. Toutefois, si la lecture d'une application démarre pendant l'appel, ce lecteur n'est pas en partant du principe que l'utilisateur a lancé la lecture intentionnellement.

Priorité audio dans Android 8.0 à Android 11

À partir d'Android 8.0 (niveau d'API 26), lorsque vous appelez requestAudioFocus() vous devez fournir un paramètre AudioFocusRequest. AudioFocusRequest contient des informations sur le contexte audio et les fonctionnalités de votre application. La système utilise ces informations pour gérer le gain et la perte de priorité audio automatiquement. Pour libérer le ciblage audio, appelez la méthode abandonAudioFocusRequest() qui utilise également un AudioFocusRequest comme argument. Utiliser la même AudioFocusRequest lorsque vous demandez et abandonnez le focus.

Pour créer un AudioFocusRequest, utilisez un AudioFocusRequest.Builder Étant donné qu'une demande de focus doit toujours spécifier le type de la requête, celui-ci est inclus dans le constructeur. pour le compilateur. Utilisez les méthodes du compilateur pour définir les autres champs du requête.

Le champ FocusGain est obligatoire. tous les autres champs sont facultatifs.

MéthodeNotes
setFocusGain() Ce champ est obligatoire dans chaque requête. Il a les mêmes valeurs que durationHint utilisé dans l'appel à requestAudioFocus() antérieur à Android 8.0: AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ou AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE.
setAudioAttributes() AudioAttributes décrit le cas d'utilisation de votre application. La lorsqu'une appli gagne ou perd sa priorité audio. Attributs remplacer la notion de type de flux. Sur Android 8.0 (niveau d'API 26) et versions ultérieures, types de flux pour toute opération autre que le contrôle du volume sont obsolètes. Utilisez les mêmes attributs dans la requête de focus que ceux utilisés dans votre lecteur audio (comme comme indiqué dans l'exemple qui suit ce tableau).

Utilisez un AudioAttributes.Builder pour spécifier le d'abord, puis utilisez cette méthode pour attribuer les attributs au requête.

Si aucune valeur n'est spécifiée, AudioAttributes est défini par défaut sur AudioAttributes.USAGE_MEDIA.

setWillPauseWhenDucked() Lorsqu'une autre application demande à sélectionner AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, l'application sélectionnée ne reçoivent généralement onAudioFocusChange() car le système peut effectuer le tout seul. Lorsque vous avez besoin de mettre la lecture en pause plutôt que que de baisser le volume, appelez setWillPauseWhenDucked(true), puis créez et définissez une OnAudioFocusChangeListener, comme décrit dans la section en baisse.
setAcceptsDelayedFocusGain() Une requête de ciblage audio peut échouer lorsque le ciblage est verrouillé par une autre application. Cette méthode active le retrait de la focalisation, à savoir la capacité d'obtenir le focus de manière asynchrone lorsqu'il devient disponible.

Notez que le gain de mise au point différé ne fonctionne que si vous spécifiez également un AudioManager.OnAudioFocusChangeListener dans la requête audio, car votre application doit recevoir le rappel afin de savoir que le focus a été activé.

setOnAudioFocusChangeListener() Un élément OnAudioFocusChangeListener n'est requis que si vous spécifiez également willPauseWhenDucked(true) ou setAcceptsDelayedFocusGain(true) dans la requête.

Il existe deux méthodes pour définir l'écouteur: l'une avec et l'autre sans l'argument "manager". Le gestionnaire est le thread sur lequel l'écouteur s'exécute. Si vous ne spécifie pas de gestionnaire : le gestionnaire associé à l'instance principale Looper est utilisé.

L'exemple suivant montre comment utiliser un AudioFocusRequest.Builder pour compiler un AudioFocusRequest et la requête et l'abandon du ciblage audio:

Kotlin

// initializing variables for audio focus and playback management
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
    setAudioAttributes(AudioAttributes.Builder().run {
        setUsage(AudioAttributes.USAGE_GAME)
        setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        build()
    })
    setAcceptsDelayedFocusGain(true)
    setOnAudioFocusChangeListener(afChangeListener, handler)
    build()
}
val focusLock = Any()

var playbackDelayed = false
var playbackNowAuthorized = false

// requesting audio focus and processing the response
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
    playbackNowAuthorized = when (res) {
        AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false
        AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
            playbackNow()
            true
        }
        AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
            playbackDelayed = true
            false
        }
        else -> false
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
override fun onAudioFocusChange(focusChange: Int) {
    when (focusChange) {
        AudioManager.AUDIOFOCUS_GAIN ->
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false
                    resumeOnFocusGain = false
                }
                playbackNow()
            }
        AudioManager.AUDIOFOCUS_LOSS -> {
            synchronized(focusLock) {
                resumeOnFocusGain = false
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying()
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // ... pausing or ducking depends on your app
        }
    }
}

Java

// initializing variables for audio focus and playback management
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(playbackAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(afChangeListener, handler)
        .build();
final Object focusLock = new Object();

boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;

// requesting audio focus and processing the response
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
    if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
        playbackNowAuthorized = false;
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        playbackNowAuthorized = true;
        playbackNow();
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
        playbackDelayed = true;
        playbackNowAuthorized = false;
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
@Override
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false;
                    resumeOnFocusGain = false;
                }
                playbackNow();
            }
            break;
        case AudioManager.AUDIOFOCUS_LOSS:
            synchronized(focusLock) {
                resumeOnFocusGain = false;
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying();
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // ... pausing or ducking depends on your app
            break;
        }
    }
}

Atténuation automatique

Dans Android 8.0 (niveau d'API 26), lorsqu'une autre application demande à se concentrer avec AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK : le système peut baisser et restaurer le volume sans appeler le rappel onAudioFocusChange() de l'application.

Bien que la diminution automatique soit un comportement acceptable pour la lecture de musique et de vidéos applications, elle n'est pas utile lors de la lecture de contenus audio, application de livre audio. Dans ce cas, l'application doit se mettre en pause.

Si vous souhaitez que votre application se mette en pause lorsque vous êtes invité à baisser plutôt que de baisser le volume, créez une OnAudioFocusChangeListener avec Une méthode de rappel onAudioFocusChange() qui implémente le comportement de mise en pause/reprise souhaité. Appelez setOnAudioFocusChangeListener() pour enregistrer l'écouteur, puis appelez setWillPauseWhenDucked(true) pour indiquer au système d'utiliser votre rappel plutôt que d'effectuer l'atténuation automatique.

Gain de concentration différé

Parfois, le système ne peut pas accorder de demande de ciblage audio, car le focus est "verrouillée" par une autre application, par exemple lors d'un appel téléphonique. Dans ce cas, requestAudioFocus() renvoie AUDIOFOCUS_REQUEST_FAILED. Dans ce cas, votre application ne doit pas poursuivre la lecture audio, car elle n'a pas le focus.

La méthode setAcceptsDelayedFocusGain(true), qui permet à votre application de gérer une requête de sélection de manière asynchrone. Avec cet indicateur défini, une requête effectuée lorsque le focus est verrouillé renvoie AUDIOFOCUS_REQUEST_DELAYED. Lorsque la condition qui a verrouillé l'audio n'existe plus. Par exemple, à la fin d'un appel téléphonique, le système accorde la demande de sélection en attente et appelle onAudioFocusChange() pour avertir votre l'application.

Afin de gérer le gain de focus retardé, vous devez créer un OnAudioFocusChangeListener avec une méthode de rappel onAudioFocusChange() qui implémente le comportement souhaité et enregistre l'écouteur en appelant setOnAudioFocusChangeListener()

Priorité audio sur Android 7.1 ou version antérieure

Lorsque vous appelez requestAudioFocus() vous devez spécifier une durée être mis à l'honneur par une autre application actuellement sélectionnée et en cours de lecture:

  • Demander la priorité audio permanente (AUDIOFOCUS_GAIN) lorsque vous prévoyez de lire du contenu audio dans un futur proche (par exemple, lorsque vous écoutez de la musique). conteneur précédent de la sélection audio pour arrêter la lecture.
  • Demander le focus temporaire (AUDIOFOCUS_GAIN_TRANSIENT) lorsque vous prévoyez de jouer le contenu audio d'un court laps de temps, lorsque vous pensez que l'élément précédent doit être mis en pause ; en cours de lecture.
  • Demander un focus temporaire avec diminution (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) pour indiquer que vous comptez lire du contenu audio que pendant une courte période et qu'il peut conserver CANNOT TRANSLATE (baisse) sa sortie audio. Les deux sorties audio sont mixtes dans le flux audio. La diminution est particulièrement adaptée aux applications qui utilisent flux audio par intermittence, par exemple pour les itinéraires routiers audibles.

La méthode requestAudioFocus() nécessite également un AudioManager.OnAudioFocusChangeListener. Cet écouteur doit être dans l'activité ou le service propriétaire de votre session multimédia. Il implémente le rappel onAudioFocusChange() que votre application reçoit lorsque une autre application acquiert ou abandonne la priorité audio.

L'extrait suivant demande la priorité audio permanente sur le flux STREAM_MUSIC et enregistre un OnAudioFocusChangeListener à gérer modifications ultérieures de la priorité audio. (L'écouteur de changement est abordé dans Répondre à un changement de priorité audio)

Kotlin

audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener

...
// Request audio focus for playback
val result: Int = audioManager.requestAudioFocus(
        afChangeListener,
        // Use the music stream.
        AudioManager.STREAM_MUSIC,
        // Request permanent focus.
        AudioManager.AUDIOFOCUS_GAIN
)

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

Java

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// Request audio focus for playback
int result = audioManager.requestAudioFocus(afChangeListener,
                             // Use the music stream.
                             AudioManager.STREAM_MUSIC,
                             // Request permanent focus.
                             AudioManager.AUDIOFOCUS_GAIN);

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

Une fois la lecture terminée, appelez abandonAudioFocus()

Kotlin

audioManager.abandonAudioFocus(afChangeListener)

Java

// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);

Cela indique au système que vous n'avez plus besoin du ciblage et annule l'enregistrement du OnAudioFocusChangeListener associé. Si vous avez demandé une mise au point temporaire, cela informera une appli qui s'est arrêtée ou baissée qu'elle peut continuer à lire ou rétablir le volume.

Répondre à un changement de priorité audio

Lorsqu'une application acquiert la priorité audio, elle doit pouvoir la publier lorsqu'une autre application demande la priorité audio pour elle-même. Dans ce cas, votre application reçoit un appel vers onAudioFocusChange() dans AudioFocusChangeListener que vous avez spécifié lorsque l'application a appelé requestAudioFocus().

Le paramètre focusChange transmis à onAudioFocusChange() indique le genre du changement en cours. Elle correspond par rapport à l'indicateur de durée utilisé par l'application qui acquiert l'attention. Votre application doit répondre de manière appropriée.

Perte de concentration temporaire
Si le changement d'objectif est temporaire (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK ou AUDIOFOCUS_LOSS_TRANSIENT), votre application devrait baisser (si vous ne comptez pas en cas d'atténuation automatique) ou mettre la lecture en pause, mais sinon ils conservent le même état.

En cas de perte temporaire de la priorité audio, vous devez continuer à surveiller les changements et soyez prêt à reprendre la lecture normale dès que le son le focus. Lorsque l'application bloquante abandonne le focus, vous recevez un rappel (AUDIOFOCUS_GAIN). À ce stade, vous pouvez rétablir le volume normal ou relancer la lecture.

Perte de concentration permanente
Si la perte de la sélection audio est permanente (AUDIOFOCUS_LOSS), une autre application est de la lecture du contenu audio. Votre application doit mettre la lecture immédiatement en pause, car elle n'effectuera jamais recevoir un rappel AUDIOFOCUS_GAIN. Pour relancer la lecture, l'utilisateur doit effectuer une action explicite, comme appuyer sur la commande de lecture de transport dans une notification ou dans l'UI d'une application.

L'extrait de code suivant montre comment mettre en œuvre OnAudioFocusChangeListener et son rappel onAudioFocusChange(). Notez que Utilisation d'un Handler pour retarder le rappel d'arrêt en cas de perte définitive de l'audio le focus.

Kotlin

private val handler = Handler()
private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
        AudioManager.AUDIOFOCUS_LOSS -> {
            // Permanent loss of audio focus
            // Pause playback immediately
            mediaController.transportControls.pause()
            // Wait 30 seconds before stopping playback
            handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30))
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            // Pause playback
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // Lower the volume, keep playing
        }
        AudioManager.AUDIOFOCUS_GAIN -> {
            // Your app has been granted audio focus again
            // Raise volume to normal, restart playback if necessary
        }
    }
}

Java

private Handler handler = new Handler();
AudioManager.OnAudioFocusChangeListener afChangeListener =
  new AudioManager.OnAudioFocusChangeListener() {
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
        // Permanent loss of audio focus
        // Pause playback immediately
        mediaController.getTransportControls().pause();
        // Wait 30 seconds before stopping playback
        handler.postDelayed(delayedStopRunnable,
          TimeUnit.SECONDS.toMillis(30));
      }
      else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
        // Pause playback
      } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
        // Lower the volume, keep playing
      } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
        // Your app has been granted audio focus again
        // Raise volume to normal, restart playback if necessary
      }
    }
  };

Le gestionnaire utilise un Runnable qui se présente comme suit:

Kotlin

private var delayedStopRunnable = Runnable {
    mediaController.transportControls.stop()
}

Java

private Runnable delayedStopRunnable = new Runnable() {
    @Override
    public void run() {
        getMediaController().getTransportControls().stop();
    }
};

Pour vous assurer que l'arrêt différé ne se déclenche pas si l'utilisateur relance la lecture, appelez mHandler.removeCallbacks(mDelayedStopRunnable) en réponse à n'importe quel état des modifications. Par exemple, appelez removeCallbacks() dans le onPlay() de votre rappel : onSkipToNext(), etc. Vous devez également appeler cette méthode dans l'objet Rappel onDestroy() lors du nettoyage des ressources utilisées par votre service.