Diffuser des contenus multimédias en streaming avec ExoPlayer

1. Avant de commencer

526b239733391e74.png

Capture d'écran : application YouTube sur Android

ExoPlayer est un lecteur multimédia au niveau de l'application, conçu à partir d'API multimédias de base dans Android. Il présente certains avantages par rapport à MediaPlayer, le lecteur intégré à Android. Il est compatible avec une grande partie des formats multimédias de MediaPlayer, ainsi qu'avec les formats adaptatifs tels que DASH et SmoothStreaming. ExoPlayer est extrêmement personnalisable et extensible. C'est pourquoi il convient à de nombreux cas d'utilisation avancés. Il s'agit d'un projet Open Source utilisé par les applications Google, y compris YouTube et Google Play Films et TV.

Prérequis

  • Une certaine connaissance du développement sur Android et d'Android Studio

Objectifs de l'atelier

  • Créer une instance SimpleExoPlayer, qui prépare et lit les contenus multimédias depuis différentes sources
  • Intégrer ExoPlayer au cycle de vie d'une activité de l'application, pour permettre le passage en arrière-plan et au premier plan, ainsi que la reprise de la lecture dans un environnement à une ou plusieurs fenêtres
  • Utiliser des MediaItem pour créer une playlist
  • Lire des flux vidéo adaptatifs, qui adaptent la qualité du contenu multimédia à la bande passante disponible
  • Enregistrer des écouteurs d'événements pour surveiller l'état de la lecture et afficher la façon dont les écouteurs peuvent être utilisés pour mesure la qualité de la lecture
  • Utiliser des composants standards de l'UI d'ExoPlayer, puis les personnaliser pour les adapter au style de votre application

Ce dont vous avez besoin

  • La dernière version stable d'Android Studio
  • Un appareil Android fonctionnant sous JellyBean (4.1) ou version ultérieure (idéalement Nougat (7.1) ou version ultérieure, car il est possible d'utiliser plusieurs fenêtres avec cette version de l'OS)

2. Configuration

Obtenir le code

Pour commencer, téléchargez le projet Android Studio :

Vous pouvez également cloner le dépôt GitHub :

git clone https://github.com/googlecodelabs/exoplayer-intro.git

Structure des répertoires

Une fois le dépôt cloné ou décompressé, vous pouvez accéder au dossier racine (exoplayer-intro), qui contient un projet Gradle composé de plusieurs modules : un module d'application et un module pour chaque étape de cet atelier de programmation, avec toutes les ressources nécessaires.

Importer le projet

  1. Lancez Android Studio.
  2. Sélectionnez Fichier > Nouveau > Importer un projet*.*
  3. Sélectionnez le fichier racine build.gradle.

111b190903697765.png

Capture d'écran : structure du projet importé

Une fois le projet compilé, six modules s'affichent : le module app (de type application) et cinq modules nommés exoplayer-codelab-N (où N correspond à un chiffre compris entre 00 et 04,, chacun de type bibliothèque). Le module app ne contient qu'un fichier manifeste. L'intégralité du module exoplayer-codelab-N spécifié fusionne lorsque l'application est compilée avec une dépendance Gradle dans app/build.gradle.

app/build.gradle

dependencies {
   implementation project(":exoplayer-codelab-00")
}

L'activité du lecteur multimédia est conservée dans le module exoplayer-codelab-N. Si elle reste stockée dans un module de bibliothèque distinct, c'est pour que vous puissiez la partager entre des APK ciblant différentes plates-formes (mobile et Android TV, par exemple). De plus, vous pouvez ainsi profiter de certaines fonctionnalités, comme Dynamic Delivery, qui permet d'installer votre fonctionnalité de lecture de contenus multimédias uniquement lorsque l'utilisateur en a besoin.

  1. Déployez et exécutez l'application pour vérifier que tout fonctionne bien. L'application doit remplir l'écran avec un arrière-plan noir.

