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.

Cómo crear una pantalla verde virtual con la API de Video de Vonage

Publicado el August 19, 2025

Tiempo de lectura: 10 minutos

Introducción

Este artículo describe cómo usar la Video API de Vonage para superponer presentadores sobre contenido compartido usando el reemplazo de fondo y el lienzo HTML5 para crear una pantalla verde virtual.

Puede encontrar el código en GitHub.

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

Pantallas ecológicas virtuales para ahorrar espacio en el escritorio

Cuando se realizan presentaciones en línea, los presentadores y los participantes tienen que compartir el espacio disponible en el escritorio con sus presentaciones y otros medios. A menudo, esto puede hacer que el contenido sea demasiado pequeño para ser leído por muchos participantes. En el ejemplo anterior, hemos utilizado las funciones de la Video API de Vonage para crear una pantalla verde virtual de modo que el presentador pueda mostrarse encima de la presentación. Esto permite que la presentación ocupe el centro del escenario, donde todos pueden verla en su totalidad.

Algunos obstáculos iniciales

El principal problema para conseguir algo así es que la Web Real-Time Communication (WebRTC) y los códecs de Video no suelen soportar la transparencia. Enviar Video transparente por Internet no va a funcionar. Sin embargo, la Video API de Vonage ofrece algunas funciones que pueden ayudar, y una de ellas es el reemplazo de fondo. Más información sobre Filtros y efectos en nuestra documentación.

Lo que haremos a través de este tutorial es utilizar la función de Reemplazo de Fondo con una imagen de fondo de un solo color para crear una pantalla verde virtual. Esto nos permite dejar que nuestros Transformadores de Video hagan lo que tienen que hacer para proporcionar una detección y reemplazo de fondo limpio. Utilizaremos una imagen pequeña (16x16) con el color sólido RGB(61,180,60). Esta imagen está incluida en el repositorio de GitHub de este tutorial junto con todo el código necesario para ejecutar el ejemplo.

A green squaregreenscale backgroundUtilizando el proceso que se describe a continuación, podemos eliminar el fondo por completo para que, al renderizarlo, el vídeo pueda mostrarse encima de medios existentes, páginas web u otros vídeos.

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

Requisitos previos

Antes de empezar, echa un vistazo al repositorio GitHub para que puedas descargar el código y ejecutar los ejemplos.

Necesitarás:

  • Cierta experiencia con JavaScript y HTML5 Canvas.

  • Alguna experiencia previa con la API de Video de Vonage es preferible, pero no esencial.

  • Con este proyecto se incluye una aplicación de vídeo básica, pero queda fuera del ámbito de este tutorial. Puede encontrar más ejemplos en nuestro Repositorio de Muestras de Video Web.

  • Necesitarás una cámara y un micrófono que funcionen para ejecutar la muestra.

  • Una clave de Video API, un ID de sesión y un token de Vonage. Puedes crearlos en el panel o a través de Video API Playground. Puedes acceder a esto a través de tu cuenta de API en línea.

Aplicaciones

La aplicación de ejemplo consta de varios componentes. Una página de destino (app.html) con un formulario que ayuda a identificar a los usuarios y las funciones que se utilizarán en la aplicación. La página principal de la aplicación (liveroom.html) carga el SDK de Video JavaScript y ejecuta nuestro código JavaScript. En este ejemplo, el código se carga como módulos ES6.

Preparar la sala y la sesión

Cuando se carga la página liveroom.html la página se carga, el nombre del usuario, el nombre de la sala y el rol se incluyen en la URL como parámetros URL. Estos se recogen y se pasan al constructor de la clase Liveroom para crear la sala.

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')

Una vez que tenemos los parámetros y comprobamos que son válidos y están rellenados, podemos crear la sala.

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

El objeto Liveroom hará primero una llamada para obtener las credenciales de sesión de un servidor. Para simplificar las cosas, hemos configurado esto para obtener un archivo config.json de la carpeta actual que contiene credenciales de sesión válidas. Tendrás que introducir tu ID de sesión, API Key y token en este archivo antes de ejecutar el ejemplo.

// config.json

{

   "apiKey": "your api key",

   "sessionId": "your session id",

   "token": "your token"

}

Liveroom también instanciará un objeto Display objeto.

this.display = new Display()

La clase Display gestiona la anchura y la altura del área de visualización y añade una escucha para garantizar que estos atributos se actualizan cuando se cambia el tamaño de la ventana de la aplicación. El código de esta clase se encuentra en el archivo display.js archivo.

Conectar la sesión de Video API de Vonage

