G9 _ Nouveau décodeur de CANAL +

Faut voir grand dans la vie, quitte à voyager à travers le temps au volant d’une voiture, autant en choisir une qui ait d’la gueule ! Emmett Brown

Pour la petite histoire

En 2010, CANAL+ sortait son décodeur satellite — nom de code G5 — basé sur une interface native faite par un prestataire. On faisait comme ça à “l’époque”.

En 2013 il fut décidé de créer un décodeur OTT/TNT — G6 ou Cube S — en y ajoutant une petite révolution puisque l’interface serait dorénavant une application web embarquée sur un webkit. Le choix technique s’est porté sur backbone.js avec une équipe chargée de son développement en interne.

A l’été 2015 le Cube S s’offrait au monde avec une nouvelle ambition pour CANAL + : porter cette nouvelle interface web sur la G5 mais aussi sur les box des différents FAI (Orange, free, bouygues…) et les télévisions connectées (Samsung…)

Dans le même temps a émergé le projet G9 qui avait pour ambition de proposer un nouveau décodeur satellite (4K, commande vocale, multi-room) offrant une expérience se rapprochant de myCANAL et ainsi unifier les univers.

L’idée était donc d’abandonner progressivement l’expérience utilisateur du Cube S pour celle de myCANAL avec cette même ambition d’être cross platform.

Le choix pour nous, équipe technique, s’est alors posé :

  • Faire fortement évoluer l’application backbone.js
  • Tout recommencer from scratch

Nous avons choisi la deuxième solution 😁 et on va vous raconter comment ça s’est passé !

Archi(cool!)

Notre architecture devait répondre à une problématique de cross-platform.

L’architecture en backbone.js présentée ci-dessous nous posait un problème de duplication de code. Les briques spécifiques aux plateformes se posant au dessus du socle shared, cela demandait une réécriture importante à chaque nouvelle target.

Ancienne architecture

L’idée a donc été d’inverser cette logique et poser les briques spécifiques à chaque plateform, le plus bas possible dans notre architecture. Ainsi la partie unifiée du code serait la plus importante possible.

Sur le schéma ci-dessous nous pouvons voir que l’interface et les modules “haut niveau” n’ont pas connaissance de la plateform et c’est seulement le middleware (MW) qui applique les spécificités techniques de chaque target.

Nouvelle architecture

Ce choix se base donc sur une interface unifiée mais la séparation des briques CORE et FRONT nous donnent la possibilité de jouer aux légos et de brancher une autre interface sur le core si besoin.

Choix des technos

Pour ce faire nous avons donc opté pour la stack suivante:

  • React : Implémentation d’un DOM virtuel.
  • RxJS: Gestion de flux de données asynchrones.
  • Redux: Système de centralisation des données et des actions utilisateurs.
  • React Router: Driving de l’UI en fonction de l’URL
  • Redux Observable: Gestion d’actions asynchrones

Tout d’abord nous avons opté pour la technologie RxJS, grâce à laquelle le “handmade dispatcher” du core a été développé pour pouvoir gérer l’ensemble des évènements asynchrones du décodeur mais également l’état global de notre décodeur.

Pour l’interface, on utilise Redux pour gérer l’état de l’UI et bien entendu React pour la gestion du DOM. Il est intéressant de mentionner que le store Redux est mis à jour de part et d’autre par le store du core lorsque celui-ci émet des changements ou est modifié par les actions de l’utilisateur depuis l’interface.

Pour permettre la communication entre ces deux stores, on a fait le choix d’utiliser Redux Observable afin que le “handmade dispatcher” puisse mettre à jour le store redux en fonction des évènements reçus du back end mais également pour permettre à notre interface d’utiliser les actions du store du dispatcher (demander la lecture d’un nouveau flux ou bien télécharger un contenu VOD).

Pour finir, notre webapp étant composée de nombreux composants et de nombreuses vues, react router fut la solution la mieux adaptée pour pouvoir gérer le plus simplement possible l’ensemble du comportement de l’interface en rendant les composants associés à chaque url, mais également pour gérer la redirection vers des services ou applications externes.

C’est la combinaison de toutes ces technologies qui nous a permis de mettre en place l’architecture la plus adaptée possible à notre produit, en utilisant les avantages et les points forts de chacune d’entre elle.

Organisation de l’équipe