2dae13fed92e6c8c.png

Capture d'écran : application vide en cours d'exécution

3. Diffuser des contenus en streaming

Ajouter une dépendance ExoPlayer

ExoPlayer est un projet Open Source hébergé sur GitHub. Chaque version est distribuée via Google Maven, l'un des dépôts de packages par défaut utilisés par Android Studio et Gradle. Chacune est identifiée de manière unique par une chaîne au format suivant :

com.google.android.exoplayer:exoplayer:X.X.X

Vous pouvez ajouter ExoPlayer à votre projet en important simplement ses classes et composants d'UI. Ce projet est plutôt de petite taille, avec une empreinte minime d'environ 70 à 300 ko, selon les fonctionnalités incluses et les formats acceptés. La bibliothèque d'ExoPlayer est divisée en modules pour que les développeurs puissent importer uniquement la fonctionnalité dont ils ont besoin. Pour en savoir plus sur la structure modulaire d'ExoPlayer, reportez-vous à la section Ajouter des modules ExoPlayer.

  1. Ouvrez le fichier build.gradle du module player-lib.
  2. Ajoutez les lignes ci-dessous à la section dependencies, puis synchronisez le projet.

exoplayer-codelab-00/build.gradle

dependencies {
   [...]

implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'

}

Ajouter l'PlayerView element

  1. Ouvrez le fichier de ressources de mise en page activity_player.xml depuis le module exoplayer-codelab-00.
  2. Placez le curseur dans l'élément FrameLayout.
  3. Commencez à saisir <PlayerView et laissez Android Studio remplir automatiquement l'élément PlayerView.
  4. Utilisez match_parent comme valeur pour width et height.
  5. Déclarez video_view comme ID.

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>

Dans la suite de cet atelier, cet élément d'interface utilisateur sera appelé "vue de la vidéo".

  1. Dans PlayerActivity, vous pouvez à présent obtenir une référence pour l'arborescence de vues, créée à partir du fichier XML que vous venez de modifier.

PlayerActivity.kt

    private val viewBinding by lazy(LazyThreadSafetyMode.NONE) {
        ActivityPlayerBinding.inflate(layoutInflater)
    }
  1. Définissez la racine de votre arborescence de vues comme vue de contenu de votre activité. Vérifiez également que la propriété videoView est visible dans votre référence viewBinding et que son type est PlayerView.
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)
    }

Créer une instance d'ExoPlayer

Pour lire un contenu multimédia en streaming, vous avez besoin d'un objet ExoPlayer. Le plus simple pour en créer un consiste à utiliser la classe SimpleExoPlayer.Builder. Comme son nom l'indique, elle se sert du schéma de compilateur pour compiler une instance SimpleExoPlayer.

L'instance SimpleExoPlayer est une implémentation polyvalente pratique de l'interface ExoPlayer.

Ajoutez une méthode privée initializePlayer pour créer votre instance SimpleExoPlayer.

PlayerActivity.kt

private var player: SimpleExoPlayer? = null
[...]
   private fun initializePlayer() {
        player = SimpleExoPlayer.Builder(this)
            .build()
            .also { exoPlayer ->
                viewBinding.videoView.player = exoPlayer
            }
    }

Créez une classe SimpleExoPlayer.Builder en utilisant votre contexte, puis appelez build pour créer votre objet SimpleExoPlayer. Celui-ci est ensuite attribué à player, que vous devez déclarer comme champ de membre. Ensuite, utilisez la propriété modifiable viewBinding.videoView.player pour lier player à la vue correspondante.

Créer un élément multimédia

Vous avez à présent besoin de contenus à lire pour votre objet player. Pour cela, créez un MediaItem. Il existe de nombreux types d'éléments MediaItem. Pour commencer, vous allez en créer un pour un fichier MP3 sur Internet.

