Greenscreen showing three participants on the screen. A participant with a blue shirt on the left, a participant with a black t-shirt at the top right, and a purple robot at the bottom right.

Comment créer un écran vert virtuel à l'aide de l'API Video de Vonage

Publié le August 19, 2025

Temps de lecture : 10 minutes

Introduction

Cet article décrit comment utiliser l'API Video de Vonage pour superposer des présentateurs sur du contenu partagé en utilisant le remplacement d'arrière-plan et le canevas HTML5 pour créer un écran vert virtuel.

Vous pouvez trouver le code sur GitHub.

Speaker shows in front of the background without the greenscale background.Presentation Example

L'utilisation d'écrans verts virtuels pour sauver l'immobilier de bureau

Lors d'une présentation en ligne, les présentateurs et les participants doivent partager l'espace disponible sur le bureau avec leurs présentations et autres médias. Cela peut souvent rendre le contenu trop petit pour être lu par de nombreux participants. Dans l'exemple ci-dessus, nous avons utilisé les fonctions de Video API de Vonage pour créer un écran vert virtuel afin que le présentateur puisse s'afficher au-dessus de la présentation. Cela permet à la présentation elle-même d'occuper le devant de la scène, où tout le monde peut la voir en entier.

Quelques obstacles initiaux

Le principal problème pour réaliser quelque chose comme cela est que la communication en temps réel sur le Web (WebRTC) et les codecs Video n'ont pas tendance à prendre en charge la transparence. L'envoi de vidéos transparentes sur Internet ne fonctionnera pas. Cependant, l'API Video de Vonage offre certaines fonctionnalités qui peuvent aider, notamment le remplacement de l'arrière-plan. En savoir plus sur Filtres et effets dans notre documentation.

Dans ce tutoriel, nous utiliserons la fonction de remplacement de l'arrière-plan avec une image d'arrière-plan d'une seule couleur pour créer un écran vert virtuel. Cela nous permet de laisser nos transformateurs Video faire ce qu'ils ont à faire pour fournir une détection et un remplacement d'arrière-plan propres. Nous utiliserons une petite image (16x16) avec la couleur unie RGB(61,180,60). Cette image est incluse dans le dépôt GitHub pour ce tutoriel, ainsi que tout le code dont vous avez besoin pour exécuter l'exemple.

A green squaregreenscale backgroundLa procédure ci-dessous permet de supprimer complètement l'arrière-plan de sorte que, lors du rendu, la vidéo puisse être affichée par-dessus des médias existants, des pages web ou d'autres vidéos.

A workflow that read "How it works" there's a ballon that starts from an image of a video, followed by background replacement, followed by a picture of the speaker showing a screen scale background followed by Video API Session, followed to the same speaker picture with a greenscale background, followed by green screen processor subscriber followed by the picture of the speaker image added to the front layer without a green background.Application Workflow

Conditions préalables

Avant de commencer, veuillez consulter le dépôt dépôt GitHub afin que vous puissiez télécharger le code et exécuter les exemples.

Vous aurez besoin de :

  • Une certaine expérience du JavaScript et du HTML5 Canvas.

  • Une expérience préalable avec l Video API de Vonage de Vonage est préférable, mais pas indispensable.

  • Une application Video de base est incluse dans ce projet, mais n'entre pas dans le cadre de ce tutoriel. Vous trouverez d'autres exemples dans notre Référentiel d'échantillons Video Web.

  • Vous aurez besoin d'un appareil photo et d'un microphone en état de marche pour réaliser l'échantillon.

  • Une clé de l'API Video de Vonage, un ID de session et un jeton. Vous pouvez les créer dans le tableau de bord ou via le Video API Playground. Vous pouvez y accéder par le biais de votre compte API en ligne.

Aperçu des applications

L'exemple d'application comprend plusieurs éléments. Une page d'accueil (app.html) avec un formulaire qui permet d'identifier les utilisateurs et les rôles à utiliser dans l'application. La page principale de l'application (liveroom.html) charge le Video JavaScript SDK et exécute notre code JavaScript. Dans cet exemple, le code est chargé sous forme de modules ES6.

Préparer la salle et la session

Lorsque la page liveroom.html est chargée, le nom de l'utilisateur, le nom de la salle et le rôle sont inclus dans l'URL en tant que paramètres. Ceux-ci sont collectés et transmis au constructeur de la classe Liveroom pour créer la salle.

let urlRoomName = new URLSearchParams(window.location.search).get('roomName')

let urlUserName = new URLSearchParams(window.location.search).get('userName')

let urlUserRole = new URLSearchParams(window.location.search).get('userRole')

Une fois que nous avons vérifié que les paramètres sont valides et remplis, nous pouvons créer la salle.

const liveroom = new Liveroom(urlRoomName, urlUserName, urlUserRole)