Le développement de l’application web a duré environ 18 mois avant sa première mise en production. Cela peut paraître long mais il n’était pas pensable de sortir un nouveau décodeur ayant moins de fonctionnalités que l’ancien (Zapping, enregistrement, VOD, grille TV…). La partie moins glamour de la primo installation et du paramétrage du décodeur a aussi été une importante charge de travail.

Globalement, il y avait une équipe pour le hardware, une pour le middleware, une équipe chargée de l’UX et une équipe Marketing. Sans oublier notre équipe webapp composée d’une dizaine de personnes.

Dans un premier temps, nous avons opté pour la méthode Scrum au sein d’un groupe (CANAL +). Cependant, travaillant avec plusieurs équipes qui ont leur propres cycles de dev/delivery, il a fallu faire preuve d’adaptation et de pragmatisme, pour trouver un équilibre sur l’ensemble du projet. Ainsi en ajustant nos méthodes à chaque sprint, nous avons fini par trouver le juste équilibre.

Nous avons quand même réussi à mettre en place des cérémonies : daily, sprint planning, rétro… afin de mieux comprendre le travail de chacun et de vite réagir aux difficultées.

La démo a été un point très important pour nous puisque ça a été notre principale porte d’entrée pour pas mal de personnes gravitant autour du projet. Nous avons donc mis beaucoup d’efforts à rendre cette démo intéressante et interactive tout en faisant une démonstration et une promotion de nos méthodologies aux autres services.

Nous avons commencé par estimer en point chaque feature au début du projet en affinant le chiffrage des user story prises à chaque sprint planning. Mais au fur et à mesure de nos expériences et devant le temps que cela prenait (versus le gain apporté pour le management) nous avons décidé de faire du no estimate. Une décision pouvant être remise en cause en fonction des besoins.

Notre cycle de livraison a évolué au long des 18 mois. Nous avons commencé par des sprints de 3 semaines, puis 2 semaines avant de faire des livraisons chaque semaine et chaque jour juste avant la sortie en production. L’idée étant de s’adapter au contexte du projet. Après la mise en production nous avons repris un cycle de 2 semaines pour livrer les features au fil de l’eau.

L’organisation au sein de l’équipe a aussi sans cesse changée. Nous avons commencé par séparer l’équipe en deux : Une partie front et une partie core. Redoutant une spécialisation des compétences, nous avons ensuite séparé l’équipe par features et changeant à chaque début de sprint.

Concernant les bugfix, nous avons pris le parti de ne pas créer d’équipe mais de définir à chaque daily les tickets à traiter et de décider ensemble quelles personnes seraient les plus apte à les prendre.

Concernant nos outils, ils sont assez standards :

  • Github pour la codebase
  • Confluence pour la gestion de la documentation technique et user story
  • Jira pour le ticketing

Nous avons voulu le plus possible nous baser sur Github pour comprendre l’avancée de chaque développeur et le status de chaque ticket.

Grâce à Github le système de code review permet de mettre en place une règle de deux approve avant la soumission à validation matérialisée par le label to valid. Nous utilisons les labels pour nous donner des informations sur la nature de la Pull Request mais aussi son statut dans le workflow (WIP, To Valid, Validated).

La validation se fait par des personnes (entre deux et trois) au sein de l’équipe et dédiées à cette tâche. Nous avons une bonne couverture de tests unitaires mais pas de tests end-to-end. En effet, étant donnée que nous travaillons sur un environnement évoluant constamment (avec la notion de “live”), il est difficile de mettre en place un mocking qui garantisse que chaque test est reproduit à l’identique à chaque fois que ces derniers sont exécutés. Ainsi pour l’heure nous faisons la non-reg manuellement, mais parallèlement nous commençons à nous pencher sur le problème et déjà une solution semble envisageable.

Nous avons aussi une intégration continue avec Jenkins qui nous alerte si nous cassons le build et valide chaque pull request. Nous avons aussi un job qui nous permet automatiquement de créer le livrable de production en fin de sprint !

Pour garder une trace des développements nous utilisons les Milestone et éditons une release note à chaque fin de sprint.

Problèmes & solutions

Carousel

Performance : La webapp repose sur un Chrome 52 embarqué qui est très limité en charge de CPU. C’est pour cela, qu’il a fallu être exigeant sur l’utilisation du DOM et surtout sur les “render” inutiles qui pourraient être engendrés par le lifecycle de React. Mais également sur le chargement des données lors de la navigation de l’utilisateur à travers différentes strates, ou encore pour les carousels fortement présents dans notre interface. Ainsi, nous avons limité le nombre de données à pré-charger, veillé à ne pas garder dans le DOM des éléments qui ne sont plus visibles à l’utilisateur ou encore éviter le render de l’ensemble des éléments d’un carousel lorsque l’utilisateur les fait défiler.