La façon la plus simple de créer un MediaItem consiste à utiliser MediaItem.fromUri, qui accepte l'URI d'un fichier multimédia. Ajoutez le MediaItem au player à l'aide de player.setMediaItem.

  1. Ajoutez le code suivant à initializePlayer dans le bloc also :

PlayerActivity.kt

private fun initializePlayer() {
    [...]
        .also { exoPlayer ->
            [...]
            val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
            exoPlayer.setMediaItem(mediaItem)
        }
}

Notez que la valeur de R.string.media_url_mp3 est https://storage.googleapis.com/exoplayer-test-media-0/play.mp3 dans strings.xml.

Optimiser le cycle de vie de l'activité

L'instance player peut consommer énormément de ressources, y compris pour la mémoire, le processeur, les connexions réseau et les codecs matériels. Un grand nombre de ces ressources est limité, surtout pour les codecs matériels qui ne peuvent avoir qu'une seule ressource. Il est important que vous libériez ces ressources pour les autres applications à utiliser lorsque vous ne vous en servez pas, par exemple quand votre application est mise en arrière-plan.

Autrement dit, le cycle de vie de votre lecteur doit être lié au cycle de vie de votre application. Pour cela, vous devez ignorer les quatre méthodes de PlayerActivity : onStart, onResume, onPause et onStop.

  1. Ouvrez PlayerActivity, puis cliquez sur Code menu (Menu du code) > Override methods (Ignorer les méthodes).
  2. Sélectionnez onStart, onResume, onPause et onStop.
  3. Initialisez le lecteur dans le rappel onStart ou onResume, selon le niveau d'API.

PlayerActivity.kt

public override fun onStart() {
 super.onStart()
 if (Util.SDK_INT >= 24) {
   initializePlayer()
 }
}

public override fun onResume() {
 super.onResume()
 hideSystemUi()
 if ((Util.SDK_INT < 24 || player == null)) {
   initializePlayer()
 }
}

Le niveau d'API Android 24 (et supérieur) gère plusieurs fenêtres. Comme votre application peut être visible et inactive en mode fenêtre partagée, vous devez initialiser le lecteur dans onStart. Pour les niveaux d'API Android 24 et inférieurs, vous devez patienter jusqu'à ce que vous récupériez les ressources. Vous devez donc attendre onResume pour pouvoir initialiser le lecteur.

  1. Ajoutez la méthode hideSystemUi.

PlayerActivity.kt

@SuppressLint("InlinedApi")
private fun hideSystemUi() {
 viewBinding.videoView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
     or View.SYSTEM_UI_FLAG_FULLSCREEN
     or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
     or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
     or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
     or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}

hideSystemUi est une méthode d'assistance appelée dans onResume. Cette méthode vous permet d'afficher le lecteur en plein écran.

  1. Libérez les ressources avec releasePlayer (que vous allez créer juste après) dans onPause et onStop.

PlayerActivity.kt

public override fun onPause() {
 super.onPause()
 if (Util.SDK_INT < 24) {
   releasePlayer()
 }
}

public override fun onStop() {
 super.onStop()
 if (Util.SDK_INT >= 24) {
   releasePlayer()
 }
}

Pour les niveaux d'API 24 et inférieurs, vous n'avez aucune certitude qu'onStop sera appelé. Vous devez donc libérer le lecteur dès que possible dans onPause. Pour les niveaux d'API 24 et supérieurs (qui proposent les modes Multifenêtre et Fenêtre partagée), onStop sera obligatoirement appelé. Lorsqu'elle est en pause, votre activité reste visible. Vous devez donc attendre onStop pour libérer le lecteur.

Vous devez à présent créer une méthode releasePlayer, qui libère les ressources du lecteur et le détruit.

  1. Ajoutez le code suivant à l'activité :

PlayerActivity.kt

private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L
[...]

private fun releasePlayer() {
    player?.run {
        playbackPosition = this.currentPosition
        currentWindow = this.currentWindowIndex
        playWhenReady = this.playWhenReady
        release()
    }
    player = null
}

