Construire une application de chat vidéo avec Python, JavaScript et Twilio Programmable Video

April 30, 2020
Rédigé par
Révisé par
Liz Moy
Twilion

Construire une application de chat vidéo avec Python, JavaScript et Twilio Programmable Video

Les efforts visant à limiter la propagation de la COVID-19 dans le monde entier ont contraint un grand nombre de personnes à travailler à domicile, ce qui a naturellement suscité un intérêt croissant pour les outils de communication et de collaboration.

Dans cet article, vous allez étudier une solution de visioconférence, mais au lieu de vous tourner vers un système tiers, vous aller adopter l'approche du « DIY » (Do It Yourself, fais-le toi même) et créer votre propre système. Notre système va fonctionner sur des navigateurs Web mobiles et de bureau modernes. Ainsi, les participants n'auront ni besoin de télécharger ni d'installer de logiciel sur leur ordinateur. La partie côté serveur du projet va utiliser Python et le framework Flask, tandis que le côté client va être construit en vanilla JavaScript, avec une pincée d'HTML et de CSS pour faire bonne mesure.

Si vous craignez que ce tutoriel soit long, difficile et obscur, laissez-moi vous rassurer. La magie qui vous permettra de construire ce projet vient du service Twilio Programmable Video, qui fait le gros du travail.

Vous trouverez ci-dessous un appel test auquel j'étais connecté via mon ordinateur portable et mon téléphone mobile.

capture d'écran du projet terminé

Cet article passe en revue la mise en œuvre du projet en détail, afin que vous puissiez le suivre et le construire sur votre ordinateur. Si vous êtes intéressé pour télécharger le projet complet au lieu de le construire étape par étape, vous pouvez le trouver dans ce référentiel GitHub : https://github.com/miguelgrinberg/flask-twilio-video.

Les prérequis du tutoriel

Pour construire le projet, vous aurez besoin des éléments suivants :

  • Python 3.6 ou version ultérieure. Si votre système d'exploitation ne fournit pas d'interpréteur Python, vous pouvez vous rendre sur python.org pour télécharger un programme d'installation.
  • ngrok. Vous utiliserez cet utilitaire pratique pour connecter l'application Flask exécutée sur votre système à une URL publique à laquelle Twilio peut se connecter. Cela est nécessaire pour la version de développement de l'application, car votre ordinateur est probablement derrière un routeur ou un pare-feu, et n'est donc pas directement accessible sur Internet. Si ngrok n'est pas installé, vous pouvez en télécharger une copie pour Windows, MacOS ou Linux.
  • Un compte Twilio gratuit ou payant. Si vous êtes nouveau sur Twilio, obtenez votre compte gratuit maintenant ! Ce lien vous offrira 10 $ lors de la mise à niveau.
  • Un navigateur Web compatible avec la bibliothèque JavaScript Twilio Programmable Video (voir la liste de ces navigateurs ci-dessous). Notez que si vous perdez votre clé API secrète, vous devrez générer une nouvelle clé.

La fonctionnalité vidéo et audio de base de ce projet étant fournie par Twilio Programmable Video, vous devez utiliser un navigateur Web pris en charge par ce service. Voici la liste actuelle des navigateurs pris en charge :

  • Android : Chrome et Firefox
  • iOS : Safari
  • Linux : Chrome et Firefox
  • MacOS : Chrome, Firefox, Safari et Edge
  • Windows : Chrome, Firefox et Edge

Consultez la documentation relative à Programmable Video pour obtenir la liste la plus récente des navigateurs Web pris en charge.

Structure du projet

Commençons par créer le répertoire où vous stockerez vos fichiers de projet. Ouvrez une fenêtre de terminal, recherchez un répertoire parent approprié, puis entrez les commandes suivantes :

$ mkdir flask-twilio-video
$ cd flask-twilio-video

En suivant la structure de l'application Flask la plus basique, vous allez maintenant créer deux sous-répertoires, static et templates pour stocker les fichiers qui seront fournis au client.

$ mkdir static
$ mkdir templates

Configuration de votre compte Twilio

Connectez-vous à votre compte Twilio pour accéder à la console. Sur cette page, vous pouvez voir le « SID du compte » (Account SID) attribué à votre compte. Cet élément est important, car il identifie votre compte et est utilisé pour authentifier les requêtes auprès de l'API Twilio.

