![]() |
![]() |
Accueil > Intranet Michel Buffa > Web Avancé EPU 2013-2014 > TP1 Web Avancé
TP1 Web AvancéDe $1Installation d'un serveur web minimal capable de servir des pistes audioVous pourrez utiliser une autre technologie si vous préférez (Apache/Php, Java EE, Ruby, Python) mais pour le moment, nous vous donnons un exemple (hyper simple, facile à reproduire avec un autre serveur) qui utilise le micro serveur web NodeJS et me module d'extension express, qui permet de transformer NodeJS en serveur web. Télécharger et installer NodeJS
Allez sur http://nodejs.org/ téléchargez et installez le serveur NodeJS. Une fois installez, ouvrez une fenêtre de commande et tapez "node --version", cela doit vous répondre "0.10.xxx" si tout va bien. Le "xxx" varie en fonction de la version. Si vous l'avez déjà installé vérifiez juste que vous n'avez pas une trop vieille version. Récupérez cette archive : Multitrackv1.1.zip (note : cette version corrige plusieurs bugs de la 1.0 qui a été distribuée lors du TP1. On peut lire plusieurs chansons consécutivement, pause et jumpto marche, on peut cliquer dans le morceau pour avancer, les boutons mute/unmute sont cohérents, le code est toujours pourri mais c'est fait exprès !) Dezippez la dans un répertoire qui sera le répertoire de votre projet, par exemple "multitrackTP1". Demandez ensuite à vos enseignants de vous donner l'archive du dossier "multitrack" contenant des pistes de musique. Il faut que ce dossier soit aussi dans le dossier "multitrackTP1". Dezippez le. On vous passera un dossier beaucoup plus complet bientôt, avec une trentaine de chansons. Faites un cd à l'intérieur de ce répertoire, vous devriez voir :
Il vous faut maintenant installer le module express pour NodeJS:
"npm" (pour Node Package Manager) est la commande qui permet d'installer dans des projets des modules complémentaires pour Node JS. Si tout se passe bien, vous devriez avoir maintennt un répertoire comme ceci :
Test de l'application Vous pouvez tester l'application en lançant la commande "node server.js" et en ouvrant "localhost:8081" dans votre navigateur. Avant de faire quoi que ce soit, ouvrez la console de debug de votre navigateur pour regarder les traces (onglet "console"). Je recommande de tester avec le dernier Firefox, beaucoup plus rapide pour décoder les pistes, ou avec Chrome (mais le décodage des pistes prend un peu de temps). Pour tester : ouvrir le menu déroulant et choisir la seconde piste disponible (deep purple), attendre que le bouton "start" devienne actif et cliquer dessus. La chanson démarre. Vous pouvez faire pause/stop/start et muter des pistes. Etude de l'application côté serveurRegardons le code de l'application côté serveur : var fs = require("fs"); // We need to use the express framework: have a real web server that knows how to send mime types etc. var express=require('express'); // Init globals variables for each module required var app = express() , http = require('http') , server = http.createServer(app); // Indicate where static files are located app.configure(function () { app.use(express.static(__dirname + '/')); }); // Config var PORT = 8081, TRACKS_PATH = './multitrack/'; // launch the http server on given port server.listen(PORT); // routing app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); }); // routing app.get('/track', function (req, res) { function sendTracks(trackList) { if (!trackList) return res.send(404, 'No track found'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.write(JSON.stringify(trackList)); res.end(); } getTracks(sendTracks); }); // routing app.get('/track/:id', function (req, res) { var id = req.params.id; function sendTrack(track) { if (!track) return res.send(404, 'Track not found with id "' + id + '"'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.write(JSON.stringify(track)); res.end(); } getTrack(id, sendTrack); }); // routing app.get(/\/track\/(\w+)\/(?:sound|visualisation)\/((\w|.)+)/, function (req, res) { res.sendfile(__dirname + '/' + TRACKS_PATH + req.params[0] + '/' + req.params[1]); }); function getTracks(callback) { getFiles(TRACKS_PATH, callback); } function getTrack(id, callback) { getFiles(TRACKS_PATH + id, function(fileNames) { var track = { id: id, instruments: [] }; fileNames.sort(); for (var i = 0; i < fileNames.length; i += 2) { var instrument = fileNames[i].match(/(.*)\.[^.]+$/, '')[1]; track.instruments.push({ name: instrument, sound: instrument + '.mp3', visualisation: instrument + '.png' }); } callback(track); }) } function getFiles(dirName, callback) { fs.readdir(dirName, function(error, directoryObject) { callback(directoryObject); }); } Bon, ça a l'air un peu compliqué mais n'ayons pas peur et regardons d'abord ce que ça fait... Lignes 1-19 : initialisation du serveur On va charger les modules nécessaires : "fs" pour "filesystem", permet de manipuler des fichiers, des répertoires. C'est avec ce module qu'on a pu implémenter la fonction getFiles() située tout à la fin du fichier. Cette fonction lit le contenu d'un répertoire et renvoie un tableau contenant les fichiers dans ce répertoire. La ligne : // Indicate where static files are located app.configure(function () { app.use(express.static(__dirname + '/')); }); Indique que le serverur sera capable de renvoyer des fichiers statiques (par ex "/multitrack/amy_rehab/voix.mp3"), sans cette ligne on ne peut demander de fichiers via l'URL, seuls les routages explicites figurant dans la configuration du serveur ne seront pris en compte. Un tel routage est présent aussi dans la configuration, pour indiquer la page qui sera envoyée par défaut : / routing app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); }); Etc... Ensuite nous avons les différents web services qui répondent à des requêtes GET... Celui-ci par exemple réponds à l'URL "/track" et il renvoie la liste des pistes disponibles sur le serveur, en JSON. Essyez donc d'ouvrir "locahost:8081/track et regardez ce que vous recevez ! // routing app.get('/track', function (req, res) { function sendTracks(trackList) { if (!trackList) return res.send(404, 'No track found'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.write(JSON.stringify(trackList)); res.end(); } getTracks(sendTracks); }); On reçoit :
C'est un tableau JSON. Vous vous doutez qu'on l'utilise pour la construction du menu déroulant dans l'interface graphique. De même, on a défini des web services pour les URLs suivants :
Voilà, vous pourrez regarder en détails comment ceci est implémenté. Vous avez compris qu'avec ce code serveur, on va pouvoir obtenir toutes les informations que nous désirons depuis un client Ajax. Contact avec la librairie Web AudioSupportée par tous les browsers récents dans leurs dernières version sauf IE. Quelques soucis sous IOS mais ça marche à 99%.
La librairie Web Audio est assez complexe, et son API a changé cette année pour devenir "stable". On trouve encore de nombreux exemples sur le web qui utilisent exclusivement la version "WebKit" de cette API. Vous devrez utiliser la version "standard" de l'API, qui fonctionne avec Chrome, Safari, les dernières versions de Firefox, Opera, Chrome android. Seul IE n'est pour le moment pas capable de faire tourner des applications Web Audio. Le principe de WebAudio consiste à charger des échantillons en mémoire, le plus souvent en Ajax via ce que l'on appelle XhR 2, la nouvelle version de XmlHttpRequest. Cette version permet notamment de faire du transfert binaire natif, de monitorer la progression des uploads et downloads et de faire des requêtes cross domains plus facilement. Une fois les échantillons chargés en mémoire il faut construire un graphe connectant les échantillons aux haut parleurs et démarrer la lecture. Si on veut, on peut ajouter des noeuds intermédiares dans ce graphe, comme des controles de volume, des analyseurs de fréquence, des effets spéciaux (égaliseurs, compresseurs, echo, reverb, etc.). Je vous propose tout d'abord de lire au moins la première moitié de cet excellent article d'introduction à Web Audio : http://www.html5rocks.com/en/tutoria...ebaudio/intro/ Puis de faire tourner ce petit exemple qui montre comment charger un échantillon et le lire : <!DOCTYPE html> <html> <head> <meta charset=utf-8 /> <title>Web Audio API</title> <script> var context, soundSource, soundBuffer, buttonPlay, buttonStop, buttonLoad, url = '/multitrack/deep_smoke/guitare.mp3'; // Step 1 - Initialise the Audio Context // There can be only one! function init() { if (typeof AudioContext == "function") { context = new AudioContext(); } else if (typeof webkitAudioContext == "function") { context = new webkitAudioContext(); } else { throw new Error('AudioContext not supported. :('); } buttonPlay = document.querySelector("#play"); buttonStop = document.querySelector("#stop"); buttonLoad = document.querySelector("#load"); } // Step 2: Load our Sound using XHR function loadSound() { console.log("loading " + url + " using Xhr2"); // Note: this loads asynchronously var request = new XMLHttpRequest(); request.open("GET", url, true); // BINARY TRANSFERT ! request.responseType = "arraybuffer"; // Our asynchronous callback request.onload = function() { var audioData = request.response; // We got the sound file from the server, let's decode it decode(audioData); }; request.send(); } // Finally: tell the source when to start function playSound() { // play the source now. // First parameter = delay in seconds before starting to play // Second parameter = where do we start (0 = beginning of song) console.log("playing sound"); // connect sound samples to the speakers buildGraph(); // BEWARE : the graph should be connected, if sound has been stopped, // and if the graph is not built (i.e the previous line of code is not present) // Then the next line will do nothing, we need to rebuild the graph soundSource.start(0, 0); buttonStop.disabled = false; buttonPlay.disabled = true; } function stopSound() { console.log("Stopping sound, Graph destroyed, cannot be played again without rebuilding the graph !"); // stop the source now. // Parameter : delay before stopping // BEWARE : THIS DESTROYS THE NODE ! If we stop, we need to rebuid the graph again ! // We do not need to redecode the data, just to rebuild the graph soundSource.stop(0); buttonPlay.disabled = false; buttonStop.disabled = true; } function buildGraph() { console.log("Building the audio graph : connecting decoded sound sample to the speakers"); // create a node with the decoded sound source soundSource = context.createBufferSource(); soundSource.buffer = soundBuffer; // Plug the cable from one thing to the other // Here we connect the decoded sound sample to the speakers soundSource.connect(context.destination); } function decode(audioData) { console.log("decoding audio data... WebAudio uses RAW sample in memory, not compressed one"); // The Audio Context handles creating source buffers from raw binary context.decodeAudioData(audioData, function onSuccess(soundBufferDecoded) { soundBuffer = soundBufferDecoded; console.log("sample ready to be played, decoded. It just needs to be inserted into an audio graph"); buttonPlay.disabled = false; buttonLoad.disabled = true; }, function onFailure() { alert("Decoding the audio buffer failed"); }); } </script> </head> <body onload=init();> <button id = "load" onclick="loadSound();">Charger son via XhR2</button> <button id="play" onclick="playSound();" disabled>Lecture du son</button> <button id="stop" onclick="stopSound();" disabled>Stopper le son</button> </body> </html> Copiez ce code dans un fichier "test.html" et copiez le fichier dans le répertoire "multitrackTP1" et testez avec l'URL "http://localhost:8081/test.html", attention, l'échantillon sonore commence par 5s de silence... A remarquer :
Pour charger plusieurs sons et les décoder avant de pouvoir les jouerComment faire dans le cas qui nous intéresse, c'est à dire charger plusieurs fichiers audio, puis les décoder avant de pouvoir construire un graphe pour les lire ? Le code JavaScript pour faire cela n'est pas évident car tous les appels Ajax sont asynchrones, et les appels au décodage des fichiers également. L'article http://www.html5rocks.com/en/tutoria...ebaudio/intro/ propose une classe BufferLoader permettant d'effectuer un chargement asyncrhone de plusieurs fichiers audio + leur décodage. L'article donne un exemple d'utilisation. Nous l'avons également utilisée dans le prototype de multitracker, dans le fichier sound.js, fonction loadAllSoundSamples()... var bufferLoader; function loadAllSoundSamples(tracks) { bufferLoader = new BufferLoader( context, tracks, finishedLoading ); bufferLoader.load(); } function finishedLoading(bufferList) { console.log("finished loading"); resetAllBeforeLoadingANewSong(); buffers = bufferList; buttonPlay.disabled = false; } Ou tracks est le tableau des fichiers audio à charger. Travail à faireBon, vous avez vu comment implémenter un petit serveur qui propose via des URLs de web services de fournir les données nécessaires à l'application. Nous avons vu le principe de chargement asyncrhone et de décodage des échantillons pour les jouer avec Web Audio, puis également comment on peut charger plusieurs échantillons en Ajax. Nous vous proposons 1) de regarder un peu le code du prototype fourni et 2) de partir de zéro pour en écrire un à vous. Pour le moment oubliez la partie "canvas/dessin/animation", vous pourrez vous contenter de récupérer simplement les images des échantillons, les afficher, etc. 3) de voir comment on a pu connecter au graphe un controleur de volume, comment on a pu muter les pistes, etc. Le but du jeu n'est pas que vous refassiez notre proto, mais que vous l'ayez sous la main pour voir comment certaines fonctionnalités ont été implémentées en vue de créer votre propre prototype.
Mots clés:
|
Powered by MindTouch Deki Open Source Edition v.8.08 |