Avant de libérer et de détruire le lecteur, stockez les informations suivantes :

  • État de lecture/pause avec playWhenReady
  • Position actuelle de la lecture avec currentPosition
  • Index de la fenêtre active avec currentWindowIndex (pour en savoir plus sur les fenêtres, reportez-vous à la section Chronologie)

Il vous suffit de fournir ces informations d'état lorsque vous initialisez le lecteur pour que l'utilisateur puisse reprendre la lecture là où il l'avait arrêtée.

Derniers préparatifs

À présent, il ne vous reste plus qu'à fournir à votre lecteur, pendant l'initialisation, les informations d'état que vous avez enregistrées dans releasePlayer.

  1. Ajoutez le code suivant à initializePlayer :

PlayerActivity.kt

private fun initializePlayer() {
    [...]
    exoPlayer.playWhenReady = playWhenReady
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.prepare()
}

Voilà ce qui se passe :

  • playWhenReady indique au lecteur s'il doit démarrer la lecture dès que toutes les ressources pour la lecture ont été acquises. Comme playWhenReady est défini sur true au départ, la lecture démarre automatiquement la première fois que l'application est lancée.
  • seekTo indique au lecteur de rechercher une certaine position dans une fenêtre spécifique. currentWindow et playbackPosition sont tous deux mis à zéro afin que la lecture démarre dès le début la première fois que l'application est lancée.
  • prepare signale au lecteur qu'il doit acquérir toutes les ressources requises pour la lecture.

Lire le fichier audio

Vous avez enfin terminé ! Lancez l'application pour lire le fichier MP3 et voir l'image intégrée.

d92917867ee23ef8.png

Capture d'écran : l'application lit un seul titre.

Tester le cycle de vie de l'activité

Testez si l'application fonctionne dans tous les états du cycle de vie de l'activité.

  1. Lancez une autre application et remettez votre application au premier plan. Est-ce qu'elle reprend à la bonne position ?
  2. Mettez l'application en pause et passez-la en arrière-plan, puis à nouveau au premier plan. Est-ce qu'elle est toujours en pause lorsqu'elle passe en arrière-plan ?
  3. Faites pivoter l'application. Son comportement change-t-il si vous passez du mode Portrait au mode Paysage et inversement ?

Lire une vidéo

Pour lire une vidéo, il vous suffit de remplacer l'URI de l'élément multimédia par un fichier MP4.

  1. Remplacez l'URI dans initializePlayer par R.string.media_url_mp4.
  2. Relancez l'application et testez son comportement, y compris lorsqu'elle est mise en arrière-plan alors qu'une vidéo est en cours de lecture.

PlayerActivity.kt

private fun initializePlayer() {
  [...]
     val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));
  [...]
}

PlayerView se charge de tout. Ici, c'est la vidéo qui est affichée en plein écran, pas l'image.

425c6c65f78e8d46.png

Capture d'écran : l'application lit une vidéo.

Félicitations ! Vous venez de créer une application pour diffuser un fichier multimédia sur Android en streaming et en plein écran, avec la gestion du cycle de vie, les enregistrements des états et les commandes d'interface utilisateur.

4. Créer une playlist

Votre application actuelle lit un seul fichier multimédia, mais que faire si vous voulez en lire plusieurs d'affilée ? Pour cela, vous avez besoin d'une playlist.

Vous pouvez créer une playlist en ajoutant plusieurs éléments MediaItem au player à l'aide d'addMediaItem. Cela permet d'enchaîner la lecture des fichiers de manière fluide. La mise en mémoire tampon étant gérée en arrière-plan, aucune icône de chargement ne s'affiche lorsque l'utilisateur passe d'un élément multimédia à un autre.

  1. Ajoutez le code suivant à initializePlayer :

PlayerActivity.kt

private void initializePlayer() {
  [...]
  exoPlayer.addMediaItem(mediaItem) // Existing code

  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3));
  exoPlayer.addMediaItem(secondMediaItem);
  [...]
}

