Création de l'expérience interactive de Vyclone en HTML5

Création de l'expérience interactive de Vyclone en HTML5

À l'heure où les développeurs commencent à exploiter pleinement le potentiel du HTML5, le Web est de plus en plus séduisant et productif pour les consommateurs. À l'occasion de ce billet, j'ai invité Anton Molleda (de Plain Concepts) à évoquer le processus de développement de Vyclone et les enseignements qu'il a pu en tirer. Vyclone est un site social de montage vidéo qui s'appuie sur le HTML5 et sur bon nombre des nouveautés des navigateurs nouvelle génération tels qu'Internet Explorer 10. Vyclone exploite différentes fonctionnalités (événements de pointeur, gestes multipoints, accélération graphique pour les objets canvas, CSS3, etc.), pour que l'utilisateur ait l'impression d'utiliser une application et non pas un site Web.

— Rob Mauceri, chef de projet, Internet Explorer

Bonjour à tous,

Je m'appelle Anton Molleda et je travaille chez Plain Concepts. Au cours de ces derniers mois, l'équipe Internet Explorer a collaboré avec les fabuleuses équipes de la startup de partage de vidéos Vyclone, promise à un avenir radieux. En tant que développeur Web, j'adore essayer de repousser les limites du Web. J'ai justement eu la chance de participer à ce projet. Aujourd'hui, je souhaite vous faire partager quelques-uns des enseignements tirés de cette collaboration, qui visait à créer un outil de montage video sur le Web pour Vyclone, en utilisant uniquement HTML5 et JavaScript !

Vyclone est une plateforme sociale de vidéos, qui vous permet de créer, de synchroniser et de monter plusieurs points de vue d'un même sujet, le tout sans effort et de façon collaborative.

Au démarrage de Vyclone, la plateforme se concentrait uniquement sur les appareils mobiles. Rapidement, nous avons cependant réalisé que si l'expérience d'enregistrement à partir d'un téléphone est agréable, le montage se révèle plus complexe, en raison de la taille de l'écran et de la puissance de l'appareil. Au vu des progrès réalisés ces dernières années par les navigateurs modernes, HTML5 s'est avéré être la solution idéale pour créer ce nouvel outil.

Le cœur de de l'outil de montage vidéo de Vyclone se compose de trois parties :

Aperçu vidéo : il est possible de visionner une version basse qualité de la séquence que l'utilisateur est en train de réaliser (dans la partie gauche).

Grille de vidéos : toutes les sources disponibles sont présentées à l'utilisateur et indiquent un point et une durée précise (à droite).