L'objet Liveroom va d'abord faire un appel pour récupérer les informations d'identification de la session auprès d'un serveur. Pour simplifier les choses, nous l'avons configuré pour obtenir un fichier config.json dans le dossier actuel, qui contient des informations d'identification de session valides. Vous devrez saisir votre ID de session, votre clé API et votre jeton dans ce fichier avant d'exécuter l'exemple.

// config.json

{

   "apiKey": "your api key",

   "sessionId": "your session id",

   "token": "your token"

}

Liveroom instancie également un Display objet.

this.display = new Display()

La classe Display gère la largeur et la hauteur de la zone d'affichage et ajoute un écouteur pour s'assurer que ces attributs sont mis à jour lorsque la fenêtre de l'application est redimensionnée. Le code de cette classe se trouve dans le display.js fichier.

Connecter la session API Video de Vonage

Une fois que l'application dispose des informations d'identification, la classe Liveroom créera alors un objet Video qui gérera le média et se connectera à la session vonage.

this.getVideoCredentials(this.roomName)

       .then(()=>{

           this.video = new Video(this.sessionCredentials, {})

           return this.video.connectSession()

       })

       .then(()=>{

           console.log(`User Role: ${this.userRole}`)

           if(this.userRole == 'composer'){

               this.display.enableComposerMode()

               this.composer = true

           }

           if(this.userRole != 'viewer'){

               this.video.publishCamera(this.userRole)

           }

           if(this.userRole == 'presenter'){

               this.video.publishScreen()

           }

           this.updateDisplay(this.video.participants, this.video.mainstage)

       })

       .catch((error)=>{

           console.log(error)

       })

Nous utilisons des promesses pour nous assurer que nous ne lançons rien tant que toutes les informations et tous les objets requis ne sont pas en place. Tout d'abord, les informations d'identification sont recueillies et transmises au constructeur de la classe Video. Celui-ci appelle alors la fonction connectSession() qui connecte le SDK JavaScript Video à l'identifiant de session. Une fois que cela est fait, les fonctions membres appropriées sont lancées en fonction du rôle de l'utilisateur qui a été transmis.

Envoyer et recevoir des médias

Comme nous l'avons mentionné précédemment, nous ne pouvons pas envoyer de transparence via WebRTC, nous devons donc remplacer l'arrière-plan par l'effet d'écran vert. Ceci est géré par la fonction publishCamera() Cette fonction crée un nouvel objet CameraPublisher, qui utilise les paramètres suivants pour créer un effet d'écran vert Cette fonction crée un nouvel objet CameraPublisher, qui utilise les paramètres suivants pour créer l'éditeur :

 let publisherOptions = {

           showControls: false,

           videoFilter: {

               type: "backgroundReplacement",

               backgroundImgUrl: "/images/greenscreen.png" // r:61 g:180 b:60    #3db43c

           }

       }

Cela demande au SDK de remplacer l'arrière-plan par notre image greenscreen.png. Étant donné qu'elle s'étire et se met à l'échelle et qu'elle n'est que d'une seule couleur, le fichier ne fait que 16x16 pixels.

Lorsque nous créons l'éditeur, nous transmettons l'objet HTML DOM auquel nous voulons que la vidéo soit ajoutée. Dans ce cas, il y a un objet DIV qui est membre de la classe CameraPublisher . Cet objet DIV est caché à l'aide de CSS display: none; car il contiendra la vidéo non traitée.

Nous créons également un objet HTML5 Canvas pour recevoir la sortie de la vidéo avec transparence. Cet objet est également membre de l'objet et s'appelle outputCanvas.

  this.publisher = OT.initPublisher(this.publisherDiv, publisherOptions, (event)=>{

           this.videoElement = this.publisherDiv.querySelector("video")

           this.videoElement.onloadeddata = (event)=>{

               console.log("Video Loaded Data")

               this.renderer = new VideoRenderer(this.videoElement, this.outputCanvas)

               this.renderer.processFrames()

               this.session.publish(this.publisher)

           }

       })

Dans ce code, nous passons également une fonction au rappel de la fonction OT.initPublisher() . Lorsque cette fonction s'exécute, nous savons que l'élément Video aura été ajouté à notre DIV. Cela signifie que nous pouvons utiliser la fonction querySelector() pour le trouver et ajouter une autre fonction de rappel. Celle-ci s'exécute lorsque le média vidéo est prêt et crée l'instance VideoRenderer instance.

Une fois que nous avons l'élément Video et un canevas sur lequel écrire, ce code crée un nouvel objet VideoRenderer.

Pour recevoir les médias, nous suivons le même processus dans la classe CameraSubscriber() en créant une autre instance de la classe VideoRenderer pour rendre la vidéo reçue.

Rendu de la Video