SID du compte dans la console twilio

Comme vous allez avoir besoin du SID du compte plus tard, cliquez sur le bouton « Copy to Clipboard » (Copier dans le presse-papiers ) sur le côté droit. Ouvrez ensuite un nouveau fichier nommé .env dans votre éditeur de texte (notez le point de début) et écrivez le contenu suivant, en collant soigneusement le SID à l'endroit indiqué :

TWILIO_ACCOUNT_SID=<your-twilio-account-sid>

Le service Programmable Video nécessite également une clé API Twilio pour l'authentification. Dans cette étape, vous allez en ajouter une à votre compte Twilio. Pour commencer, accédez à la section Clés API de la console Twilio.

Si vous n'avez jamais créé de clé API auparavant, vous verrez un bouton « Create new API Key » (Créer une nouvelle clé API). Si vous avez déjà créé une ou plusieurs clés API, vous verrez plutôt un bouton « + » rouge pour en ajouter une autre. Dans les deux cas, cliquez pour créer une nouvelle clé API.

créer une clé api

Entrez videochat comme nom de la clé (ou tout autre nom de votre choix), laissez le type de clé sur « Standard », puis cliquez sur le bouton « Create API Key » (Créer une clé API).

ajouter une nouvelle clé api

Les détails de votre nouvelle clé API s'affichent. Les valeurs « SID » et « SECRET » sont utilisées pour l'authentification avec la valeur SID du compte que vous avez enregistrée précédemment.

Ouvrez à nouveau le fichier .env dans votre éditeur de texte et ajoutez deux lignes supplémentaires pour enregistrer les détails de votre clé API :

TWILIO_ACCOUNT_SID=<your-twilio-account-sid>
TWILIO_API_KEY_SID=<your-twilio-api-key-sid>
TWILIO_API_KEY_SECRET=<your-twilio-api-key-secret>

Une fois que vous avez écrit votre clé API en toute sécurité dans le fichier .env, vous pouvez quitter la page Clés API. Notez que si vous perdez votre secret de clé API, vous devrez générer une nouvelle clé.

Les informations contenues dans votre fichier .env sont privées. Assurez-vous de ne partager ce fichier avec personne. Si vous prévoyez de stocker votre projet sous le contrôle de la source, il serait judicieux de configurer ce fichier pour qu'il soit ignoré, car vous ne voulez jamais valider ce fichier par erreur.

Créer un environnement virtuel Python

En suivant les meilleures pratiques, vous allez créer un environnement virtuel dans lequel vous allez installer vos dépendances Python.

Si vous utilisez un système Unix ou MacOS, ouvrez un terminal et entrez les commandes suivantes pour effectuer les tâches décrites ci-dessus :

$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install twilio flask python-dotenv

Pour ceux qui suivent le tutoriel sous Windows, entrez les commandes suivantes dans une fenêtre d'invite de commande :

$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install twilio flask python-dotenv

La dernière commande utilise pip, le programme d'installation de packages Python, pour installer les trois packages Python que vous allez utiliser dans ce projet, qui sont :

À titre de référence, au moment de la publication de ce tutoriel, il s'agissait des versions des packages ci-dessus et de leurs dépendances :

certifi==2020.4.5.1
chardet==3.0.4
click==7.1.1
Flask==1.1.2
idna==2.9
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
PyJWT==1.7.1
python-dotenv==0.12.0
pytz==2019.3
requests==2.23.0
six==1.14.0
twilio==6.38.1
urllib3==1.25.8
Werkzeug==1.0.1

Création d'un serveur Web

Votre projet sera conçu comme une application Web monopage. Il sera piloté par un serveur Web qui fournira les fichiers HTML, CSS et JavaScript aux clients, et qui répondra aux requêtes asynchrones émises à partir du code JavaScript en cours d'exécution dans le navigateur.

Vous allez commencer par le serveur Web, car c'est un élément essentiel du projet. Une fois que vous aurez le serveur Web en cours d'exécution, vous commencerez à ajouter toutes les autres pièces dont vous avez besoin.

Comme indiqué dans la section les Prérequis, vous allez utiliser le framework Flask pour mettre en œuvre la logique dans notre serveur Web. Puisque ce sera un projet simple, vous allez coder le serveur entier dans un fichier unique intitulé app.py.

