Partage d'écran avec JavaScript et Twilio Programmable Video

July 15, 2020
Rédigé par
Révisé par

Partage d'écran avec JavaScript et Twilio Programmable Video

L'API Twilio Programmable Video vous permet de créer des applications de chat vidéo personnalisées selon la norme WebRTC. Dans cet article, je vais vous montrer comment ajouter une option de partage d'écran à une application Programmable Video basée sur navigateur et créée en JavaScript.

démonstration de l'application fine

Prérequis pour ce tutoriel

Dans ce tutoriel, nous allons ajouter une fonctionnalité de partage d'écran à l'application de chat vidéo construite avec JavaScript et Python dans le cadre d'un tutoriel précédent. Pour exécuter cette application sur votre ordinateur, vous avez besoin de la configuration suivante :

  • 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.
  • 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 (une liste les répertoriant est disponible ci-dessous). Notez que cette exigence s'applique également aux utilisateurs que vous avez l'intention d'inviter à utiliser cette application une fois construite.

La fonctionnalité vidéo et audio de base de ce projet étant fournie par Twilio Programmable Video, nous devrons utiliser l'un des navigateurs Web pris en charge répertoriés ci-dessous :

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

Bien que la liste des navigateurs prenant en charge les appels vidéo soit assez étendue et que tous puissent afficher des pistes de partage d'écran, seul un sous-ensemble de ces navigateurs peut démarrer une session de partage d'écran. En particulier, aucun navigateur mobile ne peut le faire, et sur les ordinateurs de bureau, les versions suivantes sont requises :

  • Chrome 72+
  • Firefox 66+
  • Safari 12.2+

Consultez la documentation relative à Programmable Video pour obtenir la liste la plus récente des navigateurs Web pris en charge et la page Screen Capture pour connaître les versions de navigateur prenant en charge cette fonctionnalité.

Installation et exécution de l'application du didacticiel

Commençons par configurer l'exemple d'application. Cette application est disponible sur GitHub. Si vous avez installé le client git, vous pouvez la télécharger de la manière suivante :

$ git clone https://github.com/miguelgrinberg/flask-twilio-video

La branche master de ce repository inclut déjà tout le code permettant de prendre en charge la fonction de partage d'écran. Si vous prévoyez de coder pendant ce tutoriel, passez à la branche only-video-sharing à l'aide de la commande suivante :

$ git checkout only-video-sharing

Si vous n'avez pas installé le client git, vous pouvez également télécharger l'application complète sous forme de fichier zip. Ou si vous avez l'intention de coder pendant le tutoriel, seulement la partie d'appel vidéo.

Création d'un environnement virtuel Python

Une fois que vous aurez téléchargé et configuré le code, nous créerons un environnement virtuel dans lequel nous pourrons installer nos dépendances Python.

Si vous utilisez un système Unix ou MacOS, ouvrez un terminal, accédez au répertoire du projet et entrez les commandes suivantes :

$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install -r requirements.txt

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

$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install -r requirements.txt

La dernière commande utilise pip, le programme d'installation de package Python, pour installer les packages Python utilisés par cette application. Ces packages sont les suivants :

  • La bibliothèque Twilio Python Helper, pour travailler avec les API Twilio
  • Le framework Flask, pour créer l'application Web
  • Python-dotenv, pour importer le contenu de notre fichier .env en tant que variables d'environnement
  • Pyngrok, pour exposer temporairement sur Internet la version de développement de notre application

Configuration de votre compte Twilio

Cette application doit s'authentifier auprès du service Twilio à l'aide des identifiants associés à votre compte. Vous aurez besoin en particulier du SID de votre compte, du SID de clé API et de son secret de clé API correspondant. Si vous ne savez pas comment obtenir ces identifiants, je vous suggère de consulter les instructions de la section « Configuration de votre compte Twilio » du tutoriel de partage vidéo.

L'application inclut un fichier nommé .env.template qui comporte les trois variables de configuration nécessaires. Faites une copie de ce fichier avec le nom .env (point env) et modifiez-le comme suit :

TWILIO_ACCOUNT_SID="<enter your Twilio account SID here>"
TWILIO_API_KEY_SID="<enter your Twilio API key here>"
TWILIO_API_KEY_SECRET="<enter your Twilio API secret here>"

Exécution de l'application

L'application doit maintenant être prête à être exécutée. Assurez-vous que l'environnement virtuel est activé, puis utilisez la commande suivante pour démarrer le serveur Web :