Chronologie : elle offre une vue linéaire de la source lue pendant la vidéo. Une source lue pendant une certaine durée est appelée « séquence » (elle s'affiche au-dessus des commandes de lecture)

Outil de montage vidéo Web de Vyclone

Lorsque l'utilisateur lance la lecture de la vidéo et commence à ajouter de nouvelles séquences à la chronologie, l'aperçu vidéo change pour prendre en compte la nouvelle source et la grille de vidéos met en évidence le fichier source (des triangles sont affichés dans les coins), pour permettre à l'utilisateur d'identifier la vidéo sélectionnée.

Pour créer cet outil, nous avons dû résoudre de nombreuses difficultés : nombreuses manipulations vidéo, performances obtenues et expérience utilisateur. Examinons maintenant comment nous avons réussi à mettre en place cette expérience sur le Web. Pour cela, nous avons utilisé les objets video, canvas et requestAnimationFrame (RAF). Une vidéo est lue en arrière-plan et dans chaque objet RAF, nous traçons la source active dans un objet canvas (dans l'aperçu vidéo) ou nous calculons sa nouvelle taille et sa position dans la grille de vidéos.

Jusqu'ici tout va bien. Mais que se passe-t-il lorsque l'utilisateur commence à utiliser l'interface ? Par exemple, que se passe-t-il s'il déplace la position de lecture en avant ou en arrière dans la chronologie ou s'il ajoute ou supprime des sources vidéo (séquences) ? Lorsque nous avons mis au point les premiers prototypes, nous pensions que l'approche standard consisterait à prendre en charge ces manipulations dès que l'événement est déclenché. C'est ce que nous avons tous appris, n'est-ce pas ?

Cependant, que se passe-t-il si ces événements peuvent être déclenchés plusieurs dizaines de fois par seconde, voire plusieurs centaines de fois par seconde ? Et que faire si ces gestionnaires doivent mettre à jour l'interface ? Devons-nous réellement forcer l'actualisation de la disposition 130 fois par seconde lorsque seul un pixel est modifié ? Voilà un sérieux frein en termes de performances !

Si votre ordinateur possède un processeur i7 et 8 Go, vous disposez sans doute d'une puissance de calcul suffisante. Mais qu'en est-il des ordinateurs plus anciens ou des appareils ARM ? Ces utilisateurs ne profiteraient pas de la même expérience utilisateur et le temps de réaction du site Web leur semblerait très long.

Notre première approche a consisté à mettre l'action en file d'attente dans l'objet RAF, mais cette solution présentait d'autres problèmes. Ainsi, le risque était de déclencher le même objet RAF pour le même cycle, ce qui n'aurait fait qu'amplifier le problème. Nous avons donc décidé de mettre en place une variable indiquant si l'action a déjà été mise en file d'attente. Voici un exemple :

 var queued = false; function myAction(){ //your awesome code here queued = false; } function onEvent(evt){ if(!queued){ queued = true; requestAnimationFrame(myAction); } }

Ce code n'est pas mauvais, mais il n'est pas non plus sans problème. Si vous effectuez une opération impliquant la position de l'événement (souris ou pointeur) et un différentiel, vous vous apercevez vite que cette approche est délicate. La solution mise en œuvre dans la chronologie consiste à accumuler la valeur de l'événement et à la traiter via myAction :

 var deltaX = 0, queued = false; function myAction(){ //your awesome code here uses deltaX deltaX = 0; // we reset the deltaX so it can be incremented  // next time onEvent is executed queued = false; } function onEvent(evt){ if(!queued){ queued = true; deltaX = evt.translationX; // in the case of a pointer, if you are  // using a mouse you will have to do some  // magic with pageX or similar :) requestAnimationFrame(myAction); }else{ deltaX += evt.translationX; } }

Avec cette approche, tout était prêt ou presque. Nous avons continué à ajouter des fonctionnalités, mais nous avons constaté l'émergence d'autres problèmes.

En traitant ces événements au moment opportun pour chaque requestAnimationFrame, nous avons réussi à améliorer la réactivité sans mettre à mal les performances de calcul. Mais comme requestAnimationFrame exécute les fonctions dans l'ordre, elles sont mises en file d'attente. Parfois, le tracé avait lieu avant le nettoyage ou la chronologie était actualisée lorsque cela n'était pas nécessaire. Nous devions en outre créer beaucoup de code complexe pour faire en sorte que tout soit exécuté dans l'ordre.

Nous avons constaté que le code n'était pas très facile à utiliser et que nous perdions des cycles en attendant la réalisation d'autres actions. Nous avons donc décidé de changer une nouvelle fois la façon dont nous traitions l'entrée. C'est à ce moment-là que nous avons décidé d'envisager le processus comme une boucle de jeu. Si vous ne connaissez pas les grands principes de l'architecture des jeux, sachez qu'une boucle de jeu est essentiellement une boucle continue qui est exécutée quelle que soit l'interaction de l'utilisateur et qui se divise en plusieurs parties lorsque des événements et des actions ont lieu. Dans l'article Wikipedia intitulé « Game Programming », une boucle de jeu simplifiée en pseudo-code ressemble à ceci :

 while( user doesn't exit ) check for user input run AI move enemies resolve collisions draw graphics play sounds end while

C'est exactement ce dont nous avions besoin. En exploitant l'objet RAF, nous avons créé une fonction de cycle qui est exécutée en continu. Dans cette fonction, nous décidons ce que nous devons faire en fonction de l'entrée précédente de l'utilisateur ou d'autres facteurs.

Le cycle simplifié de la grille de vidéos ressemble à ceci :

 function tick(){ //we clean if we've changed the size of the quadrant if(needsClean){ cleanCanvas(); } // if we have to change the quadrant's frame because we are  // the active one (or the opposite) if(newFrame){ drawFrame(); // we draw just the frame in a separate canvas so it // doesn't need to be calculated all the time, and it  // is still faster than copying from an image } //we draw the new frame if we are playing or seeking if(dirty){ draw(); drawFrameInQuadrant(); } requestAnimationFrame(tick); }

Les valeurs de needsClean, newFrame et dirty sont mises à jour sur les gestionnaires d'événements (lorsque l'utilisateur recherche une image, lors de la lecture de la vidéo, etc.).

En envisageant ainsi les interactions utilisateur sous un nouvel angle et en optant pour un mécanisme basé sur celui d'une boucle de jeu, nous avons pu améliorer les performances et simplifier notre code dans l'outil de montage.

Voici la grande leçon à retenir de tout cela : si vous développez du code qui nécessite beaucoup d'interactivité et qui reçoit de nombreuses entrées utilisateur, la boucle de jeu peut potentiellement vous simplifier la vie ! Dans notre cas, ce fut indéniable. Si vous n'avez pas encore eu la chance d'essayer le superbe (c'est le moins que l'on puisse dire) outil de montage Web de Vyclone, essayez-le ! Cliquez sur « Remix » dans n'importe quelle vidéo de Vyclone.com : vous découvrirez ainsi notre outil de montage Web. Il fonctionne aussi bien avec une souris qu'avec une interface tactile. Je vous recommande vivement de l'essayer avec une Surface Pro !

Amusez-vous bien ! N'hésitez pas à laisser vos commentaires ci-dessous si vous avez des questions.

— Anton Molleda, Plain Concepts