Vous trouverez ci-dessous la première version de notre serveur Web. Copiez le code dans un fichier app.py dans le répertoire du projet.

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')

La variable app est appelée « instance d'application ». Son but est de fournir les fonctions de support dont vous avez besoin pour mettre en œuvre votre serveur Web. Vous utilisez le décorateur app.route pour définir un mappage entre les URLs et les fonctions Python. Dans cet exemple particulier, lorsqu'un client requête l'URL racine de notre serveur, Flask exécute notre fonction index() et s'attend à ce qu'elle fournisse la réponse. L'implémentation de votre fonction index() crée un fichier index.html que vous n'avez pas encore écrit. Ce fichier contiendra la définition HTML de la page principale et unique de notre application de chat vidéo.

Même si vous n'en n'êtes qu'aux prémices de notre projet, vous êtes prêts à démarrer le serveur Web. Si vous utilisez un ordinateur Linux ou MacOS, utilisez la commande suivante :

(venv) $ FLASK_ENV=development flask run

Si vous utilisez un ordinateur Windows, utilisez les commandes suivantes à la place :

(venv) $ set FLASK_ENV=development
(venv) $ flask run

Une fois le serveur démarré, vous devriez voir un résultat semblable à celui qui suit :

 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 274-913-316

À ce stade, le serveur Web est en cours d'exécution et prêt à recevoir des requêtes. Vous avez également activé le mode de débogage de Flask, qui déclenchera le redémarrage du serveur Web chaque fois que des modifications sont apportées à l'application. Ainsi, vous pouvez maintenant laisser cette fenêtre de terminal seule pendant que vous commencez à coder les composants de votre projet.

Si vous essayez de vous connecter à l'application à partir de votre navigateur Web, vous recevrez une erreur « template not found », car vous n'avez pas encore écrit le fichier index.html référencé par votre chemin principal et unique.* Vous allez écrire ce fichier dans la section suivante, puis vous aurez une première version en cours d'exécution de l'application.

Mise en page de l'application

Votre conception de page sera très simple. Vous inclurez un titre, un formulaire Web dans lequel l'utilisateur peut saisir son nom et rejoindre ou quitter des appels vidéo, puis la zone de contenu, où les flux vidéo de tous les participants seront affichés. Pour l'instant, vous allez ajouter une vidéo de substitution pour vous-mêmes.*

Voici l'aspect de la page :

mise en page

Pour créer cette page, vous avez besoin d'une combinaison d'HTML et de CSS. Vous trouverez ci-dessous le fichier templates/index.html.

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
    </head>
    <body>
        <h1>Flask & Twilio Video Conference</h1>
        <form>
            <label for="username">Name: </label>
            <input type="text" name="username" id="username">
            <button id="join_leave">Join call</button>
        </form>
        <p id="count"></p>
        <div id="container" class="container">
            <div id="local" class="participant"><div></div><div>Me</div></div>
            <!-- more participants will be added dynamically here -->
        </div>

        <script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
        <script src="{{ url_for('static', filename='app.js') }}"></script>
    </body>
</html>

La section <head> de ce fichier fait référence à un fichier styles.css. Vous utilisez la fonction url_for() de Flask pour générer l'URL correcte. C'est une bonne chose puisqu'il vous suffit de placer le fichier dans le répertoire static et laisser Flask générer l'URL. Si vous vous demandez quelle est la différence entre un fichier de modèle et un fichier statique, en voici la réponse. Les fichiers de modèle peuvent avoir des espaces réservés générés dynamiquement lorsque la fonction render_template() que vous avez vue ci-dessus s'exécute.

La section <body> de la page définit les éléments suivants :

  • Un titre <h1>
  • Un élément <form> avec un champ de nom et un bouton d'envoi
  • Un élément <p> où vous allez afficher l'état de la connexion et le nombre de participants
  • Un conteneur <div> avec un participant identifié et le nom local où vous allez afficher votre propre flux vidéo. D'autres participants seront ajoutés dynamiquement lorsqu'ils rejoindront l'appel vidéo
  • Le <div> de chaque participant contient un <div> vide où la vidéo sera affichée et un second <div> où le nom sera affiché.
  • Liens vers deux fichiers JavaScript dont vous aurez besoin : la publication officielle de la bibliothèque twilio-video.js et un app.js que vous allez écrire bientôt.