(venv) $ flask run
 * 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

L'application est en cours d'exécution, mais elle ne peut recevoir que des connexions locales provenant du même ordinateur. Pour attribuer une URL publique temporaire, nous permettant de nous connecter à partir d'un téléphone ou d'un autre ordinateur, nous utiliserons ngrok, qui est déjà installé dans l'environnement virtuel Python. Pour démarrer ngrok, ouvrez une seconde fenêtre de terminal, activez l'environnement virtuel (source venv/bin/activate ou venv\scripts\activate selon votre système d'exploitation), puis entrez la commande suivante :

(venv) $ ngrok http 5000

Le second terminal affiche maintenant une sortie similaire à cet écran :

capture d&#x27;écran ngrok

Ngrok attribue une URL publique à votre serveur. Recherchez les valeurs répertoriées par rapport aux clés « Forwarding » pour voir à quoi elle correspond. Nous allons utiliser l'URL 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 exécuterez 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'une source externe, telle qu'un autre ordinateur ou un smartphone.

Si vous souhaitez mieux comprendre certains aspects de cette application, le premier tutoriel vous donnera toutes les réponses dont vous avez besoin.

Introduction à l'API getDisplayMedia

Pour capturer le flux vidéo de l'écran d'un utilisateur, nous allons utiliser l'API getDisplayMedia du navigateur. En supposant que la salle soit stockée dans une variable room, l'extrait de code suivant démarre une session de partage d'écran et la publie dans la salle :

        navigator.mediaDevices.getDisplayMedia().then(stream => {
            screenTrack = new Twilio.Video.LocalVideoTrack(stream.getTracks()[0]);
            room.localParticipant.publishTrack(screenTrack);
        }).catch(() => {
            alert('Could not share the screen.')
        });

L'appel getDisplayMedia() invite l'utilisateur à sélectionner ce qu'il souhaite partager. L'implémentation de cette sélection est fournie par le navigateur Web. Voici comment il se présente sur Chrome :

séléction du partage d&#x27;écrans dans chrome

Ici, l'utilisateur peut choisir de partager un écran complet, une seule fenêtre ou même un onglet de navigateur. Une fois la sélection effectuée, la piste vidéo est créée et publiée pour l'appel. À ce stade, tous les autres participants recevront l'événement trackSubscribed, qui alerte également l'application lorsque la piste vidéo d'un participant est publiée.

Pour arrêter le partage d'un écran, nous devons annuler la publication de la piste de l'appel, puis arrêter la piste vidéo. Pour ce faire, nous utilisons le code suivant :

        room.localParticipant.unpublishTrack(screenTrack);
        screenTrack.stop();
        screenTrack = null;

Pour en savoir plus sur le partage d'écran avec l'API Twilio Programmable Video, consultez la documentation.

Améliorations de la mise en page

Avant d'intégrer le partage d'écran dans l'application, nous devons apporter quelques modifications à la mise en page. L'une d'entre elles consiste à ajouter un bouton « Share screen » (Partager l'écran), que nous allons placer à côté du bouton « Join call » (Rejoindre l'appel).

bouton de partage d&#x27;écran

La mise en page actuelle suppose que chaque participant aura une seule piste vidéo qui est présentée avec le nom en dessous. Lorsqu'un participant ajoute une piste de partage d'écran, le nom couvre les deux pistes. Pour préciser qu'un participant partage un écran, nous allons ajouter une couleur d'arrière-plan à l'élément <div> qui affiche le nom. Voici à quoi ressemble la mise en page lorsqu'un participant partage uniquement sa caméra :

Example participant

Lorsque le participant commence à partager son écran en plus de la vidéo, le nom est centré sous les deux pistes. La couleur d'arrière-plan permet d'indiquer qui est propriétaire de la piste d'écran :

exemple de participant avec partage d&#x27;écran

Faisons ces modifications. Tout d'abord, ajoutons le bouton de partage d'écran à la page HTML de base. Voici la version mise à jour du fichier *templates/index.html* pour que vous puissiez remplacer tout le code par ce qui suit :

<!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>
            <button id="share_screen" disabled>Share screen</button>
        </form>
        <p id="count">Disconnected.</p>
        <div id="container" class="container">
            <div id="local" class="participant"><div></div><div class="label">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>

Notez que nous ajoutons le bouton de partage d'écran à l'état désactivé, car vous devez être en communication avant de pouvoir utiliser cette fonction.

En plus du nouveau bouton, j'ai ajouté une classe label à l'élément <div> qui contient le nom du participant. Cela facilitera l'ajout de la couleur d'arrière-plan dans le fichier CSS.

Le fichier static/styles.css mis à jour, comprenant la nouvelle couleur d'arrière-plan de l'étiquette et un léger nettoyage, est illustré ci-dessous :

.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 video {
    width: 240px;
    height: 180px;
    background-color: #ccc;
    border: 1px solid black;
}
.participant .label {
    background-color: #ddd;
}

La classe label doit également être ajoutée à chaque participant dans la fonction participantConnected() du fichier static/app.js :

function participantConnected(participant) {
    // ...
    var labelDiv = document.createElement('div');
    labelDiv.setAttribute('class', 'label');
    labelDiv.innerHTML = participant.identity;
    participantDiv.appendChild(labelDiv);
    // ...

Démarrage d'une session de partage d'écran

Nous sommes maintenant prêts à implémenter le partage d'écran dans le fichier app.js. En haut du fichier, ajoutez une instance du nouveau bouton et une nouvelle variable qui contiendra la piste de partage d'écran locale :

const shareScreen = document.getElementById('share_screen');
var screenTrack;

En bas, associez un gestionnaire à l'événement click sur ce bouton :

shareScreen.addEventListener('click', shareScreenHandler);

Ensuite, n'importe où dans le fichier, nous pouvons ajouter notre gestionnaire de partage d'écran :

function shareScreenHandler() {
    event.preventDefault();
    if (!screenTrack) {
        navigator.mediaDevices.getDisplayMedia().then(stream => {
            screenTrack = new Twilio.Video.LocalVideoTrack(stream.getTracks()[0]);
            room.localParticipant.publishTrack(screenTrack);
            shareScreen.innerHTML = 'Stop sharing';
            screenTrack.mediaStreamTrack.onended = () => { shareScreenHandler() };
        }).catch(() => {
            alert('Could not share the screen.');
        });
    }
    else {
        room.localParticipant.unpublishTrack(screenTrack);
        screenTrack.stop();
        screenTrack = null;
        shareScreen.innerHTML = 'Share screen';
    }
};

Nous utilisons la variable screenTrack non seulement pour contenir la piste vidéo, mais aussi pour savoir si le partage d'écran est activé ou non. Lorsque cette variable a une valeur erronée, nous savons que le partage d'écran n'est pas activé, donc nous commençons une nouvelle session, en utilisant la technique indiquée ci-dessus. Nous modifions également le libellé du bouton afin qu'il corresponde à « Stop sharing » (Arrêter le partage).

Nous définissons aussi l'événement onended sur la piste de partage d'écran. Certains navigateurs fournissent leur propre interface utilisateur pour mettre fin à une session de partage d'écran. Chrome affiche ce widget flottant, par exemple :

pop up de partage d&#x27;écrans dans chrome

L'arrêt du flux en cliquant sur ce bouton « Hide » (Masquer) met fin au partage d'écran. Cependant, l'application et l'API Twilio Video ne savent pas que le partage d'écran est terminé et continueront d'afficher la piste avec une image figée ou noire à tous les participants. L'événement onended est un moyen de recevoir un rappel lorsque l'utilisateur termine le flux de cette façon. Il nous suffit d'envoyer le rappel à notre fonction de gestionnaire pour effectuer le nettoyage approprié.

Le dernier ensemble de modifications concerne l'état du bouton de partage d'écran, qui commence en étant désactivé. Une fois que le participant se connecte à un appel, nous pouvons activer ce bouton et le désactiver à nouveau lors de la déconnexion :

function connectButtonHandler(event) {
    event.preventDefault();
    if (!connected) {
        // ...
        connect(username).then(() => {
            // ...
            shareScreen.disabled = false;
        }).catch(() => {
        // ...
       });
    }
   else {
        disconnect();
        # ...
        shareScreen.innerHTML = 'Share screen';
        shareScreen.disabled = true;
    }
};

Nous avons mis à jour le libellé afin qu'il indique « Stop sharing » (Arrêter le partage) lorsqu'une session de partage d'écran est lancée. Nous allons maintenant devoir le réinitialiser lorsqu'un participant se déconnecte.

Avec ces modifications, la fonction de partage d'écran de base est maintenant terminée. Exécutez l'application, avec ngrok, connectez-vous à un appel vidéo à partir d'au moins deux fenêtres de navigateur différentes (sur le même appareil ou sur des appareils différents), et essayez de partager votre écran de l'un à l'autre !

Ajout d'une fonction plein écran

Lors du partage de vidéos, avoir de grandes pistes vidéo n'est pas une préoccupation majeure. Cependant, lors du partage d'écran, l'affichage d'une piste vidéo de petite taille rendra la plupart du texte illisible. Pour rendre le partage d'écran plus facile à utiliser, nous pouvons ajouter une fonction de zoom qui met toute piste vidéo en plein écran en cliquant simplement dessus :

démo plein écran

Pour effectuer un zoom sur une piste vidéo, nous allons affecter à la piste une nouvelle classe CSS appelée participantZoomed, et en même temps nous allons affecter une classe participantHidden à toutes les autres pistes. Voici les fichiers static/styles.css mis à jour avec ces nouvelles classes :

.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 video {
    background-color: #ccc;
    border: 1px solid black;
}
.participant div video:not(.participantZoomed) {
    width: 240px;
    height: 180px;
}
.participant .label {
    background-color: #ddd;
}
.participantZoomed {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
}
.participantHidden {
    display: none;
}

Nous devons ensuite ajouter des gestionnaires d'événement click dans toutes les pistes. Pour la piste vidéo locale, modifiez la fonction addLocalVideo() :

function addLocalVideo() {
    Twilio.Video.createLocalVideoTrack().then(track => {
        var video = document.getElementById('local').firstChild;
        var trackElement = track.attach();
        trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
        video.appendChild(trackElement);
    });
};

Pour les pistes vidéo d'autres participants, nous pouvons ajouter le gestionnaire dans la fonction trackSubscribed() :

function trackSubscribed(div, track) {
    var trackElement = track.attach();
    trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
    div.appendChild(trackElement);
};

Le gestionnaire zoomTrack() est illustré ci-dessous :

function zoomTrack(trackElement) {
    if (!trackElement.classList.contains('participantZoomed')) {
        // zoom in
        container.childNodes.forEach(participant => {
            if (participant.className == 'participant') {
                participant.childNodes[0].childNodes.forEach(track => {
                    if (track === trackElement) {
                        track.classList.add('participantZoomed')
                    }
                    else {
                        track.classList.add('participantHidden')
                    }
                });
                participant.childNodes[1].classList.add('participantHidden');
            }
        });
    }
    else {
        // zoom out
        container.childNodes.forEach(participant => {
            if (participant.className == 'participant') {
                participant.childNodes[0].childNodes.forEach(track => {
                    if (track === trackElement) {
                        track.classList.remove('participantZoomed');
                    }
                    else {
                        track.classList.remove('participantHidden');
                    }
                });
                participant.childNodes[1].classList.remove('participantHidden');
            }
        });
    }
};

La procédure de zoom avant effectue une itération sur tous les éléments de l'élément div container, qui sont les participants à l'appel. Une itération est effectuée sur les pistes de chaque participant, en appliquant participantZoomed à la piste sélectionnée et participantHidden à toutes les autres. La classe masquée est également appliquée à l'élément <div> qui contient le nom du participant. Le processus de zoom arrière se déroule de la même façon, en sens inverse.

Une complication peut résulter de ces modifications lors de l'annulation de la publication d'une piste en cours de zoom avant dans l'appel. Dans ce cas, nous devons exécuter la procédure de zoom arrière avant de libérer la piste. Nous pouvons le faire dans le gestionnaire trackUnsubscribed() :

function trackUnsubscribed(track) {
    track.detach().forEach(element => {
        if (element.classList.contains('participantZoomed')) {
            zoomTrack(element);
        }
        element.remove()
    });
};

La fonction de partage d'écran est terminée !

Conclusion

J'espère qu'en suivant ce tutoriel, vous pourrez ajouter le partage d'écran à votre propre application Twilio Programmable Video.

Si vous avez besoin de prendre en charge le partage d'écran dans d'autres navigateurs que Chrome, Firefox et Safari, ou peut-être dans des versions plus anciennes de ces navigateurs, mon collègue Phil Nash a rédigé quelques tutoriels susceptibles de vous aider :

J'ai hâte de voir les super applications de chat vidéo que vous allez construire !

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