Cela fait longtemps que j’y pense, et il aura fallu qu’on en discute une nouvelle fois ce matin pour finalement le réaliser : La vidéo est de plus en plus utilisée sur les sites web, mais pour une expérience optimale, il est toujours préférable d’envoyer la bonne qualité à vos clients. Vous n’avez pas la même bande passante sur un iphone en 3G et sur votre connexion internet en fibre à la maison ! Mais comment parvenir à faire comme youtube et afficher dynamiquement la bonne qualité de vidéo ?
le problème
Le concept est simple, et la réalisation le sera tout autant. Commençons par la base : La qualité d’une vidéo que l’on diffuse, pour un codec donné, est directement liée à son poids.
Le but du jeu étant que le débit de la bande passante du spectateur de votre vidéo soit plus grande ou égale à celle requise par votre vidéo.
Pierre Boureau l’explique bien mieux que moi.
Voici un tableau pour vous faire une idée des débits suivant les connexions de vos clients.
Aujourd’hui ce principe est maîtrisé et plusieurs qualités de vidéos sont créées pour une même vidéo à diffuser. Mais l’on se contente trop souvent de détecter grossièrement le type de device utilisé et de proposer la vidéo qui va bien… Pourquoi ne pas faire comme youtube et détecter la bande passante de la connexion et se régler dynamiquement ?
C’est ce que nous allons faire.
La solution
Le principe
Pour calculer la bande passante, nous allons prendre un fichier de taille raisonnable (500ko par exemple), le downloader, et calculer le temps que cela à pris. De cette mesure, nous en déduisons le débit disponible (en kb/s). Bien entendu, le calcul peut grandement varier suivant ce qui se passe sur la connexion. Aussi, il vous sera loisible de réaliser plusieurs fois le test et d’en déduire une moyenne.
La réalisation
Pour que les tests de download ne bloquent ni ne ralentissent les nombreux scripts js et animations que vous exécutez sur le client, nous allons embarquer la fonctionnalité de détection de bande passante dans un webworker.
Un webworker, c’est tout simplement un espace d’exécution séparé de l’espace d’exécution principal, celui dans lequel votre affichage se produit. Nous créons un thread séparé qui n’aura AUCUN impact sur le principal. Et ça, c’est bien.
Voici comment instancier un webworker dans votre code :
var worker = new Worker("detectbandwidth.js"); worker.onmessage = workerResultReceiver; worker.onerror = workerErrorReceiver; worker.postMessage({ 'testFile' : "http://bandwidth.local/MaisonCausapscal.JPG" }); function workerResultReceiver(e) { console.log(e.data); } function workerErrorReceiver(e) { console.log("A webworker error has occurred " + e); }
A noter que le webworker est instancié avec son js à charger. En paramètre, on lui passe l’url du fichier de test via un « worker.postMessage() ».
Attention : N’oubliez pas, on fait de l’ajax, donc votre fichier de test doit se trouver dans le même domaine que votre appli (toujours la problématique du same-origin policy).
Vous trouvez enfin dans le petit code ci-dessous les fonctions d’écoute du résultat ou de l’erreur émises par votre webworker.
Le webworker fonctionne, lui, de la façon suivante : Il reçoit une url de fichier à utiliser comme fichier de test. Il envoie alors en ajax une requête HTTP de type HEAD (on ne recevra pas le corps en réponse mais juste les en-têtes correspondant à la requête et parmi eux, la taille du fichier de test). On récupère la taille du fichier de test. Bien pratique et très rapide.
Ensuite, un appel ajax pour récupérer le fichier a lieu. Une fois cet appel réalisé, on va pouvoir traiter le résultat obtenu et calculer notamment le débit disponible. Le résultat est renvoyer sous forme de json.
"use strict"; self.addEventListener('message', function (e) { var data, url, start, fileSize; data = e.data; url = data.testFile; fileSize=852907; start=new Date(); start=start.getTime(); getFileSize(); analyze(); function getFileSize() { debugger; try { var xhr = new XMLHttpRequest(); xhr.open('HEAD', url, false); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status == 200) { fileSize = xhr.getResponseHeader('Content-Length'); } } }; xhr.send(null); } catch (e) { } } function analyze() { debugger; try { var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status == 200) { result(); } } }; xhr.send(null); } catch (e) { postMessage(null); } } function result(v) { var end, rate, duration, message; end = new Date(); end = end.getTime(); duration = end-start; rate = Math.round( (fileSize/(end-start)) *10 ) /10; message = "{'fileSize':" + fileSize + ",'rate':"+ rate + ",'duration':" + duration + "}"; postMessage(JSON.stringify(message)); } }, false);
Il ne vous reste plus qu’à décider quelle vidéo envoyer à votre client ! (utilisez ce tableau pour vous faire une idée des débits en fonction de la qualité de vos vidéos).
Bien entendu, vous pouvez faire réaliser plusieurs appels à votre webworker afin d’affiner l’analyse : Il n’y a aucun impact en termes de perfs sur le client (attention tout de même, une partie de la bande passante est occupée par le test pendant que vous le faites).
Un projet gitHub est à votre disposition. Nous utilisons ce code dans logiciel destiné aux tablettes Ipad et Androïd. Vous pouvez le downloader ici (n’hésitez pas à y participer, il faudrait par exemple rendre l’appel xhr natif cross-browser) : https://github.com/gregorybesson/bandwidthCalculator
Enjoy !