Technologies Web synchrones et multi-dispositifs
This project is maintained by aurelient
L’objectif du TP est de mettre en place une Single Page Application (SPA) permettant à deux navigateurs de commencer une conversation via chat vidéo.
Ce TP s’étalera sur 2 séances et fera l’objet d’un rendu en binome et d’une note.
Vous ferez le rendu sur la forge.
Repartez de votre projet du TP1.
Vérifiez que vous arrivez à lancer une page hello world avec un serveur node basique comme dans le TP1.
Vérifiez que votre déploiement sur Heroku fonctionne.
On continuera d’utiliser ESlint.
Nous allons créer une application qui permet de faire un chat entre deux navigateurs.
Elle ressemblera à cela :
À la différence d’une application de chat “normale”, ici les messages vont s’échanger entre les navigateurs sans passer par un serveur. Nous allons nous appuyer sur WebRTC pour réaliser cela. WebRTC est une technologie p2p.
Nous aurons :
Nous allons nous appuyer sur la peer.js pour réaliser ce TP. Cette bibliothèque abstrait une bonne partie de la complexité de WebRTC. Vous pouvez aller voir le TP de l’année dernière pour avoir une idée de comment faire les choses “à la main”.
On aura besoin des dépendances suivantes :
Nous allons créer un composant React qui va assembler les composants suivants :
function DataChat() {
const [startAvailable, setStart] = useState(true)
const [sendAvailable, setSend] = useState(false)
const [hangupAvailable, setHangup] = useState(false)
return (
// TODO rajouter les champs textes correspondants
// Vous pouvez utiliser des TextField de material-UI
// Et une Grid plutôt que des div pour la mise en page
<div>
<Button onClick={start} disabled={!startAvailable}>
Start
</Button>
<Button onClick={send} disabled={!callAvailable}>
Send
</Button>
<Button onClick={hangUp} disabled={!hangupAvailable}>
Hang Up
</Button>
</div>
)
}
export default DataChat
Vous devriez à ce stade avoir un cadre d’application non fonctionelle
Comme dans les TP précédents nous allons utliser express.
Nous allons y adjoindre un serveur facilitant la découverte entre pairs.
Installez globalement peer : npm install -g peer
(une installation locale devrait fonctionner aussi, je l’ai installé globalement car le module offre aussi la possibilité de lancer un serveur peer dédié en ligne de commande - indépendant d’express - ce qui était pratique pour le développement)
Voir la documentation ici
Voici à quoi votre serveur Express + Peer devrait ressembler :
const express = require('express');
const http = require('http');
const path = require('path');
const app = express();
const server = http.createServer(app);
const { ExpressPeerServer } = require('peer');
const port = process.env.PORT || '3000';
const peerServer = ExpressPeerServer(server, {
debug: true,
path: '/mypeer',
});
app.use(peerServer);
const DIST_DIR = path.join(__dirname, '../dist');
app.use(express.static(DIST_DIR));
app.get('/', (request, response) => {
response.sendFile(__dirname + '/index.html');
});
server.listen(port);
console.log('Listening on: ' + port);
Quand vous lancez le serveur vous pourrez vérifier que votre application React s’affiche bien sur localhost:3000 et que le serveur peer est actif sur localhost:3000/mypeer
⚠️ ATTENTION ⚠️
Nous allons gérer pour ce qui a trait à peerJS à l’extérieur de nos composants React. En effet peerjs est une abstraction d’interfaces de communication réseau, il n’y a pas trop de raison de le gérer à l’interieur de l’état de notre application.
En vous référent à la documentation de peerJS mettez en relation les deux clients.
Le déroulé est le suivant :
// initialisation de votre objet Peer (à l'extérieur du composant)
peer = new Peer(localID, {
host: 'localhost',
port: 3000,
path: '/myapp',
});
Nous allons maintenant créer un nouveau composant dédié à la vidéo.
Pour simplifier les choses je vous conseille de faire un Composant dédié qui a sa propre route.
Comme pour le TP précédent, nous allons démarrer par établir une connexion WebRTC entre 2 peers en local.
Nous allons reprendre le principe du DataChat, mais cette fois transmettre des flux vidéos plutôt que des data.
Nous allons créer un composant React qui va assembler les composants suivants :
le flux vidéo du correspondant
un champ d’identifiant de la personne distante
function VideoChat() {
const [startAvailable, setStart] = useState(true)
const [callAvailable, setCall] = useState(false)
const [hangupAvailable, setHangup] = useState(false)
return (
<div>
<video
ref={localVideoRef} autoPlay muted
style=
>
<track kind="captions" srcLang="en" label="english_captions"/>
</video>
<video
ref={remoteVideoRef} autoPlay
style=
>
<track kind="captions" srcLang="en" label="english_captions"/>
</video>
<div>
<Button onClick={start} disabled={!startAvailable}>
Start
</Button>
<Button onClick={call} disabled={!callAvailable}>
Call
</Button>
<Button onClick={hangUp} disabled={!hangupAvailable}>
Hang Up
</Button>
</div>
</div>
)
}
export default VideoChat
Pour établir la connexion il va falloir gérer la gestion d’identifiants permettant de savoir qui sont les clients à mettre en relation.
Avant de transmettre notre flux local à notre correspondant, nous allons tout d’abord faire en sorte de récupérer le flux vidéo du navigateur, lorsqu’on clique sur Start
Utilisez l’API mediaDevices pour récupérer le stream
vidéo et le visualiser dans votre composant.
const start = () => {
// TODO initialisation de peerjs
setStart(false)
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then(gotStream)
.catch(e => {console.log(e); alert("getUserMedia() error:" + e.name)})
}
const gotStream = stream => {
localVideoRef.current.srcObject = stream
setCall(true) // On fait en sorte d'activer le bouton permettant de commencer un appel
localStreamRef.current = stream
}
Plutôt que des déclarer des variables hors du scope du composant, comme au TP précédent, nous allons faire les choses plus proprement en déclarant certaines objets comme mutable et persistant tout au long du cycle de vie grâce au hook useref.
Vous aurez globalement besoin de gérer trois objets (déclarer au début du composant après vos variables d’état) :
const localStreamRef = useRef();
const localVideoRef = useRef();
const remoteVideoRef = useRef();
On établit la connexion de la même manière qu’au TP précédent.
Le click sur le bouton Call
initiera la connexion entre les deux pairs.
Référez vous à la documentation de peerjs pour l’émission et la réception du flux vidéo.
Vous pourrez appelez gotRemoteStream(stream_de_peerjs);
dans votre événement on call
pour cabler le flux à l’interface.
const call = () => {
// TODO voir la doc de peerjs
setCall(false);
setHangup(true);
};
const gotRemoteStream = (remoteStream) => {
const remoteVideo = remoteVideoRef.current;
if (remoteVideo.srcObject !== remoteStream) {
remoteVideo.srcObject = remoteStream;
}
};
Il suffit d’appeler les méthodes disconnect
sur chacune des connexion (avec les listeners appropriés), potentiellement destroy
pour forcer la fin de la connexion.
Pour terminer nous allons déployer le code sur Heroku. Pour cela nous allons utiliser un serveur peerjs dédié.
Vous pouvez en déployer un par ici.
Il faudra donc mettre à jour votre application en conséquence.
Vous pouvez faire du code code conditionnel et tester location
côté client pour savoir si vous êtes en localhost ou déployé sur heroku, et parler à un serveur peer ou un autre en conséquence.
Dans votre README préciser bien quelle type de configuration fonctionne.
À rendre pour le dimanche 13 à 23h59.
Déposer les liens sur Tomuss “UE-INF2427M Technologies Web Synchrones Et Multi-Dispositifs”