Le contenu du fichier static/styles.css est le suivant :

.container {
    margin-top: 20px;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
}
.participant {
    margin-bottom: 5px;
    margin-right: 5px;
}
.participant div {
    text-align: center;
}
.participant div:first-child {
    width: 240px;
    height: 180px;
    background-color: #ccc;
    border: 1px solid black;
}
.participant video {
    width: 100%;
    height: 100%;
}

Ces définitions CSS sont toutes dédiées à la mise en page de l'élément <div> « conteneur », qui est structuré comme une flexbox. Ainsi, les participants sont automatiquement ajoutés à droite et renvoyés à la ligne suivante selon les besoins en fonction de la taille de la fenêtre du navigateur.

La définition de .participant div:first-child s'applique au premier élément enfant de tout élément <div> ayant la classe participant. Ici, nous limitons la taille de la vidéo à 240x180 pixels. Vous avez également un arrière-plan plus sombre et une bordure noire, afin que vous puissiez voir un espace réservé pour la fenêtre vidéo. L'arrière-plan est également utile lorsque les dimensions de la vidéo ne correspondent pas exactement à votre rapport hauteur/largeur. N'hésitez pas à régler ces options à votre convenance.

Une fois les fichiers HTML et CSS en place, le serveur devrait pouvoir répondre à votre navigateur Web et afficher la mise en page de base que vous avez vue ci-dessus. Pendant que le serveur est en cours d'exécution, ouvrez votre navigateur Web et saisissez http://localhost:5000 dans la barre d'adresse pour voir la première version de l'application en cours d'exécution.

Affichage de votre propre flux vidéo

Si vous avez regardé dans le journal réseau du navigateur, vous avez probablement remarqué que le navigateur a tenté de charger le fichier app.js que nous référençons au bas de l'élément index.html et que cela a échoué parce que vous n'avez pas encore ce fichier dans votre projet. Vous allez maintenant écrire votre première fonction dans ce fichier pour ajouter votre propre flux vidéo à la page.

Créez le fichier et ajoutez le code suivant à static/app.js :

function addLocalVideo() {
    Twilio.Video.createLocalVideoTrack().then(track => {
        let video = document.getElementById('local').firstChild;
        video.appendChild(track.attach());
    });
};

addLocalVideo();

La fonction addLocalVideo() utilise la bibliothèque JavaScript Twilio Programmable Video pour créer une piste vidéo locale. La fonction createLocalVideoTrack() de la bibliothèque est asynchrone et renvoie une promesse. Vous utilisez donc la méthode then() pour ajouter une logique dans une fonction de rappel après la création de la piste vidéo.

La fonction de rappel reçoit un objet LocalVideoTrack en tant qu'argument. Vous utilisez sa méthode attach() pour ajouter l'élément vidéo au premier <div> enfant de l'élément local. Au cas où cela ne serait pas clair, examinons la structure du participant local à partir du fichier index.html :

            <div id="local" class="participant"><div></div><div>Me</div></div>

Vous pouvez voir ici que l'élément local a deux éléments <div> comme enfants. Le premier est vide, et c'est l'élément auquel vous attachez la vidéo. Le second <div> est destiné à l'étiquette qui apparaît sous la vidéo.

Vous pouvez actualiser la page dans le navigateur et vous devriez voir votre vidéo affichée. Notez que la plupart des navigateurs vous demanderont votre autorisation avant d'activer la caméra.

afficher le flux vidéo local

Générer un token d'accès pour un participant

Twilio prend la sécurité très au sérieux. Avant que les utilisateurs puissent participer à un appel vidéo, l'application doit vérifier que les utilisateurs sont autorisés et leur générer un token d'accès. Les tokens doivent être générés sur le serveur Python, car les secrets que vous avez stockés dans le fichier .env sont requis pour ce processus.

Dans une application réelle, c'est l'endroit où l'application authentifie l'utilisateur qui souhaite rejoindre l'appel. La requête de connexion dans une telle application comprend probablement un mot de passe, un cookie d'authentification ou une autre forme d'identification en plus du nom de l'utilisateur. Un token d'accès à la salle de conversation vidéo ne sera généré qu'après que l'utilisateur demandant l'accès à l'appel vidéo soit correctement authentifié.