Vérifiez le comportement des commandes du lecteur. Vous pouvez utiliser les boutons 1f79fee4d082870f.png et 39627002c03ce320.png pour parcourir la liste des éléments multimédias.

7b5c034dafabe1bd.png

Capture d'écran : commandes de lecture, avec les boutons Précédent et Suivant

C'est plutôt pratique ! Pour en savoir plus, reportez-vous à la documentation pour les développeurs sur les éléments multimédias et les playlists, ainsi qu'à cet article sur l'API Playlist.

5. Streaming adaptatif

Le streaming adaptatif est une technique de streaming de fichiers multimédias qui consiste à adapter la qualité du flux à la bande passante réseau disponible. L'utilisateur bénéficie ainsi de la meilleure qualité possible pour sa bande passante.

Généralement, le même contenu multimédia est divisé en plusieurs titres de différentes qualités (débits et résolutions). Le lecteur choisit le titre en fonction de la bande passante réseau disponible.

Chaque titre est sectionné en fragments d'une certaine durée, généralement entre 2 et 10 secondes. Cela permet au lecteur de passer rapidement d'un titre à l'autre en fonction de l'évolution de la disponibilité de la bande passante. Le lecteur se charge ensuite d'assembler à nouveau ces fragments pour que la lecture soit fluide.

Sélection de titres adaptative

Sélectionner le titre le plus adapté à l'environnement actuel est une opération centrale du streaming adaptatif. Mettez à jour votre application pour lire des fichiers multimédias en streaming en utilisant la sélection de titres adaptative.

  1. Mettez à jour initializePlayer avec le code suivant :

PlayerActivity.kt

private fun initializePlayer() {
   val trackSelector = DefaultTrackSelector(this).apply {
        setParameters(buildUponParameters().setMaxVideoSizeSd())
    }
   player = SimpleExoPlayer.Builder(this)
        .setTrackSelector(trackSelector)
        .build()
  [...]
}

Tout d'abord, créez un DefaultTrackSelector, qui sera chargé de sélectionner les titres dans l'élément multimédia. Ensuite, indiquez à votre trackSelector de ne sélectionner que les titres de définition standard ou inférieure. Il s'agit d'un moyen efficace d'économiser les données de l'utilisateur (au détriment de la qualité). Enfin, transférez votre trackSelector à votre compilateur pour qu'il soit utilisé lors de la compilation de l'instance SimpleExoPlayer.

Créer un MediaItem adaptatif

DASH est un format de streaming adaptatif couramment utilisé. Pour diffuser en streaming un contenu DASH, vous devez créer un MediaItem, comme précédemment. Cependant, ici, vous devez utiliser MediaItem.Builder au lieu de fromUri,

car fromUri utilise l'extension de fichier pour déterminer le format multimédia sous-jacent. Or, l'URI DASH ne possède pas d'extension de fichier. De ce fait, nous devons fournir un type MIME d'APPLICATION_MPD pour créer le MediaItem.

  1. Mettez à jour initializePlayer comme indiqué ci-dessous :

PlayerActivity.kt

private void initializePlayer() {
  [...]

  // Replace this line
  val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));

  // With this
   val mediaItem = MediaItem.Builder()
        .setUri(getString(R.string.media_url_dash))
        .setMimeType(MimeTypes.APPLICATION_MPD)
        .build()

  // Also remove the following lines
  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
    exoPlayer.addMediaItem(secondMediaItem)
}
  1. Redémarrez l'application et observez le fonctionnement du streaming vidéo adaptatif avec DASH. C'est plutôt simple avec ExoPlayer.

Autres formats de streaming adaptatif

HLS (MimeTypes.APPLICATION_M3U8) et SmoothStreaming (MimeTypes.APPLICATION_SS) sont d'autres formats de streaming adaptatif courants qui sont compatibles avec ExoPlayer. Pour savoir comment créer d'autres sources multimédias adaptatives, reportez-vous à l'application de démonstration ExoPlayer.