La classe VideoRenderer est l'élément final qui permet de faire fonctionner le système. Elle prend un élément d'entrée <video> et un élément de sortie <canvas> HTML5 de sortie. Lorsqu'elle est activée, elle copie l'image vidéo de l'élément VIDEO. Elle la colle avec un fond vert dans un CANVAS interne ("builderCanvas"), ce qui nous permet d'accéder aux données en pixels de chaque image vidéo.

Regardons de plus près ce que fait le VideoRenderer et comment il fonctionne.

constructor(sourcevideo, outputcanvas){

       let randomIcon = (Math.floor(Math.random()*14)+1) + '.png'

       this.imageIcon = new Image()

       this.imageIcon.src = /images/${randomIcon}

       console.log('Random icon for user: ', this.imageIcon.src)

      

       this.targetFPS = 30

       this.videoElement = sourcevideo

       this.width = sourcevideo.videoWidth

       this.height = sourcevideo.videoHeight

       this.outputCanvas = outputcanvas

       this.builderCanvas = document.createElement("canvas")

       this.enabled = false

   }

Le constructeur de la classe met en place notre canevas de construction et contient également quelques fonctions permettant de remplacer le flux vidéo par une image aléatoire si la vidéo est désactivée.

Ensuite, nous avons deux courtes fonctions qui permettent d'activer et de désactiver le moteur de rendu vidéo. Cela permet d'économiser des ressources pour les participants qui ne sont pas affichés ou qui sont en sourdine.

   enable(){

       if(!this.enabled){

          this.enabled = true

           this.processFrames()

       }      

   }

   disable(){

       this.enabled = false

   }

Ensuite, nous avons la fonction processFrames() . Elle est appelée pour la première fois lorsque le moteur de rendu est activé. Elle est appelée pour la première fois lorsque le moteur de rendu est activé et utilise ensuite la fonction requestAnimationFrame() pour s'appeler à nouveau. Le fait de demander une image d'animation de cette manière réduit l'utilisation des ressources lorsque l'image n'est pas affichée.

La fonction processFrames se termine si le moteur de rendu n'est pas activé. Cela désactive complètement le moteur de rendu.

   processFrames() {

      if(!this.enabled) return

}

Ensuite, il capture la largeur et la hauteur de l'élément Video pour s'assurer que toutes nos toiles ont les mêmes dimensions. Il crée également une variable sourceImage comme pointeur sur l'élément Video. Si la vidéo n'est pas activée, la variable sourceImage est définie sur l'imageIcon sélectionnée par le constructeur.

this.width = this.videoElement.videoWidth

       this.height = this.videoElement.videoHeight

       let sourceImage = this.videoElement

      

       if(!this.videoElement.srcObject.getVideoTracks()[0].enabled){

           sourceImage = this.imageIcon

           this.width = sourceImage.width

           this.height = sourceImage.height

       }

La largeur du canevas pour le constructeur est définie et le contexte du canevas est créé. Ce contexte builderCtx nous permet de lire et d'écrire dans le canevas. Tout d'abord, nous l'effaçons, puis nous dessinons notre image source sur le builderCanvas.

       this.builderCanvas.width = this.width

       this.builderCanvas.height = this.height

       let builderCtx = this.builderCanvas.getContext("2d")

       builderCtx.clearRect(0,0,this.width, this.height)

       builderCtx.drawImage(sourceImage, 0, 0, this.width, this.height)

Avec les builderCanvas et context nous passons ensuite à l'étape outputCanvas. Nous nous assurons que la largeur et la hauteur correspondent à notre média source.

REMARQUE : cette opération ne modifie pas la largeur et la hauteur d'affichage en CSS. Elle ne modifie que la largeur et la hauteur du canevas interne. Le DOM mettra à l'échelle le canevas interne pour qu'il corresponde au nœud externe.

       this.outputCanvas.width = this.width

       this.outputCanvas.height = this.height

       let outputCtx = this.outputCanvas.getContext("2d")

Nous avons maintenant mis en place nos contextes de construction et de sortie. Pour continuer, nous créons un objet ImageData dans le contexte du constructeur. Cela permet d'accéder aux données au niveau du pixel dans le canevas à travers le tableau ImageData.data tableau. Il s'agit d'un tableau plat unidimensionnel contenant les attributs rouge, vert, bleu et alpha de chaque pixel, présentés de manière séquentielle.

var imgdata = builderCtx.getImageData(0, 0, this.width, this.height);

var pix = imgdata.data;

Notre fonction doit parcourir tous ces pixels dans le tableau et évaluer chacun d'entre eux pour déterminer s'il correspond au vert de notre arrière-plan. Pour ce faire, elle utilise la fonction adjustPixel() que nous aborderons prochainement.

 for (var i = 0, n = pix.length; i < n; i += 4) {

           let r = pix[i]

           let g = pix[i+1]

           let b = pix[i+2]

           let a = pix[i+3]

           let newColor = this.adjustPixel(r,g,b,a)

           pix[i] = newColor.r

           pix[i+1] = newColor.g

           pix[i+2] = newColor.b

           pix[i+3] = newColor.a      

       }