Le fichier app.py mis à jour est illustré ci-dessous.

import os
from dotenv import load_dotenv
from flask import Flask, render_template, request, abort
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VideoGrant

load_dotenv()
twilio_account_sid = os.environ.get('TWILIO_ACCOUNT_SID')
twilio_api_key_sid = os.environ.get('TWILIO_API_KEY_SID')
twilio_api_key_secret = os.environ.get('TWILIO_API_KEY_SECRET')

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/login', methods=['POST'])
def login():
    username = request.get_json(force=True).get('username')
    if not username:
        abort(401)

    token = AccessToken(twilio_account_sid, twilio_api_key_sid,
                        twilio_api_key_secret, identity=username)
    token.add_grant(VideoGrant(room='My Room'))

    return {'token': token.to_jwt().decode()}

Comme mentionné ci-dessus, vous avez besoin des trois secrets que vous avez stockés dans le fichier .env plus tôt, donc vous appelez la fonction load_dotenv() du paquet python-dotenv pour importer ces secrets, puis vous les attribuez à des variables pour plus de commodité.

La génération de tokens se produit dans une nouvelle route que vous allez invoquer du côté JavaScript, joint à l'URL /login. La fonction recevra le nom d'utilisateur au format JSON. Comme il s'agit d'une application simple, la seule authentification que vous allez effectuer sur l'utilisateur est de vérifier que le nom d'utilisateur ne soit pas vide. Si la validation échoue, une erreur 401 est renvoyée pour indiquer que l'utilisateur n'a pas accès à l'appel vidéo. Comme vous l'avez vu précédemment, une application réelle mettrait en œuvre ici un mécanisme d'authentification plus complet.

Le token est généré à l'aide de la classe d'aide AccessToken de la bibliothèque Twilio Python Helper. Vous attachez une autorisation vidéo pour une salle vidéo appelée « Ma salle ». Une application plus complexe peut fonctionner avec plusieurs salles vidéo et décider de « la » ou « des » salles auxquelles l'utilisateur peut accéder.

Le token est renvoyé dans une charge utile JSON au format :

{
    "token": "the-token-goes-here"
}

Gestion du formulaire de connexion

