diff --git a/_tutorials/fr/2019-08-30-kotlin-exoplayer/index.md b/_tutorials/fr/2019-08-30-kotlin-exoplayer/index.md new file mode 100644 index 000000000..2f4f24b7b --- /dev/null +++ b/_tutorials/fr/2019-08-30-kotlin-exoplayer/index.md @@ -0,0 +1,22 @@ +--- +contentType: tutorial +lang: fr +date: '2019-08-30' +slug: kotlin-exoplayer +title: Créer votre player Netflix avec Exoplayer +excerpt: >- + Dans ce tutoriel nous allons créer un player android en Kotlin grâce à Exoplayer, pour pouvoir lire une vidéo locale mp4. +categories: + - architecture +authors: + - babas +keywords: + - android + - kotlin +steps: + - introduction + - creation-du-player + - creation-ui + - gestion-des-etats +--- + diff --git a/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/creation-du-player.md b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/creation-du-player.md new file mode 100644 index 000000000..7d45eee5d --- /dev/null +++ b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/creation-du-player.md @@ -0,0 +1,116 @@ +--- +contentType: tutorial-step +tutorial: kotlin-exoplayer +slug: creation-du-player +title: Création du Player +--- +## Création du player Exoplayer : + +### Prérequis : + +Créez un fichier versions.gradle dans le répertoire de votre projet et listez vos dépendances en spécifiant leur version. N'oubliez pas d'y introduire la liste des versions que vous utiliserez dans vos dépendances. +Ici : + +```java +ext { + exoPlayerVersion = '2.9.6' +} +``` + +Dans votre fichier build.gradle applicatif ajoutez en haut : + +apply from: 'versions.gradle' + +Enfin, ajoutez dans votre fichier build.gradle les dépendances à ExoPlayer : + +```java + // exoplayer + implementation "com.google.android.exoplayer:exoplayer-core:$exoPlayerVersion" + implementation "com.google.android.exoplayer:exoplayer-ui:$exoPlayerVersion" +``` + +### En ce qui concerne le Player + +Nous allons créer une classe Player. Cette classe va avoir un rôle de façade sur notre player ExoPlayer pour exécuter toutes nos actions type play, pause, instancier notre player, le configurer en fonction du flux d'entrée... + +Pour commencer nous allons créer une variable privé de type SimpleExoPlayer et l'initialiser dans notre objet : + +```java +class Player(val context: Context) { + + private var player: SimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context) + +} +``` + +À partir d'ici, nous avons notre player instancié. Maintenant il faut lui permettre de lire des vidéos locales. Nous allons donc créer une méthode qui va lui permettre de décoder notre fichier video. ExoPlayer est capable de lire un nombre assez conséquent de formats vidéos différents, que ce soient des vidéos locales ou des streams live. Pour différencier ces types de flux d'entrées nous devons créer un fichier de type MediaSource, qui est le media final que nous passerons à notre player ExoPlayer pour qu'il le lise. Pour créer ce fichier, nous devons passer par une des factories ExoPlayer, qui différera en fonction du flux. À savoir qu'en entrée, la factory va demander une URI. + +Ici j'ai choisi de lire un fichier MP4, format considéré comme standard par exoplayer : + +```java + private fun buildMediaSource(uri: Uri): MediaSource { + val factorymediaSourceFactory: ExtractorMediaSource.Factory = + ExtractorMediaSource.Factory( + DefaultDataSourceFactory( + context, + Util.getUserAgent(context, "MyNetflix") + ) + ) + return factorymediaSourceFactory.createMediaSource(uri) + } +``` + +Nous avons notre player, notre MediaSource résultant de la transformation de notre URI. Mais il nous manque un dernier détail... Pour pouvoir s'afficher correctement, ExoPlayer a besoin qu'on lui fournisse une SurfaceView, custom view de la librairie Exoplayer sur laquelle il va pouvoir afficher son contenu. + +Nous allons donc pour l'instant ajouter une méthode pour fournir à notre objet Player une surfaceView : + +```java + fun setVideoSurface(surfaceview: SurfaceView) { + player.setVideoSurfaceView(surfaceview) + } +``` + +Enfin bien sûr, nous allons ajouter plusieurs actions, pour pouvoir tester notre super player par la suite. + +Pour charger notre média : + +```java + fun prepare(uri: Uri) { + player.prepare(buildMediaSource(uri), false, true); + } +``` + +Pour reset notre player : + +```java + fun stop() { + player.stop(true) + player.seekToDefaultPosition() + } +``` + +Pour lancer la lecture : + +```java + fun play() { + player.playWhenReady = true + } +``` + +Pour le mettre en pause : + +```java + fun pause() { + player.playWhenReady = false + } +``` + +Et un getter sur son état en lecture ou pas : + +```java + fun isPlaying() { + return player.playWhenReady + } +``` + +À savoir que pour indiquer que l'on veut jouer la vidéo de notre player, il faut modifier la valeur du boolean playWhenReady. ExoPlayer écoute cette valeur. Dès qu'elle est modifiée et s'il n'est pas en cours d'initialisation il lancera instantanément la vidéo. Sinon il attendra de finir son initialisation puis la lancera. diff --git a/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/creation-ui.md b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/creation-ui.md new file mode 100644 index 000000000..2b4985edf --- /dev/null +++ b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/creation-ui.md @@ -0,0 +1,122 @@ +--- +contentType: tutorial-step +tutorial: kotlin-exoplayer +slug: creation-ui +title: Création de l'UI +--- + +## Création de notre vue PlayerView : + +Créez un fichier player_view.xml dans le dossier layout de votre projet. +Dans ce fichier nous allons ajouter une SurfaceView, réceptacle de notre player, et un bouton play/pause qui permettra de lancer/arrêter le flux : + +```xml + + + + + + + + +``` +Ensuite nous allons créer une classe PlayerView qui héritera de FrameLayout, dans laquelle on va définir différents comportements, notamment la gestion de l'état du bouton play/pause, son logo, et les actions que ce bouton va effectuer. + +```java +class PlayerView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + init { + inflate(context, R.layout.player_view, this) + } +} +``` + +On va y initialiser notre PlayerManager : + +```java + private lateinit var playerManager: PlayerManager + + private fun initPlayer() { + playerManager = PlayerManager(context = context) + playerManager.setVideoSurface(surface_view) + playerManager.prepare(Uri.parse("file:/android_asset/example_video.mp4")) + } +``` + +Et dans notre bloc init, on va exécuter notre initPlayer puis définir un listener sur notre bouton pour modifier l'icône et ainsi nous permettre d'avoir un retour UX entre nos lectures et nos pauses : + +À savoir que des ressources sont mises à disposition par la librairie pour avoir des images de contrôles ISO sur tout les players, les ressources exo_controls_play et exo_controls_pause sont donc accessibles sans avoir à ajouter d'icônes dans votre projet. + +```java +init { + inflate(context, R.layout.player_view, this) + + play_pause_button.setOnClickListener { + if (playerManager.isPlaying()) { + playerManager.pause() + play_pause_button.setBackgroundResource(R.drawable.exo_controls_play) + } else { + playerManager.play() + play_pause_button.setBackgroundResource(R.drawable.exo_controls_pause) + } + } + initPlayer() + } +``` + +### Ressources : + +Niveau ressourcee, créez un dossier assets dans votre dossier main à coté des dossiers res et java et mettez y vos ressources vidéos. +Ainsi vous pourrez y accéder dans vos tests comme défini plus haut : + +"file:/android_asset/example_video.mp4" + +### Tests : + +Afin de tester, créer une MainActivity. Dans son layout activity_main définissez un PlayerView comme bon vous semble niveau format : + +```xml + + + + + + +``` + +Si tout s'aligne bien, vous pouvez exécuter votre code et lancer votre application ! diff --git a/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/gestion-des-etats.md b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/gestion-des-etats.md new file mode 100644 index 000000000..28bdeaa73 --- /dev/null +++ b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/gestion-des-etats.md @@ -0,0 +1,166 @@ +--- +contentType: tutorial-step +tutorial: kotlin-exoplayer +slug: gestion-des-etats +title: Gestion des états +--- +## Allons plus loin + +Dans les deux premières étapes nous avons été plutôt "straightforward" pour avoir un player qui tourne bien. +Il s'agirait maintenant de l'étoffer un peu, et d'utiliser plus d'outils de la librairie. + +Retournons sur notre class PlayerManager et ajoutons un listener. La librairie ExoPlayer met à disposition un listener, Player.EventListener, pour pouvoir écouter et être notifié de tout changement de configuration ou d'état du player. + +Nous allons ici nous intéresser plus particulièrement à deux des méthodes de ce listener : onPlayerError et onPlayerStateChanged, qui notifient en cas d'erreur du player et en cas de changement d'état. Ces informations nous allons les remonter à notre Custom View par le biais d'un listener que nous allons créer comme ceci : + +```java +interface PlayerListener { + fun onError() + fun onStateChanged(playerState: PlayerState) +} +``` + +Puis nous allons créer une sealed class PlayerState qui comportera tous les différents états de notre player. L'utilisation d'une sealed class est plus appropriée ici car elle marche très bien avec l'utilisation du when en kotlin :) Si jamais vous ne connaissez pas les sealed class, il s'agit d'une interface dont les objets pouvant en hériter sont limités aux objets définis dans son fichier, ça nous permet d'avoir une liste d'objets finie et maîtrisée, ces derniers étant réunis en un même fichier. + +Pour en savoir un peu plus sur les sealed class je vous laisse avec la documentation officielle : +[Sealed class](https://kotlinlang.org/docs/reference/sealed-classes.html) + + +Concernant les états, si l'on regarde du coté d'exoplayer on a : + +```java + /** + * The player does not have any media to play. + */ + int STATE_IDLE = 1; + /** + * The player is not able to immediately play from its current position. This state typically + * occurs when more data needs to be loaded. + */ + int STATE_BUFFERING = 2; + /** + * The player is able to immediately play from its current position. The player will be playing if + * {@link #getPlayWhenReady()} is true, and paused otherwise. + */ + int STATE_READY = 3; + /** + * The player has finished playing the media. + */ + int STATE_ENDED = 4; +``` + +Nous allons donc créer 5 états, IDLE, BUFFERING, PLAYING, PAUSED, ENDED. + +```java +sealed class PlayerState { + object IDLE : PlayerState() + object BUFFERING : PlayerState() + object PLAYING : PlayerState() + object PAUSED : PlayerState() + object ENDED : PlayerState() +} +``` + +Maintenant que nous avons nos objets communiquants, nous allons faire la connexion : + +On va implémenter Player.EventListener en ajoutant en paramètre d'entrée notre nouveau listener puis en nous enregistrant sur le player Exoplayer : + +```java +class Player(val context: Context, listener: PlayerListener) : Player.EventListener { + + private var player: SimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context) + + init { + player.addListener(this) + } +``` + +Ensuite nous allons override la méthode onPlayerStateChanged pour qu'à chaque état changeant elle puisse transmettre l'information par le biais de notre listener : + +```java + override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { + when (playbackState) { + Player.STATE_IDLE -> listener.onStateChanged(PlayerState.IDLE) + Player.STATE_BUFFERING -> listener.onStateChanged(PlayerState.BUFFERING) + Player.STATE_READY -> if (playWhenReady) { + listener.onStateChanged(PlayerState.PLAYING) + } else { + listener.onStateChanged(PlayerState.PAUSED) + } + Player.STATE_ENDED -> listener.onStateChanged(PlayerState.ENDED) + } + } +``` + +Ensuite nous allons faire de même pour la méthode onPlayerError : + +```java + override fun onPlayerError(error: ExoPlaybackException?) { + listener.onError() + } +``` +Ici nous n'entrons pas dans le détail des types d'erreurs et l'adaptation du message qui irait avec. Dans le cadre de notre tutoriel nous allons juste remonter qu'il y a un souci et rendre visible l'erreur. + +### Côté UI : + +Retournons sur notre PlayerView. Désormais elle va implémenter notre nouveau listener `PlayerListener` et réagir à ces changements. Je vous laisse aussi le soin d'ajouter à la création du PlayerManager notre listener. + +Occupons-nous de nos nouvelles méthodes, onStateChanged et onError. Nous allons ajouter un peu de composants UI pour nous aider à mieux visualiser nos états ! + +Dans votre fichier player_view.xml ajoutez : + +```xml + + + +``` + +Ici nous avons une progressBar qui indiquera le buffering et un textView pour indiquer une erreur. + +Dans notre fichier PlayerView, nous pouvons donc remplir nos deux méthodes : + +```java + override fun onError() { + error_message.text = "Une erreur technique est survenue" + error_message.visibility = View.VISIBLE + } + + override fun onStateChanged(playerState: PlayerState) { + when (playerState) { + is PlayerState.IDLE -> play_pause_button.visibility = View.VISIBLE + is PlayerState.BUFFERING -> { + play_pause_button.visibility = GONE + spinner.visibility = View.VISIBLE + } + is PlayerState.PLAYING -> { + spinner.visibility = GONE + play_pause_button.setBackgroundResource(R.drawable.exo_controls_pause) + play_pause_button.visibility = View.VISIBLE + } + is PlayerState.PAUSED -> { + spinner.visibility = GONE + play_pause_button.setBackgroundResource(R.drawable.exo_controls_play) + play_pause_button.visibility = View.VISIBLE + } + } + } +``` +Le code est un peu barbare, ici il est suffisant au vu du peu de composants impactés par les changements d'états. +Vous pouvez supprimer le changement d'icône dans le onClickListener du play_plause_button et lancer votre application ! + + +Voila pour ce tutoriel, j'espère qu'il vous aura aidé un peu à comprendre comment fonctionne ExoPlayer, et vous aura donné une idée d'approche sur comment construire sa brique player dans son application. Bonne route amis astronautes ! \ No newline at end of file diff --git a/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/introduction.md b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/introduction.md new file mode 100644 index 000000000..742041b53 --- /dev/null +++ b/_tutorials/fr/2019-08-30-kotlin-exoplayer/steps/introduction.md @@ -0,0 +1,29 @@ +--- +contentType: tutorial-step +tutorial: kotlin-exoplayer +slug: introduction +title: Introduction +--- +## Qu'allons-nous faire ? + +Dans ce tutoriel nous allons créer une application qui aura pour but de pouvoir lire nos vidéos locales. Pour ce faire nous utiliserons la librairie ExoPlayer. Le but ici sera d'en prendre en main les différents composants, de créer sa propre UI, et jouer des vidéos locales sur son téléphone ! + +Vous pouvez retrouver toute la documentation sur le site officiel d'ExoPlayer : +- [Exoplayer](https://exoplayer.dev/hello-world.html) + +Voici les étapes que nous allons suivre au cours de ce tutoriel : +- La création de notre Player +- La création de notre PlayerView +- La gestion des changements d'état et des erreurs + +Le code source du projet est disponible sur mon github : +- [Bastien Calone](https://github.com/BastienCalone) + + +## Pré-requis + +Pour les besoins de ce tutoriel il vous faudra : + +- Télécharger Android Studio +- Télécharger les dépendances android API 28 +- Télécharger et activer le plugin Kotlin pour android studio