Gestion des dépendances circulaires: Notre interface étant composée de nombreuses fonctionnalités, cela engendre l’utilisation de plusieurs reducers afin de pouvoir gérer au mieux l’état de notre interface. Typiquement, la gestion du disque dur, les informations de toutes les chaînes ainsi que l’update de leurs programmes, le scanning fréquentiel, l’appairage de la télévision, le progressive download, le code parent et beaucoup d’autres encore.

Cependant très vite, nous nous sommes retrouvés face à des problèmes de dépendances circulaires. En effet par exemple, nous avons besoin d’importer des selectors du reducer du disque dur au niveau du reducer du progressive download (les informations sur l’espace disque étant nécessaire pour poser un enregistrement). Il se peut aussi qu’une action d’un reducer soit appelée dans un autre reducer pour permettre une mise à jour du state. Pour palier à ce problème nous avons donc décidé d’utiliser le plugin suivant: circular-dependency-plugin afin d’être conscient des dépendances dans notre code et pouvoir les régler au fur et à mesure.

Futur 🚀

Séparation Core Front & Primo: Afin de faciliter le multi-target, et surtout les livraisons pour les mises en production. Nous avons pris la décision de séparer le Core et le Front dans des repositories différents. Ainsi il sera beaucoup plus facile de “pluger” une nouvelle interface sur le front.

De plus, pour rendre très robuste notre primo (installation du décodeur), nous allons externaliser ce fonctionnel également dans un nouveau repository afin d’assurer que tous les futurs développements n’impacteront pas cette partie qui est primordiale pour pallier aux problèmes de mise en production. La primo est le point d’entré lorsqu’un utilisateur va acquérir un décodeur. En effet, l’ensemble des fonctionnalités du décodeur est initialisé à cet instant (update, apparaige de la télécommande, scanning, acquisition des droits). Si une des étapes de la primo est défectueuse, l’utilisateur peut se retrouver dans l’impossibilité d’utiliser son décodeur.

Ainsi à chaque développement, il est nécessaire de valider et de tester de nouveau l’ensemble de la primo, ce qui est coûteux.

CANAL + international: Nous avons pour nouveau but de mettre le décodeur à disposition des DOM-TOM, pour cela il va falloir être capable de gérer notre front en fonction de la target. En effet, le but est de pouvoir effectuer simplement quelques petites modifications de certaines vues de l’interface pour les DOM-TOM et de ne pas impacter les vues qui sont communes. Ainsi il va nous falloir travailler sur le fait de pouvoir généraliser encore plus nos composants mais aussi nos vues ainsi que la logique de certains reducers afin d’éviter de devoir utiliser des conditions en fonction de la target à plusieurs niveaux dans le code. Pour illustrer le problème, par exemple pour le scanning fréquentiel, les DOM-TOM requièrent une étape en plus ce qui engendre un affichage légèrement différent à certains endroits.

Timeline indexée: Ajouter le vignettage sur notre player va rendre notre interface encore plus moderne aux yeux de l’utilisateur.

Rx-Player: Comme prochain objectif nous voudrions intégrer le Rx-Player, c’est un player open source développé par des développeurs de CANAL +, qui va nous permettre de lire des flux DASH et SMOOTH ce qui n’est actuellement pas possible avec le player que nous utilisons en ce moment sur les décodeurs. ⭐ GitHub Rx-Player⭐️

Commande vocale: Et pour finir, la mise en place de la commande vocale via la télécommande afin de pouvoir piloter son décodeur à distance, par exemple demander de zapper sur une chaîne ou bien encore de lancer la recherche d’un film. Mais le choix de l’API reste encore à définir…

Et maintenant …

Nous travaillons beaucoup sur la stabilité du produit suite à cette récente mise en production et commençons les chantiers de travail pour les futurs features.

La première étape du projet est atteinte, mais le chemin vers un produit à la hauteur des attentes de l’utilisateur est encore long !


Le nouveau décodeur est d’ores et déjà disponible !

Contributeurs au projet : Sylvain Bekaert, Augustin Roux, Paul Berberian, Florian Vautier, Florent DUVEAU, Mathilde Canesse, Julien Bernadet.