https://d226lax1qjow5r.cloudfront.net/blog/blogposts/create-a-party-with-ruby-on-rails-and-the-vonage-video-api-part-2-building-the-frontend-dr/Blog_Ruby_Video-API-Part2_1200x600.png

Crea una fiesta con Ruby on Rails y la Video API de Vonage - Parte 2

Publicado el May 5, 2021

Tiempo de lectura: 13 minutos

Esta es la segunda parte de una serie de dos partes sobre la creación de una aplicación de video watch party utilizando la API de Video de Vonage y Ruby on Rails.

En el primer artículorepasamos los pasos para construir el backend de la aplicación. Si usted no ha leído ese post todavía, sería un buen lugar para empezar. Ahora vamos a centrarnos en el frontend de nuestra aplicación. Mientras que el backend fue escrito principalmente en Ruby, el frontend será un montón de JavaScript del lado del cliente.

Una vez que hayamos terminado, ¡tendremos una aplicación para ver vídeos que podremos utilizar para chatear con nuestros amigos y ver vídeos juntos!

Landing Page

Empecemos.

tl;dr Si quieres saltarte el proceso e ir directamente a desplegarla, puedes encontrar todo el código de la aplicación en GitHub.

¿Qué vamos a construir?

Antes de empezar a programar, conviene dedicar un momento a hablar de lo que vamos a construir.

Si recuerdas el primer post, habíamos instanciado un ID de sesión de la Video API, y estamos creando activamente tokens para cada participante. Esa información se pasa al frontend mediante variables JavaScript recién creadas en los archivos de vista ERB. Además, también estamos pasando datos de nuestras variables de entorno al frontend. Utilizaremos toda esa información en el código que escribiremos para crear la experiencia de la aplicación.

Ruby on Rails ha avanzado mucho en la integración de JavaScript del lado del cliente directamente en la pila con la introducción de Webpack en Rails a partir de la versión 5.1. JavaScript se incorpora a través de paquetes colocados dentro de /app/javascript/packs y añadidos como import o require() dentro del archivo application.js dentro del directorio.

Vamos a separar las diversas preocupaciones de nuestro código en diferentes archivos de modo que al final su carpeta tendrá los siguientes archivos:

# app/javascript/packs - application.js - app_helpers.js - chat.js - opentok_screenshare.js - opentok_video.js - party.js - screenshare.js

Cada archivo, además de application.jscontendrá código para cubrir distintos aspectos:

  • app_helpers.js: Código multifuncional que se necesita en todo el frontend

  • chat.js: Creación de una clase Chat que se utilizará para instanciar instancias del chat de texto

  • opentok_screenshare.js: El código cliente de la vista Screenshare

  • opentok_video.js: El código del cliente para la vista de Video Chat

  • party.js: Creación de una clase Party que se utilizará para instanciar instancias del chat de vídeo

  • screenshare.js: Creación de una clase Screenshare que se utilizará para instanciar instancias de la funcionalidad screenshare

Antes de crear el código, vamos a añadir estos archivos al archivo application.js que indicará a Webpack que los compile en tiempo de ejecución:

// application.js

import './app_helpers.js'
import './opentok_video.js'
import './opentok_screenshare.js'

Creación de paquetes JavaScript

En cada subsección, crearemos los archivos JavaScript que hemos enumerado anteriormente.

Enapp_helpers.js Archivo

El archivo app_helpers.js contendrá funciones genéricas de ayuda que exportaremos al resto del código para utilizarlas en toda la aplicación. Crearemos screenshareMode(), setButtonDisplay(), formatChatMsg()y streamLayout() funciones.

La función screenshareMode() aprovechará la API de señales de Video API de Vonage para enviar un mensaje a los navegadores de todos los participantes que desencadenará un window.location cambio. La Signal API es la misma API que utilizaremos para el chat de texto, que es su caso de uso más sencillo. Sin embargo, como veremos en esta función, la Signal API proporciona una manera intuitiva y poderosa de dirigir el flujo de tu aplicación simultáneamente para todos los participantes sin necesidad de escribir mucho código:

export function screenshareMode(session, mode) {
  if (mode == 'on') {
    window.location = '/screenshare?name=' + name;
    session.signal({
      type: 'screenshare',
      data: 'on'
    });
  } else if (mode == 'off') {
    window.location = '/party?name=' + name;
    session.signal({
      type: 'screenshare',
      data: 'off'
    });    
  };
};

La siguiente función, setButtonDisplay() cambia el estilo del elemento HTML que contiene el botón "Watch Mode On/Off" para que sea block o none dependiendo de si el participante es el moderador o no. Hay muchas otras formas de hacer esto, incluyendo métodos más seguros. Sin embargo, con el fin de mantener las cosas simples para esta aplicación para ver videos entre amigos, vamos a mantener el minimalismo:

export function setButtonDisplay(element) {
  if (name == moderator_env_name) {
    element.style.display = "block";
  } else {
    element.style.display = "none";
  };
};

La función formatChatMsg() toma como argumento el mensaje de texto enviado por el participante y lo formatea para su presentación en el sitio. Esta función busca cualquier texto entre corchetes de dos dos puntos e intenta parsear el texto dentro de esos dos puntos como un emoji. También añade el nombre del participante a cada mensaje para que todo el mundo sepa quién está hablando.

Para añadir los emojis, necesitamos instalar un paquete node llamado node-emoji y podemos hacerlo añadiendo const emoji = require('node-emoji); al principio del archivo y ejecutando yarn add node-emoji en la línea de comandos. La función utilizará match() con una expresión regular para buscar cadenas de texto marcadas por dos dos puntos, y si coincide, invocará la función emoji const que definimos para convertir esa cadena en un emoji:

export function formatChatMsg(message) {
  var message_arr;
  message_arr = message.split(' ').map(function(word) {
    if (word.match(/(?:\:)\b(\w*)\b(?=\:)/g)) {
      return word = emoji.get(word);
    } else {
      return word;
    }
  })
  message = message_arr.join(' ');
  return `${name}: ${message}`
};

La última función dentro de app_helpers.js que tenemos que crear es streamLayout() que toma como argumentos el elemento HTML y el número de participantes. La función añadirá o quitará clases CSS al elemento dependiendo del número de participantes para cambiar la presentación del videochat a un formato de cuadrícula:

export function streamLayout(element, count) {
  if (count >= 6) {
    element.classList.add("grid9");
  } else if (count == 5) {
    element.classList.remove("grid9");
    element.classList.add("grid4");
  } else if (count < 5) {
    element.classList.remove("grid4");
  }
};

Enchat.js Archivo

El código chat.js va a crear la clase Chat utilizando una clase constructor(). Esta clase Chat será llamada e instanciada tanto en la vista de videochat como en la de pantalla compartida:

// chat.js

import { formatChatMsg } from './app_helpers.js';

export default class Chat {
  constructor(session) {
    this.session = session;
    this.form = document.querySelector('form');
    this.msgTxt = document.querySelector('#message');
    this.msgHistory = document.querySelector('#history');
    this.chatWindow = document.querySelector('.chat');
    this.showChatBtn = document.querySelector('#showChat');
    this.closeChatBtn = document.querySelector('#closeChat');
    this.setupEventListeners();
  }

Hemos dado varias propiedades a Chatbasadas principalmente en diferentes elementos del DOM y de la sesión de la Video API. La última this.setupEventListeners() está invocando una función que ahora tenemos que añadir al archivo:

setupEventListeners() {
    let self = this;
    this.form.addEventListener('submit', function(event) {
      event.preventDefault();

      self.session.signal({
        type: 'msg',
        data: formatChatMsg(self.msgTxt.value)
      }, function(error) {
        if (error) {
          console.log('Error sending signal:', error.name, error.message);
        } else {
          self.msgTxt.value = '';
        }
      });
    });

    this.session.on('signal:msg', function signalCallback(event) {
      var msg = document.createElement('p');
      msg.textContent = event.data;
      msg.className = event.from.connectionId === self.session.connection.connectionId ? 'mine' : 'theirs';
      self.msgHistory.appendChild(msg);
      msg.scrollIntoView();
    });

    this.showChatBtn.addEventListener('click', function(event) {
      self.chatWindow.classList.add('active');
    });

    this.closeChatBtn.addEventListener('click', function(event) {
      self.chatWindow.classList.remove('active');
    });
  }
}

setupEventListeners() crea un EventListener para el chat de texto submit de texto. Cuando se envía un nuevo mensaje, se envía a la API de señales para que lo procese y lo envíe a todos los participantes. Del mismo modo, cuando se recibe un nuevo mensaje se añade una nueva etiqueta <p> al elemento de chat, y la ventana de chat de texto del participante se desplaza para verlo.

Los dos siguientes archivos que vamos a crear realizan una funcionalidad similar en la creación de nuevas clases para la parte de chat de vídeo y para la vista de pantalla compartida.

Enparty.js Archivo

En este archivo crearemos la clase Party que se utilizará para instanciar nuevas instancias del chat de vídeo:

// party.js

import { screenshareMode, setButtonDisplay, streamLayout } from './app_helpers.js';

export default class Party {
  constructor(session) {
    this.session = session;
    this.watchLink = document.getElementById("watch-mode");
    this.subscribers = document.getElementById("subscribers");
    this.participantCount = document.getElementById("participant-count");
    this.videoPublisher = this.setupVideoPublisher();
    this.clickStatus = 'off';
    this.setupEventHandlers();
    this.connectionCount = 0;
    setButtonDisplay(this.watchLink);
  }

La función constructor() recibe la sesión de la Video API como argumento y la pasa a la función this.session. El resto de las propiedades se definen y se les asignan valores. La dirección watchLink, subscribers, participantCount proceden de los elementos HTML, mientras que videoPublisher recibe una función como valor, y clickStatus recibe por defecto el valor off.

Crearemos la función setupVideoPublisher() en este punto. La función invoca la función Video API JavaScript SDK initPublisher() para iniciar la publicación del vídeo. Puede recibir argumentos opcionales y, como tal, especificamos que el vídeo debe ocupar el 100 % de la anchura y la altura de su elemento y debe adjuntarse al elemento:

setupVideoPublisher() {
    return OT.initPublisher('publisher', {
      insertMode: 'append',
      width: "100%",
      height: "100%"
    }, function(error) {
      if (error) {
        console.error('Failed to initialise publisher', error);
      };
    });
  }

Hay varias acciones para las que también debemos crear escuchadores de eventos y añadirlos a la clase. Necesitamos escuchar cuando la sesión está conectada, cuando se ha creado un flujo de vídeo, cuando se ha añadido una conexión y cuando se ha destruido una conexión. Cuando se ha añadido o destruido una conexión, incrementamos o disminuimos el recuento de participantes, y compartimos el número de participantes en el elemento de recuento de participantes <div> de la página:

setupEventHandlers() {
    let self = this;
    this.session.on({
      // This function runs when session.connect() asynchronously completes
      sessionConnected: function(event) {
        // Publish the publisher we initialzed earlier (this will trigger 'streamCreated' on other
        // clients)
        self.session.publish(self.videoPublisher, function(error) {
          if (error) {
            console.error('Failed to publish', error);
          }
        });
      },

      // This function runs when another client publishes a stream (eg. session.publish())
      streamCreated: function(event) {
        // Subscribe to the stream that caused this event, and place it into the element with id="subscribers"
        self.session.subscribe(event.stream, 'subscribers', {
          insertMode: 'append',
          width: "100%",
          height: "100%"
        }, function(error) {
          if (error) {
            console.error('Failed to subscribe', error);
          }
        });
      },

      // This function runs whenever a client connects to a session
      connectionCreated: function(event) {
        self.connectionCount++;
        self.participantCount.textContent = `${self.connectionCount} Participants`;
        streamLayout(self.subscribers, self.connectionCount);
      },

      // This function runs whenever a client disconnects from the session
      connectionDestroyed: function(event) {
        self.connectionCount--;
        self.participantCount.textContent = `${self.connectionCount} Participants`;
        streamLayout(self.subscribers, self.connectionCount);
      }
    });

Por último, añadimos un receptor de eventos más. Este receptor de eventos se adjunta a la acción click acción en el botón "Watch Mode On/Off". Cuando se pulsa se va a la vista de pantalla compartida, si el estado de pulsación era desactivado. Usted recordará que el estado de clic se le da un valor predeterminado de apagado en la construcción de la clase:

this.watchLink.addEventListener('click', function(event) {
      event.preventDefault();
      if (self.clickStatus == 'off') {
        // Go to screenshare view
        screenshareMode(self.session, 'on');
      };
    });
  }
}

Enscreenshare.js Archivo

La última clase que crearemos es una clase Screenshare que se encargará de definir la pantalla de vídeo. La función constructor() toma como argumentos la sesión de la Video API y el nombre del participante:

// screenshare.js

import { screenshareMode } from './app_helpers.js';

export default class Screenshare {
  constructor(session, name) {
    this.session = session;
    this.name = name;
    this.watchLink = document.getElementById("watch-mode");
    this.clickStatus = 'on';
  }

A diferencia de la Party la clase clickStatus aquí por defecto es on ya que queremos salir de la pantalla compartida y volver al modo de videochat, si el moderador hace clic en el botón "Activar/desactivar el modo de visualización".

También utilizamos toggle() para compartir la pantalla del participante, si el participante es el moderador, o suscribirse a la pantalla compartida para todos los demás:

toggle() {
    if (this.name === moderator_env_name) {
      this.shareScreen();
    } else {
      this.subscribe();
    }
  }

La función shareScreen() invocada en la función toggle() debe definirse:

shareScreen() {
    this.setupPublisher();
    this.setupAudioPublisher();
    this.setupClickStatus();
  }

Esta función a su vez tiene tres funciones que también necesitan ser creadas. La primera función publicará la pantalla del moderador. Sin embargo, la publicación de la pantalla por sí misma no incluye también el audio. Por lo tanto, una segunda función publicará el audio del ordenador del moderador. Luego, la última función en shareScreen() volverá a la vista del chat de vídeo si se hace clic en el botón "Activar/desactivar el modo Observar":

setupClickStatus() {
    // screen share mode off if clicked off
    // Set click status
    let self = this;
    this.watchLink.addEventListener('click', function(event) {
      event.preventDefault();
      if (self.clickStatus == 'on') {
        self.clickStatus = 'off';
        screenshareMode(self.session, 'off');
      };
    });
  }

  setupAudioPublisher() {
    var self = this;
    var audioPublishOptions = {};
    audioPublishOptions.insertMode = 'append';
    audioPublishOptions.publishVideo = false;
    var audio_publisher = OT.initPublisher('audio', audioPublishOptions,
      function(error) {
        if (error) {
          console.log(error);
        } else {
          self.session.publish(audio_publisher, function(error) {
            if (error) {
              console.log(error);
            }
          });
        };
      }
    );
  }

  setupPublisher() {
    var self = this;
    var publishOptions = {};
    publishOptions.videoSource = 'screen';
    publishOptions.insertMode = 'append';
    publishOptions.height = '100%';
    publishOptions.width = '100%';
    var screen_publisher = OT.initPublisher('screenshare', publishOptions,
      function(error) {
        if (error) {
          console.log(error);
        } else {
          self.session.publish(screen_publisher, function(error) {
            if (error) {
              console.log(error);
            };
          });
        };
      }
    );
  }

Todo lo anterior es con el fin de crear la pantalla compartida para el moderador. Todos los demás en la aplicación querrán suscribirse a ese screenshare. Usaremos la función subscribe() para hacerlo. Esta será la última función dentro del archivo:

subscribe() {
    var self = this;
    this.watchLink.style.display = "none";
    this.session.on({
      streamCreated: function(event) {
        console.log(event);
        if (event.stream.hasVideo == true) {
          self.session.subscribe(event.stream, 'screenshare', {
            insertMode: 'append',
            width: '100%',
            height: '100%'
          }, function(error) {
            if (error) {
              console.error('Failed to subscribe to video feed', error);
            }
          });
        } else if (event.stream.hasVideo == false ) {
          self.session.subscribe(event.stream, 'audio', {
            insertMode: 'append',
            width: '0px',
            height: '0px'
          }, function(error) {
            if (error) {
              console.error('Failed to subscribe to audio feed', error);
            }
          });
        };
      }
    });
  }
}

Ahora estamos listos para hacer que todas estas clases que hemos definido funcionen en la aplicación creando instancias de ellas dentro de la clase opentok_screenshare.js y opentok_video.js .

Creación deopentok_video.js

El archivo opentok_video.js construirá una nueva experiencia de videochat. La mayor parte del trabajo se realizó en las clases que definimos anteriormente, por lo que este archivo es relativamente pequeño. Primero, vamos a importar las clases Chat y Party :

// opentok_video.js

import Chat from './chat.js'
import Party from './party.js'

A continuación, definiremos una variable global vacía para guardar la sesión de la Video API:

var session = ''

A continuación, envolvemos el resto del código en tres comprobaciones para asegurarnos de que estamos en la ruta correcta del sitio web, de que el DOM está completamente cargado y de que el nombre del participante no está vacío:

if (window.location.pathname == '/party') {
  document.addEventListener('DOMContentLoaded', function() {
    if (name != '') {

El resto del código inicia una nueva sesión de la Video API si no existe ninguna e instancian un nuevo Chat y new Party. Al final, también esperamos a que la API de señales envíe un mensaje de datos screenshare con el valor de on. Cuando se recibe ese mensaje, el window.location se mueve a /screenshare:

// Initialize an OpenTok Session object
      if (session == '') {
        session = OT.initSession(api_key, session_id);
      }

      new Chat(session);
      new Party(session);

      // Connect to the Session using a 'token'
      session.connect(token, function(error) {
        if (error) {
          console.error('Failed to connect', error);
        }
      });

      // Listen for Signal screenshare message
      session.on('signal:screenshare', function screenshareCallback(event) {
        if (event.data == 'on') {
          window.location = '/screenshare?name=' + name;
        };
      });
    };
  });
}

Creación deopentok_screenshare.js

El último archivo JavaScript que crearemos es muy similar al anterior. Es responsable de la vista de pantalla compartida y aprovecha las funciones Screenshare y Chat que definimos anteriormente:

import Screenshare from './screenshare.js'
import Chat from './chat.js'

// declare empty global session variable
var session = ''

if (window.location.pathname == '/screenshare') {
  document.addEventListener('DOMContentLoaded', function() {
    // Initialize an OpenTok Session object
    if (session == '') {
      session = OT.initSession(api_key, session_id);
    }

    // Hide or show watch party link based on participant
    if (name != '' && window.location.pathname == '/screenshare') {
      new Chat(session);
      new Screenshare(session, name).toggle();

      // Connect to the Session using a 'token'
      session.connect(token, function(error) {
        if (error) {
          console.error('Failed to connect', error);
        }
      });

      // Listen for Signal screenshare message
      session.on('signal:screenshare', function screenshareCallback(event) {
        if (event.data == 'off') {
          window.location = '/party?name=' + name;
        };
      });
    }
  });
};

Por último, pero no por ello menos importante, tenemos que definir el estilo del frontend de la aplicación. Todo este código es inútil si no es accesible por los participantes.

Estilizar la aplicación

La hoja de estilo de esta aplicación no habría sido posible sin la ayuda de mi amigo y antiguo colega, Hui Jing Chen que me enseñó mucho sobre diseño front-end a través de este proceso. La aplicación utiliza principalmente Flexbox Grid para ordenar los elementos.

Empecemos creando un archivo custom.css dentro de app/javascript/stylesheets. Queremos asegurarnos de que se incluye en nuestra aplicación, así que añade una línea de importación a application.scss en la misma carpeta, @import './custom.css';.

En primer lugar, vamos a añadir el estilo del núcleo en custom.css:

:root {
  --main: #343a40;
  --txt-alt: white;
  --txt: black;
  --background: white;
  --bgImage: url('~images/01.png');
  --chat-bg: rgba(255, 255, 255, 0.75);
  --chat-mine: darkgreen;
  --chat-theirs: indigo;
}

html {
  box-sizing: border-box;
  height: 100%;
}
 
*,
*::before,
*::after {
  box-sizing: inherit;
  margin: 0;
  padding: 0;
}
 
body {
  height: 100%;
  display: flex;
  flex-direction: column;
  background-color: var(--background);
  background-image: var(--bgImage);
  overflow: hidden;
}
 
main {
  flex: 1;
  display: flex;
  position: relative;
}

input {
  font-size: inherit;
  padding: 0.5em;
  border-radius: 4px;
  border: 1px solid currentColor;
}

button,
input[type="submit"] {
  font-size: inherit;
  padding: 0.5em;
  border: 0;
  background-color: var(--main);
  color: var(--txt-alt);
  border-radius: 4px;
}

header {
  background-color: var(--main);
  color: var(--txt-alt);
  padding: 0.5em;
  height: 4em;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

A continuación, vamos a añadir el estilo de la página de destino:

.landing {
  margin: auto;
  text-align: center;
  font-size: 125%;
}

.landing form {
  display: flex;
  flex-direction: column;
  margin: auto;
  position: relative;
}

.landing input,
.landing p {
  margin-bottom: 1em;
}

.landing .error {
  color: maroon;
  position: absolute;
  bottom: -2em;
  width: 100%;
  text-align: center;
}

También queremos añadir el estilo para el chat de texto, especialmente asegurándonos de que se mantiene en su lugar y no se desplaza toda la página a medida que avanza:

.chat {
  width: 100%;
  display: flex;
  flex-direction: column;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 2;
  background-color: var(--chat-bg);
  transform: translateX(-100%);
  transition: transform 0.5s ease;
}

.chat.active {
  transform: translateX(0);
}

.chat-header {
  padding: 0.5em;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.24);
  display: flex;
  justify-content: space-between;
}

.btn-chat {
  height: 5em;
  width: 5em;
  border-radius: 50%;
  box-shadow: 0 3px 6px 0 rgba(0, 0, 0, .2), 0 3px 6px 0 rgba(0, 0, 0, .19);
  position: fixed;
  right: 1em;
  bottom: 1em;
  cursor: pointer;
}

.btn-chat svg {
  height: 4em;
  width: 2.5em;
}

.btn-close {
  height: 2em;
  width: 2em;
  background: transparent;
  border: none;
  cursor: pointer;
}

.btn-close svg {
  height: 1em;
  width: 1em;
}

.messages {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow-y: scroll;
  padding: 1em;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.24);
  scrollbar-color: #c1c1c1 transparent;
}