Una vez que la aplicación tiene las credenciales, la clase Liveroom creará un objeto Video que gestionará los medios y se conectará a la sesión 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)

       })

Estamos utilizando promesas para asegurarnos de que no lanzamos nada hasta que toda la información y los objetos necesarios estén en su lugar. En primer lugar, se recogen las credenciales y se pasan al constructor de la clase Video. A continuación, se llama a la función connectSession() que conecta el SDK JavaScript de Video con el identificador de sesión. Una vez hecho esto, se lanzan las funciones miembro apropiadas basadas en el rol de usuario que se ha pasado.

Enviar y recibir medios

Como hemos mencionado antes, no podemos enviar transparencia a través de WebRTC, por lo que necesitamos sustituir el fondo por el efecto de pantalla verde. De esto se encarga la función publishCamera() función. Esta función crea un nuevo objeto CameraPublisher, que utiliza la siguiente configuración para crear el editor:

 let publisherOptions = {

           showControls: false,

           videoFilter: {

               type: "backgroundReplacement",

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

           }

       }

Esto indica al SDK que reemplace el fondo con nuestra imagen greenscreen.png. Debido a que se estirará y escalará y es de un solo color, el archivo es de sólo 16x16 píxeles.

Cuando creamos el editor, le pasamos el objeto HTML DOM al que queremos que se añada el vídeo. En este caso, hay un objeto DIV que es miembro de la clase CameraPublisher clase. Este objeto DIV se oculta de la vista con CSS display: none; porque contendrá el vídeo sin procesar.

También creamos un objeto HTML5 Canvas para recibir la salida del Video con transparencia. Este también es un miembro del objeto y se llama 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)

           }

       })

En este código, también pasamos una función a la llamada de retorno de la función OT.initPublisher() función. Cuando esto se ejecuta, sabemos que el elemento Video se habrá añadido a nuestro DIV. Esto significa que podemos utilizar la función querySelector() para encontrarlo y añadir otra función callback. Esto se ejecuta cuando el medio de vídeo está listo y crea el VideoRenderer instancia.

Una vez que tenemos el elemento Video y un canvas donde escribir, este código crea un nuevo objeto VideoRenderer.

Para recibir medios, seguimos el mismo proceso en la clase CameraSubscriber() creando otra instancia de la clase VideoRenderer para renderizar el vídeo recibido.

Renderizar el Video

La clase VideoRenderer es la pieza final para que esto funcione. Toma un elemento <video> y un elemento <canvas> HTML5 de salida. Cuando se activa, copia el fotograma de Video del elemento VIDEO. Lo pega con un fondo verde en un CANVAS interno ("builderCanvas"), lo que nos permite acceder a los datos de píxeles de cada fotograma de vídeo.

Echemos un vistazo más de cerca a lo que hace el VideoRenderer y cómo funciona.

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

   }

El constructor de la clase configura nuestro lienzo constructor, además de contener algunas funciones para sustituir la fuente de vídeo por una imagen aleatoria si el vídeo está desactivado.

A continuación, tenemos dos funciones cortas que simplemente activan y desactivan el renderizador de Video. Esto ayuda a ahorrar recursos para los participantes que no se están visualizando o están silenciados.

   enable(){

       if(!this.enabled){

          this.enabled = true

           this.processFrames()

       }      

   }

   disable(){

       this.enabled = false

   }

A continuación, tenemos la función processFrames() función. Esta se llama por primera vez cuando se activa el renderizador y luego utiliza requestAnimationFrame() para llamarse a sí misma de nuevo. Solicitar un fotograma de animación de esta manera reduce el uso de recursos cuando el fotograma no se está mostrando.

La función processFrames saldrá si el renderizador no está habilitado. Esto desactiva el renderizador por completo.

   processFrames() {

      if(!this.enabled) return

}

A continuación, captura la anchura y la altura del elemento de vídeo para asegurarte de que todos nuestros lienzos tienen las mismas dimensiones. También crea una variable sourceImage como puntero al elemento Video. Si el vídeo no está activado, la sourceImage se establece en el imageIcon seleccionado por el constructor.

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

       }

Se establece la anchura del lienzo para el constructor y se crea el contexto del lienzo. Este contexto builderCtx nos permite leer y escribir en el lienzo. Primero, lo borramos, y luego dibujamos nuestra sourceImage en el 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)

Con los botones builderCanvas y context pasamos a la función outputCanvas. Nos aseguramos de que la anchura y la altura coincidan con nuestros medios de origen.