6. Écouter les événements

Lors des étapes précédentes, vous avez vu comment lire en streaming des flux multimédias progressifs et adaptatifs. ExoPlayer effectue de nombreuses opérations en arrière-plan. Par exemple :

  • Allouer la mémoire
  • Télécharger les fichiers du conteneur
  • Extraire les métadonnées du conteneur
  • Décoder les données
  • Afficher la vidéo et le texte à l'écran, et diffuser l'audio sur les enceintes

Il est parfois utile de savoir ce qu'ExoPlayer fait lors de l'exécution afin de comprendre et d'améliorer l'expérience de lecture pour vos utilisateurs.

Par exemple, vous pouvez décider de refléter les changements de l'état de lecture dans l'interface utilisateur en procédant comme suit :

  • En affichant une icône de chargement lorsque le lecteur met les données en mémoire tampon
  • En affichant le texte "À regarder ensuite" en superposition à la fin de la lecture du titre

ExoPlayer propose plusieurs interfaces d'écouteur qui fournissent des rappels pour les événements utiles. Un écouteur vous permet de consigner l'état dans lequel se trouve le lecteur.

Rester à l'écoute

  1. Créez une constante TAG en dehors de la classe PlayerActivity, que vous utiliserez par la suite pour la journalisation.

PlayerActivity.kt

private const val TAG = "PlayerActivity"
  1. Implémentez l'interface Player.EventListener dans une fonction de fabrique en dehors de la classe PlayerActivity. Elle servira à vous signaler les événements importants du lecteur, y compris les erreurs et ses changements d'état.
  2. Ignorez onPlaybackStateChanged en ajoutant le code suivant :

PlayerActivity.kt

private fun playbackStateListener() = object : Player.EventListener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        val stateString: String = when (playbackState) {
            ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE      -"
            ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
            ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY     -"
            ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED     -"
            else -> "UNKNOWN_STATE             -"
        }
        Log.d(TAG, "changed state to $stateString")
    }
}
  1. Déclarez un membre privé de type Player.EventListener dans PlayerActivity.

PlayerActivity.kt

class PlayerActivity : AppCompatActivity() {
    [...]

    private val playbackStateListener: Player.EventListener = playbackStateListener()
}

onPlaybackStateChanged est appelé lorsque l'état de lecture change. Le nouvel état est donné par le paramètre playbackState.

Les états du lecteur sont au nombre de quatre :

État

Description

ExoPlayer.STATE_IDLE

Le lecteur a été instancié, mais n'a pas encore été préparé.

ExoPlayer.STATE_BUFFERING

Le lecteur ne peut pas lire de fichiers depuis la position actuelle, car la quantité de données en mémoire tampon n'est pas suffisante.

ExoPlayer.STATE_READY

Le lecteur peut démarrer immédiatement la lecture depuis la position actuelle. Cela signifie qu'il commencera à lire le fichier multimédia automatiquement si la valeur de la propriété playWhenReady est définie sur true. Si cette valeur est définie sur false, le lecteur est mis en pause.

ExoPlayer.STATE_ENDED

Le lecteur a terminé de lire le fichier multimédia.

Enregistrer votre écouteur

Pour que vos rappels soient appelés, vous devez enregistrer playbackStateListener auprès du lecteur, dans initializePlayer.

  1. Enregistrez l'écouteur avant que la lecture ne soit préparée.

PlayerActivity.kt

private void initializePlayer() {
    [...]
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.addListener(playbackStateListener)
    [...]
}

Là aussi, vous devez nettoyer le code pour éviter que des références du lecteur restent présentes et provoquent une fuite de mémoire.

  1. Supprimez l'écouteur de releasePlayer :

PlayerActivity.kt