.messages p {
  margin-bottom: 0.5em;
}

.mine {
  color: var(--chat-mine);
}

.theirs {
  color: var(--chat-theirs);
}

.chat form {
  display: flex;
  padding: 1em;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.24);
}

.chat input[type="text"] {
  flex: 1;
  border-top-left-radius: 0px;
  border-bottom-left-radius: 0px;
  background-color: var(--background);
  color: var(--txt);
  min-width: 0;
}

.chat input[type="submit"] {
  border-top-right-radius: 0px;
  border-bottom-right-radius: 0px;
}

Ahora vamos a crear el estilo para el chat de vídeo y los elementos de pantalla compartida:

.videos {
  flex: 1;
  display: flex;
  position: relative;
}

.subscriber.grid4 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(25em, 1fr));
}

.subscriber.grid9 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18em, 1fr));
}
 
.subscriber,
.screenshare {
  width: 100%;
  height: 100%;
  display: flex;
}
 
.publisher {
  position: absolute;
  width: 25vmin;
  height: 25vmin;
  min-width: 8em;
  min-height: 8em;
  align-self: flex-end;
  z-index: 1;
}

.audio {
  position: absolute;
  opacity: 0;
  z-index: -1;
}

.audio {
  display: none;
}

.dark {
  --background: black;
  --chat-mine: lime;
  --chat-theirs: violet;
  --txt: white;
}