Une fois que tous les pixels de notre imgdata ont été mis à jour, nous effaçons le contexte du constructeur et repoussons l'élément imgdata dans le canevas. Nous pouvons alors dessiner à partir de ce canevas caché vers notre contexte visible. outputCanvas visible. Avec requestAnimationFrame()cette fonction sera exécutée entre 30 et 60 fois par seconde.

       builderCtx.clearRect(0,0,this.width,this.height)

       builderCtx.putImageData(imgdata, 0, 0)

       outputCtx.drawImage(this.builderCanvas, 0, 0, this.width, this.height)

       this.animationFrameId = window.requestAnimationFrame(()=>{this.processFrames()})

   }

Définir l'opacité des pixels

La fonction adjustPixel() de la classe VideoRenderer accepte une couleur avec des valeurs rouges, vertes, bleues et alpha indépendantes. Elle renvoie ensuite un objet avec quatre membres, r, g, b et a. La première ligne crée un objet output objet.

adjustPixel(r,g,b,a){

  let c = {r: r, g: g, b: b, a: a}
}

La fonction prend ensuite une moyenne des valeurs rouges et bleues et la stocke sous le nom de rb.

       let rb = (r + b) / 2

La ligne de code suivante détermine si le pixel doit être transparent ou non. Elle utilise d'abord la fonction withinRange() pour voir si la différence entre le rouge et le bleu est inférieure à 40. Si le rouge et le bleu ont des valeurs différentes, la couleur ne sera pas verte.

La deuxième partie de l'argument vérifie si la valeur du vert est supérieure d'au moins 20 à la moyenne du rouge et du bleu. Cela couvre un certain nombre de nuances de vert et de teintes qui correspondent le mieux à notre image originale de 16x16.

if((this.withinRange(r, b, 40) && g - rb > 20)){

           c.a = 0

       }

       return c

   }

   withinRange(val1, val2, range){

       let diff = val1 - val2

       if(diff < 0) diff = diff * -1

       if(diff < range){

           return true

       } else {

           return  false

       }

   }

Haut-parleurs multiples et fondu enchaîné

Dans la capture d'écran ci-dessous, vous pouvez voir un exemple de la façon dont plusieurs orateurs ont été pris en compte en faisant disparaître ceux qui ne sont pas en train de parler et en gardant tous les orateurs alignés en bas à gauche. À ce stade, il y a trois intervenants dans l'appel, mais une seule personne peut parler à la fois.

The image shows multiple speakers layered in front of the screen presentation without a greenscale backgroundMultiple Speaker

En supprimant les haut-parleurs inactifs, il est possible d'augmenter le nombre de haut-parleurs et la résolution de l'écran partagé ou de la présentation sans compromettre la qualité de l'expérience.

Exécuter l'échantillon

Vous devez saisir votre clé API, votre ID de session et votre jeton dans le fichier config.json fourni, puis vous pouvez servir ces fichiers sur votre serveur web favori. Si vous utilisez Node, vous pouvez exécuter les commandes suivantes à partir de la racine du dépôt cloné :

npm install 

npm run serve

Ensuite, il suffit d'ouvrir votre navigateur et de vous rendre à l'adresse suivante http://127.0.0.1:3000. Lorsque vous parlez, le micro devrait capter votre voix et activer votre flux vidéo.

Conclusion

C'est tout pour ce tutoriel. Que vous présentiez un diaporama, que vous modifiiez une feuille de calcul ou que vous regardiez une Video, le flux de votre caméra ne s'affichera que lorsque vous parlerez et n'occupera pas d'espace supplémentaire à l'écran. J'espère que vous avez trouvé cela intéressant. D'autres implémentations et améliorations pourraient inclure :

  • Arrière-plans vidéo pour les éditeurs

  • Applications du Watchparty

Le code complet de ce billet se trouve sur GitHub. code complet de cet article peut être trouvé sur GitHub.

Vous avez une question ou souhaitez partager ce que vous construisez ?

Restez connecté et tenez-vous au courant des dernières nouvelles, astuces et événements concernant les développeurs.

Partager:

https://a.storyblok.com/f/270183/689x689/2b79e61b9f/richard-sabbarton.png
Richard SabbartonIngénieur d'assistance Senior Video API

Richard est ingénieur principal du support Video API chez Vonage. Il a plus de 25 ans d'expérience dans le domaine des produits, du support et de l'ingénierie dans les secteurs de la fabrication, des télécommunications et de la mise en réseau.