NOTA: Esto no cambia la anchura y altura de la pantalla en CSS. Sólo cambia la anchura y la altura del lienzo interno. El DOM escalará el lienzo interno para que coincida con el nodo externo.

       this.outputCanvas.width = this.width

       this.outputCanvas.height = this.height

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

Ahora tenemos nuestro constructor y contextos de salida en su lugar. Para proceder, creamos un objeto ImageData del contexto constructor. Esto permite acceder a los datos a nivel de píxel dentro del lienzo a través de la matriz ImageData.data matriz. Se trata de una matriz plana unidimensional que contiene los atributos rojo, verde, azul y alfa de cada píxel, presentados secuencialmente.

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

var pix = imgdata.data;

Nuestra función necesita iterar a través de todos estos píxeles en la matriz y evaluar cada uno para determinar si coincide con el verde de nuestro fondo. Para ello se utiliza la función adjustPixel() que veremos en breve.

 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      

       }

Una vez actualizados todos los píxeles de nuestro imgdata se han actualizado, borramos el contexto del constructor y volvemos a colocar el icono imgdata al lienzo. A continuación, podemos dibujar desde este lienzo oculto a nuestro contexto visible. outputCanvas visible. Con requestAnimationFrame()esta función se ejecutará entre 30 y 60 veces por segundo.

       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()})

   }

Fijar la opacidad de los píxeles

La función adjustPixel() de la clase VideoRenderer acepta un color con valores independientes rojo, verde, azul y alfa. Luego devuelve un objeto con cuatro miembros, r, g, b y a. La primera línea crea un objeto output objeto.

adjustPixel(r,g,b,a){

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

A continuación, la función toma una media de los valores rojo y azul y la almacena como rb.

       let rb = (r + b) / 2

La siguiente línea de código determina si el píxel debe ser transparente o no. Primero utiliza la función withinRange() para ver si la diferencia entre el rojo y el azul es inferior a 40. Si el rojo y el azul tienen valores diferentes, entonces el color no será verde. Si el rojo y el azul tienen valores distintos, el color no será verde.

La segunda parte del argumento comprueba si el valor del verde es al menos 20 más que la media del rojo y el azul. Esto abarca una serie de tonos y matices de verde que se ajustan mejor a nuestra imagen original 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

       }

   }

Altavoces múltiples y desvanecimiento

En la siguiente captura de pantalla, puede ver un ejemplo de cómo se han acomodado varios oradores desvaneciendo a los que no están hablando en ese momento y manteniendo a todos alineados en la parte inferior izquierda. En este momento, hay tres interlocutores en la llamada, pero solo puede hablar una persona a la vez.

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

Al atenuar y eliminar los altavoces inactivos, se puede aumentar el número de altavoces y la resolución de la pantalla compartida o de la presentación sin comprometer la calidad de la experiencia.

Ejecutar la muestra

Necesitas introducir tu API Key, Session ID, y Token en el archivo config.json proporcionado, y luego puedes servir estos archivos en tu servidor web favorito. Si estás usando Node, puedes ejecutar los siguientes comandos desde la raíz del repositorio clonado:

npm install 

npm run serve

A continuación, simplemente abra su navegador y navegue hasta http://127.0.0.1:3000. Cuando hables, el micrófono debería captar tu voz y activar tu señal de vídeo.

Conclusión

Eso es todo por este tutorial. Tanto si estás presentando una presentación de diapositivas, editando una hoja de cálculo o viendo un Video, la alimentación de tu cámara sólo se mostrará cuando estés hablando y no ocupará espacio adicional en la pantalla. Espero que te haya resultado interesante. Otras implementaciones y mejoras podrían incluir:

  • Fondos de vídeo para editores

  • Aplicaciones Watchparty

El código completo de código completo para este post se puede encontrar en GitHub.

¿Tienes alguna pregunta o algo que compartir? Únete a la conversación en Slack de la comunidad de Vonagey mantente actualizado con el Boletín para desarrolladoressíguenos en X (antes Twitter)suscríbete a nuestro canal de YouTube para ver tutoriales en video, y sigue la página de página para desarrolladores de Vonage en LinkedInun espacio para que los desarrolladores aprendan y se conecten con la comunidad. Mantente conectado, comparte tu progreso y entérate de las últimas noticias, consejos y eventos para desarrolladores.

Compartir:

https://a.storyblok.com/f/270183/689x689/2b79e61b9f/richard-sabbarton.png
Richard SabbartonIngeniero superior de soporte de Video API

Richard es Ingeniero Senior de Soporte de Video API en Vonage con más de 25 años de experiencia en productos, soporte y funciones de ingeniería en Manufactura, Telecomunicaciones y Redes.