Vous allez maintenant implémenter le traitement du formulaire de connexion dans la page Web. Le participant entre son nom, puis clique sur le bouton « Join call » (Rejoindre l'appel). Une fois la connexion établie, le même bouton est utilisé pour se déconnecter de l'appel.

Pour gérer le bouton de formulaire, vous devez joindre un gestionnaire pour l'événement Click. Les modifications apportées à static/app.js sont indiquées ci-dessous.

let connected = false;
const usernameInput = document.getElementById('username');
const button = document.getElementById('join_leave');
const container = document.getElementById('container');
const count = document.getElementById('count');
let room;

function addLocalVideo() { /* no changes in this function */ };

function connectButtonHandler(event) {
    event.preventDefault();
    if (!connected) {
        let username = usernameInput.value;
        if (!username) {
            alert('Enter your name before connecting');
            return;
        }
        button.disabled = true;
        button.innerHTML = 'Connecting...';
        connect(username).then(() => {
            button.innerHTML = 'Leave call';
            button.disabled = false;
        }).catch(() => {
            alert('Connection failed. Is the backend running?');
            button.innerHTML = 'Join call';
            button.disabled = false;    
        });
    }
    else {
        disconnect();
        button.innerHTML = 'Join call';
        connected = false;
    }
};

addLocalVideo();
button.addEventListener('click', connectButtonHandler);

Vous pouvez constater que vous avez maintenant quelques variables globales déclarées en haut. Quatre d'entre elles permettent d'accéder facilement aux éléments de la page, tels que le champ de saisie du nom, le bouton d'envoi de notre formulaire Web, etc. Le booléen connected suit l'état de la connexion, principalement pour aider à déterminer si le clic d'un bouton entraîne une connexion ou une déconnexion. La variable room contiendra l'objet de la salle de chat vidéo une fois que vous l'aurez.

Tout en bas du script, vous attachez la fonction connectButtonHandler() à l'événement clic sur le bouton de formulaire. La fonction est un peu longue, mais elle consiste principalement à valider que l'utilisateur a saisi un nom et à mettre à jour l'apparence du bouton à mesure que l'état de la connexion change. Si vous filtrez la gestion des formulaires, vous pouvez voir que la connexion et la déconnexion réelles sont gérées par deux fonctions connect() et disconnect() que vous allez écrire dans les sections suivantes.

Connexion à une salle de chat vidéo

Vous atteignez maintenant la partie la plus importante (et aussi la plus complexe !) de votre application. Pour connecter un utilisateur à la salle de chat vidéo, l'application JavaScript exécutée dans le navigateur Web doit effectuer deux opérations dans l'ordre. Tout d'abord, le client doit contacter le serveur Web et demander un token d'accès pour l'utilisateur, puis, une fois le token reçu, le client doit appeler la bibliothèque Twilio Video avec ce token pour établir la connexion.

let connected = false;
const usernameInput = document.getElementById('username');
const button = document.getElementById('join_leave_button');
const container = document.getElementById('container');
const count = document.getElementById('count');
let room;

function addLocalVideo() { /* no changes in this function */ };

function connectButtonHandler(event) { /* no changes in this function */ };

function connect(username) {
    let promise = new Promise((resolve, reject) => {
        // get a token from the back end
        fetch('/login', {
            method: 'POST',
            body: JSON.stringify({'username': username})
        }).then(res => res.json()).then(data => {
            // join video call
            return Twilio.Video.connect(data.token);
        }).then(_room => {
            room = _room;
            room.participants.forEach(participantConnected);
            room.on('participantConnected', participantConnected);
            room.on('participantDisconnected', participantDisconnected);
            connected = true;
            updateParticipantCount();
            resolve();
        }).catch(() => {
            reject();
        });
    });
    return promise;
};

La fonction connect() renvoie une promesse, que l'appelant peut utiliser pour joindre des actions à effectuer une fois la connexion établie, ou pour gérer les erreurs. En interne, le résultat de la promesse est contrôlé via les fonctions resolve() et reject() qui sont transmises en tant qu'arguments dans la fonction d'exécution transmise dans le constructeur Promise().

Vous pouvez voir les appels vers ces fonctions répandus dans toute la logique de connexion. Un appel à resolve() déclenchera le rappel de réussite de l'appelant, tandis qu'un appel à reject() fera de même pour le rappel d'erreur.

La logique de connexion comporte deux étapes, comme indiqué ci-dessus. Vous utilisez d'abord la fonction fetch() du navigateur pour envoyer une requête au chemin /login dans l'application Flask que vous avez créée ci-dessus. Cette fonction renvoie également une promesse, donc vous utilisez les gestionnaires then(...).catch(...) pour fournir des rappels de réussite et d'échec.

Si l'appel fetch échoue, vous appelez reject() pour faire échouer votre propre promesse. Si l'appel réussit, vous décodez le JSON dans la variable data, puis appelez la fonction connect() à partir de la bibliothèque Twilio Video en transmettant votre token nouvellement acquis.

L'appel de connexion vidéo est également une promesse, donc une fois encore vous continuez à enchaîner vos fonctions avec ’then(...). Le gestionnaire de succès pour la connexion est la partie la plus intéressante, où vous avez été connectés à la salle de chat vidéo et devez organiser la partie ’container de la page pour refléter cela.

Ce rappel de réussite reçoit un argument ’_room, qui représente la salle vidéo. Étant donné qu'il s'agit d'une variable utile, vous attribuez ’_room à la variable globale ’room, afin que le reste de l'application puisse accéder à cette salle lorsque cela est nécessaire.

Le tableau ’room.participants contient la liste des personnes déjà présentes dans l'appel. Pour chacun de ces éléments, vous devez ajouter une section ’<div> qui montre la vidéo et le nom. Tout ceci est encapsulé dans la fonction ’participantConnected(), donc vous l'invoquez pour chaque participant. Vous souhaitez également que tous les futurs participants soient traités de la même manière. C'est pourquoi vous avez mis en place un gestionnaire pour l'événement ’participantConnected qui pointe vers la même fonction. L'événement ’participantDisconnected est également important, car vous souhaitez supprimer tous les participants qui quittent l'appel. Vous avez donc également mis en place un gestionnaire pour cet événement.

À ce stade, vous êtes entièrement connectés. Ainsi, vous pouvez l'indiquer dans la variable booléenne ’connected. L'action finale que vous avez entreprise consiste à mettre à jour l'élément ’<p> qui indique l'état de la connexion pour afficher le nombre de participants. Cela se fait dans une fonction séparée, car vous aurez besoin de le faire à plusieurs endroits. La fonction met à jour le texte de l'élément en fonction de la longueur du tableau room.participants. Ajoutez l'implémentation de cette fonction à static/app.js.

function updateParticipantCount() {
    if (!connected)
        count.innerHTML = 'Disconnected.';
    else
        count.innerHTML = (room.participants.size + 1) + ' participants online.';
};

Notez que le tableau room.participants inclut tous les participants sauf vous-mêmes. Ainsi, le nombre total de personnes en cours d'appel est toujours supérieur d'une unité à la taille de la liste.

Connexion et déconnexion des participants

Vous avez vu dans la section précédente que lorsqu'un participant rejoint l'appel, vous appelez le gestionnaire participantConnected. Cette fonction doit créer un nouveau <div> à l'intérieur de l'élément container, en suivant la même structure que celle utilisée pour l'élément local qui affiche notre propre flux vidéo.

Ci-dessous, vous pouvez voir l'implémentation de la fonction participantConnected() avec l'homologue participantDisconnected() et quelques fonctions auxiliaires, qui se trouvent toutes également dans static/app.js.

function participantConnected(participant) {
    let participantDiv = document.createElement('div');
    participantDiv.setAttribute('id', participant.sid);
    participantDiv.setAttribute('class', 'participant');

    let tracksDiv = document.createElement('div');
    participantDiv.appendChild(tracksDiv);

    let labelDiv = document.createElement('div');
    labelDiv.innerHTML = participant.identity;
    participantDiv.appendChild(labelDiv);

    container.appendChild(participantDiv);

    participant.tracks.forEach(publication => {
        if (publication.isSubscribed)
            trackSubscribed(tracksDiv, publication.track);
    });
    participant.on('trackSubscribed', track => trackSubscribed(tracksDiv, track));
    participant.on('trackUnsubscribed', trackUnsubscribed);

    updateParticipantCount();
};

function participantDisconnected(participant) {
    document.getElementById(participant.sid).remove();
    updateParticipantCount();
};

function trackSubscribed(div, track) {
    div.appendChild(track.attach());
};

function trackUnsubscribed(track) {
    track.detach().forEach(element => element.remove());
};

Le rappel participantConnected() reçoit un objet Participant de la bibliothèque Twilio Video. Les deux propriétés importantes de cet objet sont participant.sid et participant.identity, qui sont respectivement un identifiant et un nom de session uniques. L'attribut identity provient directement du token que vous avez généré. Rappelez-vous que vous avez transmis identity=username à votre fonction de génération de tokens Python.

La structure HTML d'un participant est similaire à celle que vous avez utilisée pour la vidéo locale. La différence majeure est que vous devez maintenant créer cette structure de manière dynamique à l'aide de l'API DOM du navigateur. Il s'agit du balisage que vous devez créer pour chaque participant :

<div id="{{ participant.sid }}" class="participant">
    <div></div>  <!-- the video and audio tracks will be attached to this div -->
    <div>{{ participant.name }}</div>
</div>

Au début de la fonction participantConnected(), vous pouvez voir que vous créez un participant_div, auquel vous ajoutez un élément tracks_div et un élément label_div comme enfants. Enfin, vous ajoutez le participant_div comme enfant de container, qui est l'élément <div> de niveau supérieur où vous avez tous les participants de l'appel.

La deuxième partie de la fonction concerne l'association des pistes audio et vidéo à l'élément tracks_div que vous venez de créer. Vous exécutez une boucle sur toutes les pistes que les participants exportent et, suivant l'utilisation de base indiquée dans la documentation de la bibliothèque, vous attachez celles auxquelles vous êtes abonnés. La fixation de piste réelle est gérée dans une fonction auxiliaire trackSubscribed() définie juste en dessous.

Dans les utilisations plus avancées de cette bibliothèque, un participant peut ajouter ou supprimer dynamiquement des pistes pendant un appel (par exemple, s'il doit désactiver temporairement sa vidéo, couper son son ou même commencer à partager son écran). Comme vous voulez répondre à tous ces changements de suivi, vous créez également des gestionnaires d'événements pour les événements trackSubscribed et trackUnsubscribed, qui utilisent les méthodes attach() et detach() de l'objet de suivi pour ajouter et supprimer les éléments HTML qui transportent les flux.

Déconnexion de la salle de conversation

La contrepartie de la fonction connect() est disconnect(), qui doit restaurer la page à son état précédent avant la connexion. C'est beaucoup plus simple, car cela implique principalement de supprimer tous les enfants de l'élément container à l'exception du premier, qui est votre flux vidéo local.

function disconnect() {
    room.disconnect();
    while (container.lastChild.id != 'local')
        container.removeChild(container.lastChild);
    button.innerHTML = 'Join call';
    connected = false;
    updateParticipantCount();
};

Comme vous pouvez le voir ici, vous supprimez tous les enfants de l'élément container à partir de la fin et jusqu'à arriver au <div> avec l'ID local, qui est celui que vous avez créé statiquement dans la page index.html. Vous profitez également de l'occasion pour mettre à jour votre variable globale connected, modifier le texte du bouton de connexion et actualiser l'élément <p> pour afficher un message « Disconnected » (Déconnecté).

Exécution de votre serveur de chat vidéo

Si vous avez démarré votre serveur Web au début de ce tutoriel, assurez-vous qu'il est toujours en cours d'exécution. S'il ne fonctionne pas, démarrez-le une fois de plus à l'aide de la commande suivante :

(venv) $ FLASK_ENV=development flask run

Si vous utilisez un ordinateur Windows, utilisez les commandes suivantes à la place :

(venv) $ set FLASK_ENV=development
(venv) $ flask run

Lorsque le serveur est en cours d'exécution, vous pouvez vous connecter à partir du même ordinateur en saisissant http://localhost:5000 dans la barre d'adresse de votre navigateur Web. Bien évidemment, vous voudrez probablement également vous connecter à partir d'un deuxième ordinateur ou d'un smartphone, ou peut-être même inviter un ami à rejoindre votre chat vidéo. Cela nécessite une étape supplémentaire, car le serveur ne fonctionne qu'en interne sur votre ordinateur et n'est pas accessible depuis Internet.

Il existe plusieurs façons d'exposer le serveur à Internet. Un moyen rapide et facile de le faire est d'utiliser ngrok, un utilitaire pratique qui crée un tunnel entre notre serveur local en cours d'exécution et une URL publique sur le domaine ngrok.io. Si ngrok n'est pas installé, vous pouvez en télécharger une copie pour Windows, MacOS ou Linux.

Une fois le serveur Flask en cours d'exécution, ouvrez une seconde fenêtre de terminal et démarrez ngrok comme suit :

$ ngrok http 5000

Votre terminal affiche maintenant un élément similaire à cet écran :

capture d&#x27;écran ngrok

Recherchez les lignes Forwarding pour voir quelle est l'URL publique attribuée par ngrok à votre serveur. Utilisez celle qui commence par https://, car de nombreux navigateurs n'autorisent pas les sites non cryptés à accéder à la caméra et au microphone. Dans l'exemple ci-dessus, l'URL publique est https://bbf1b72b.ngrok.io. La vôtre sera similaire, mais le premier composant du domaine sera différent chaque fois que vous démarrerez ngrok.

En exécutant le serveur Flask et ngrok sur votre ordinateur, vous pouvez utiliser l'URL https:// publique de ngrok pour vous connecter à votre serveur à partir d'autres ordinateurs et smartphones. Vous êtes maintenant prêt à inviter vos amis à discuter en vidéo avec vous !

Conclusion

J'espère que ce tutoriel a été plaisant et intéressant. Si vous décidez de l'utiliser comme base pour créer votre propre projet de chat vidéo, vous devez savoir que l'API Twilio Programmable Video dispose de nombreuses autres fonctionnalités que nous n'avons pas explorées, y compris l'option pour :

J'ai hâte de voir ce que vous allez construire !

Miguel Grinberg est développeur Python pour le contenu technique chez Twilio. Écrivez-lui sur mgrinberg [at] twilio [dot] com si vous avez un super projet Python que vous souhaitez partager sur le blog de Twilio !