private void releasePlayer() {
 player?.run {
   [...]
   removeListener(playbackStateListener)
   release()
 }
  player = null
}
  1. Ouvrez logcat et exécutez l'application.
  2. Utilisez les commandes d'interface utilisateur pour rechercher, mettre en pause et reprendre la lecture. Vous devriez voir l'état de la lecture changer dans les journaux.

Aller plus loin

ExoPlayer propose d'autres écouteurs pratiques pour comprendre l'expérience de lecture de l'utilisateur. Il existe des écouteurs pour l'audio et la vidéo, ainsi qu'un écouteur AnalyticsListener, qui contient les rappels de tous les écouteurs. Voici quelques-unes des méthodes les plus importantes :

  • La méthode onRenderedFirstFrame est appelée lorsque le premier frame d'une vidéo est rendu. Vous pouvez alors calculer combien de temps l'utilisateur a dû attendre pour qu'un contenu satisfaisant s'affiche à l'écran.
  • La méthode onDroppedVideoFrames est appelée lorsque des frames vidéo ont été perdus. Une perte de frames indique que la lecture est mauvaise et que l'expérience utilisateur est probablement médiocre.
  • La méthode onAudioUnderrun est appelée si la mémoire tampon audio est sous-utilisée. Une telle sous-utilisation peut être à l'origine de problèmes audio plus perceptibles que la perte de frames.

AnalyticsListener peut être ajouté à player avec addAnalyticsListener. Il existe également des méthodes équivalentes pour les écouteurs audio et vidéo.

Déterminez les événements importants pour votre application et vos utilisateurs. Pour en savoir plus, consultez Écouter des événements du lecteur. C'est tout pour les lecteurs d'événements !

7. Personnaliser l'interface utilisateur

Jusqu'à présent, vous avez utilisé la fonction PlayerControlView d'ExoPlayer pour présenter une barre de lecture à l'utilisateur.

bcfe17eebcad9e13.png

Capture d'écran : barre de lecture par défaut

Que faire si vous voulez modifier la fonctionnalité ou l'aspect de ces commandes ? Heureusement, ces commandes sont ultra-personnalisables.

La première personnalisation simple consiste à ne pas du tout utiliser la barre. Pour cela, il suffit d'utiliser l'attribut use_controller sur l'élément PlayerView dans activity_player.xml.

  1. Définissez use_controller sur false. La barre ne s'affiche plus :

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   [...]
   app:use_controller="false"/>
  1. Ajoutez l'espace de noms suivant à FrameLayout :

activity_player.xml

<FrameLayout
  [...]
  xmlns:app="http://schemas.android.com/apk/res-auto">

Essayez.

Personnaliser le comportement

PlayerControlView possède plusieurs attributs qui influent sur son comportement. Par exemple, vous pouvez utiliser show_timeout pour personnaliser le délai en millisecondes entre la dernière interaction de l'utilisateur avec la barre et le masquage de celle-ci. Pour ce faire, procédez comme suit :

  1. Supprimez app:use_controller="false".
  2. Remplacez la vue du lecteur par show_timeout :

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:show_timeout="10000"/>

Les attributs de PlayerControlView peuvent également être définis par programmatique.

Personnaliser l'aspect

C'est un bon début, mais comment faire pour changer l'aspect de PlayerControlView ou les boutons affichés ? L'implémentation de PlayerControlView ne suppose pas l'existence de boutons. Il est donc facile d'en supprimer et d'en ajouter.

Voyons comment vous pouvez personnaliser PlayerControlView.

  1. Créez un fichier de mise en page custom_player_control_view.xml dans le dossier player-lib/res/layout/.
  2. Dans le menu contextuel du dossier de mise en page, sélectionnez New > Layout resource file (Nouveau > Fichier des ressources de mise en page), puis nommez-le custom_player_control_view.xml.

ae1e3795726d4e4e.png

Capture d'écran : le fichier de mise en page pour la vue des commandes du lecteur a été créé.

  1. Copiez le fichier de mise en page d'origine depuis cet emplacement dans custom_player_control_view.xml.
  2. Supprimez les éléments ImageButton associés aux ID @id/exo_prev et @id/exo_next.