Por último, añadiremos una consulta de medios que mantendrá el chat de texto en proporción en las pantallas más pequeñas:

@media screen and (min-aspect-ratio: 1 / 1) {
  .chat {
    width: 20%;
    min-width: 16em;
  }
}

Ya está. La aplicación, tanto el backend como el frontend, ha sido creada. Ahora estamos listos para ponerlo todo junto.

Puesta en común

Aunque la aplicación es una combinación de varios lenguajes de programación, concretamente Ruby y JavaScript, con un backend y un frontend entrelazados, es relativamente sencillo ejecutarla. Esto se debe a que Rails nos permite integrarlo todo a la perfección con un solo comando.

Desde la línea de comandos puedes ejecutar bundle exec rails s y ver cómo se inicia el servidor Rails. También verás la siguiente línea casi mágica en la salida de tu consola la primera vez que ejecutes la aplicación:

[Webpacker] Compiling...

De hecho, lo verás cada vez que realices un cambio en cualquiera de tus paquetes JavaScript o CSS. Esa salida te indica que Rails está utilizando Webpack para compilar e incorporar todos tus paquetes a la aplicación. Una vez que [Webpacker] Compiling... verás una lista de todos tus paquetes compilados:

Version: webpack 4.42.1 Time: 1736ms Built at: 05/01/2020 12:01:37 PM Asset Size Chunks Chunk Names js/app_helpers-31c49752d24631573287.js 100 KiB app_helpers [emitted] [immutable] app_helpers js/app_helpers-31c49752d24631573287.js.map 44.3 KiB app_helpers [emitted] [dev] app_helpers js/application-d253fe0e7db5e2b1ca60.js 564 KiB application [emitted] [immutable] application js/application-d253fe0e7db5e2b1ca60.js.map 575 KiB application [emitted] [dev] application js/chat-451fca901a39ddfdf982.js 103 KiB chat [emitted] [immutable] chat js/chat-451fca901a39ddfdf982.js.map 46.1 KiB chat [emitted] [dev] chat js/opentok_screenshare-2bc51be74c7abf27abe2.js 110 KiB opentok_screenshare [emitted] [immutable] opentok_screenshare js/opentok_screenshare-2bc51be74c7abf27abe2.js.map 51 KiB opentok_screenshare [emitted] [dev] opentok_screenshare js/opentok_video-15ed35dc7b01325831c0.js 109 KiB opentok_video [emitted] [immutable] opentok_video js/opentok_video-15ed35dc7b01325831c0.js.map 50.6 KiB opentok_video [emitted] [dev] opentok_video js/party-f5d6c0ccd3bb1fcc225e.js 105 KiB party [emitted] [immutable] party js/party-f5d6c0ccd3bb1fcc225e.js.map 47.5 KiB party [emitted] [dev] party js/screenshare-4c13687e1032e93dc59a.js 105 KiB screenshare [emitted] [immutable] screenshare js/screenshare-4c13687e1032e93dc59a.js.map 47.9 KiB screenshare [emitted] [dev] screenshare manifest.json 2.38 KiB [emitted]

