Media3 fournit un PlayerView
par défaut qui propose certaines options de personnalisation.
Ignorer les drawables
PlayerView
utilise PlayerControlView
pour afficher les commandes de lecture et la barre de progression. Les drawables utilisés par PlayerControlView
peuvent être remplacés par des drawables portant les mêmes noms définis dans votre application. Consultez la documentation sur PlayerControlView
pour obtenir la liste des drawables de contrôle pouvant être remplacés.
Pour toute personnalisation supplémentaire, les développeurs d'applications doivent implémenter leurs propres composants d'interface utilisateur. Toutefois, voici quelques bonnes pratiques qui peuvent vous aider à vous lancer.
Bonnes pratiques
Lorsque vous implémentez une UI multimédia qui se connecte à un Player
Media3 (par exemple, ExoPlayer
, MediaController
ou une implémentation Player
personnalisée), nous vous recommandons de suivre ces bonnes pratiques pour une expérience UI optimale.
Bouton Lecture/Pause
Le bouton de lecture et de mise en pause ne correspond pas directement à l'état d'un seul joueur. Par exemple, un utilisateur doit pouvoir redémarrer la lecture une fois qu'elle s'est terminée ou a échoué, même si le lecteur n'est pas mis en pause.
Pour simplifier l'implémentation, Media3 fournit des méthodes utilitaires permettant de choisir le bouton à afficher (Util.shouldShowPlayButton
) et de gérer les pressions sur les boutons (Util.handlePlayPauseButtonAction
):
val shouldShowPlayButton: Boolean = Util.shouldShowPlayButton(player) playPauseButton.setImageDrawable(if (shouldShowPlayButton) playDrawable else pauseDrawable) playPauseButton.setOnClickListener { Util.handlePlayPauseButtonAction(player) }
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player); playPauseButton.setImageDrawable(shouldShowPlayButton ? playDrawable : pauseDrawable); playPauseButton.setOnClickListener(view -> Util.handlePlayPauseButtonAction(player));
Écouter les mises à jour d'état
Le composant d'UI doit ajouter un Player.Listener
pour être informé des modifications d'état qui nécessitent une mise à jour de l'UI correspondante. Pour en savoir plus, consultez Écouter les événements de lecture.
L'actualisation de l'interface utilisateur peut être coûteuse, et plusieurs événements de joueur arrivent souvent en même temps. Pour éviter d'actualiser l'UI trop souvent sur une courte période, il est généralement préférable d'écouter uniquement onEvents
et de déclencher les mises à jour de l'UI à partir de là:
player.addListener(object : Player.Listener{ override fun onEvents(player: Player, events: Player.Events){ if (events.containsAny( Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) { updatePlayPauseButton() } if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) { updateRepeatModeButton() } } })
player.addListener(new Player.Listener() { @Override public void onEvents(Player player, Player.Events events) { if (events.containsAny( Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) { updatePlayPauseButton(); } if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) { updateRepeatModeButton(); } } });
Gérer les commandes disponibles
Un composant d'interface utilisateur à usage général qui peut être compatible avec différentes implémentations Player
doit vérifier les commandes de lecteur disponibles pour afficher ou masquer des boutons et éviter d'appeler des méthodes non compatibles:
nextButton.isEnabled = player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)
nextButton.setEnabled(player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT));
Obturateur du premier frame et affichage de l'image
Lorsqu'un composant d'interface utilisateur affiche une vidéo ou des images, il utilise généralement une vue d'obturateur d'espace réservé jusqu'à ce que le premier frame ou l'image réelle soit disponible. De plus, la lecture mixte de vidéos et d'images nécessite de masquer et d'afficher la vue d'image aux moments appropriés.
Un modèle courant pour gérer ces mises à jour consiste à écouter Player.Listener.onEvents()
pour détecter tout changement dans les pistes sélectionnées (EVENT_TRACKS_CHANGED
) et pour savoir quand le premier frame vidéo a été rendu (EVENT_RENDERED_FIRST_FRAME
), ainsi que ImageOutput.onImageAvailable()
pour savoir quand une nouvelle image est disponible:
override fun onEvents(player: Player, events: Player.Events) { if (events.contains(Player.EVENT_TRACKS_CHANGED)) { // If no video or image track: show shutter, hide image view. // Otherwise: do nothing to wait for first frame or image. } if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) { // Hide shutter, hide image view. } } override fun onImageAvailable(presentationTimeUs: Long, bitmap: Bitmap) { // Show shutter, set image and show image view. }
@Override public void onEvents(Player player, Events events) { if (events.contains(Player.EVENT_TRACKS_CHANGED)) { // If no video or image track: show shutter, hide image view. // Otherwise: do nothing to wait for first frame or image. } if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) { // Hide shutter, hide image view. } } @Override public void onImageAvailable(long presentationTimeUs, Bitmap bitmap) { // Show shutter, set image and show image view. }