Pour utiliser votre mise en page personnalisée, vous devez définir l'attribut app:controller_layout_id de l'élément PlayerView dans le fichier activity_player.xml.

  1. Utilisez l'ID de mise en page de votre fichier personnalisé comme dans l'extrait de code suivant :

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:controller_layout_id="@layout/custom_player_control_view"/>
  1. Redémarrez l'application. La vue des commandes du lecteur ne contient plus les boutons Précédent et Suivant.

89e6535a22c8e321.png

Capture d'écran : vue personnalisée des commandes du lecteur, sans les boutons Précédent et Suivant

Vous pouvez appliquer les modifications que vous voulez dans le fichier de mise en page. Par défaut, les couleurs du thème Android sont sélectionnées. Vous pouvez les ignorer et choisir des couleurs adaptées au design de votre application.

  1. Ajoutez un attribut android:tint à chaque élément ImageButton :

custom_player_control_view.xml

<ImageButton android:id="@id/exo_rew"
   android:tint="#FF00A6FF"
   style="@style/ExoMediaButton.Rewind"/>
  1. Définissez la même couleur (#FF00A6FF) pour tous les attributs android:textColor que vous trouvez dans votre fichier personnalisé.

custom_player_control_view.xml

<TextView android:id="@id/exo_position"
   [...]
   android:textColor="#FF00A6FF"/>
<TextView android:id="@id/exo_duration"
   [...]
   android:textColor="#FF00A6FF"/>
  1. Exécutez l'application. Vous avez à présent de beaux éléments d'UI colorés !

e9835d65d6dd0634.png

Capture d'écran : boutons et affichage de texte colorés

Ignorer le style par défaut

Vous venez de créer un fichier de mise en page personnalisé et de le référencer à l'aide de controller_layout_id dans activity_player.xml.

Une autre approche consiste à ignorer le fichier de mise en page par défaut qu'utilise PlayerControlView. Le code source de PlayerControlView nous indique qu'il se sert de R.layout.exo_player_control_view pour la mise en page. Si vous créez votre propre fichier de mise en page avec le même nom, PlayerControlView utilise votre fichier à la place.

  1. Supprimez l'attribut controller_layout_id que vous venez d'ajouter.
  2. Supprimez le fichier custom_player_control_view.xml.

Dans activity_player.xml, PlayerView devrait désormais se présenter comme ceci :

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>
  1. Créez un fichier nommé exo_player_control_view.xml dans le dossier res/layout de votre module de bibliothèque player-lib.
  2. Insérez le code suivant dans exo_player_control_view.xml pour ajouter un bouton de lecture, un bouton de pause et un élément ImageView avec un logo :

exo_player**_control_view.xml**

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_gravity="bottom"
   android:layoutDirection="ltr"
   android:background="#CC000000"
   android:orientation="vertical">

 <LinearLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:paddingTop="4dp"
   android:orientation="horizontal">

   <ImageButton android:id="@id/exo_play"
      style="@style/ExoMediaButton.Play"/>

   <ImageButton android:id="@id/exo_pause"
      style="@style/ExoMediaButton.Pause"/>

 </LinearLayout>

 <ImageView
     android:contentDescription="@string/logo"
     android:src="@drawable/google_logo"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"/>

</LinearLayout>

Voilà comment vous pouvez ajouter vos propres éléments et les mélanger aux éléments de commande standards. ExoPlayerView utilise à présent vos commandes personnalisées, et toute la logique pour masquer et afficher les commandes lorsque l'utilisateur interagit avec celles-ci est préservée.

8. Félicitations

Félicitations ! Vous avez appris beaucoup de choses concernant l'intégration d'ExoPlayer dans votre application.

En savoir plus

Pour en savoir plus sur ExoPlayer, consultez le guide du développeur et le code source, et abonnez-vous au blog ExoPlayer.