Los nombres de archivo reflejan que se han compilado hacia abajo, pero todavía se puede ver sus nombres de paquete en allí si se mira de cerca, como opentok_screenshare, party, app_helpersetc.

Ejecutar tu aplicación localmente es estupendo para hacer pruebas contigo mismo, pero probablemente te gustaría invitar a tus amigos a participar contigo.

Puedes crear un enlace accesible externamente a tu aplicación ejecutándose localmente usando una herramienta como ngrok. Proporciona una URL externa para tu entorno local. La Plataforma de Desarrolladores Nexmo tiene una guía sobre puesta en marcha con ngrok que puedes seguir.

Si quieres ponerte manos a la obra, también puedes desplegar con un solo clic esta aplicación desde GitHub directamente a Heroku.

Me encantaría saber qué has creado con la Video API de Vonage. Únete a la conversación en nuestro Slack de la comunidad ¡y comparte tu historia!

Compartir:

https://a.storyblok.com/f/270183/384x384/e5480d2945/ben-greenberg.png
Ben GreenbergAntiguos alumnos de Vonage

Ben es un desarrollador de segunda carrera que anteriormente pasó una década en los campos de la educación de adultos, la organización comunitaria y la gestión de organizaciones sin ánimo de lucro. Trabajó como defensor de los desarrolladores para Vonage. Escribe regularmente sobre la intersección entre el desarrollo comunitario y la tecnología. Originario del sur de California y residente durante mucho tiempo en Nueva York, Ben reside ahora cerca de Tel Aviv (Israel).