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

Créer une fête avec Ruby on Rails et l'API Video de Vonage Partie 2

Publié le May 5, 2021

Temps de lecture : 13 minutes

Ceci est la deuxième partie d'une série en deux parties sur la création d'une application de veille vidéo en utilisant l'API Video de Vonage et Ruby on Rails.

Dans le premier articlenous avons passé en revue les étapes de la construction du backend de l'application. Si vous n'avez pas encore lu cet article, c'est un bon point de départ. Maintenant, nous allons nous concentrer sur le frontend de notre application. Alors que le backend a été écrit principalement en Ruby, le frontend sera composé en grande partie de JavaScript côté client.

Une fois que nous aurons terminé, nous aurons une application de Video Watch Party que nous pourrons utiliser pour discuter avec nos amis et regarder des vidéos ensemble !

Landing Page

C'est parti !

tl;dr Si vous souhaitez passer directement au déploiement, vous pouvez trouver tout le code de l'application sur GitHub.

Que construirons-nous ?

Avant de commencer à coder, il est bon de prendre un moment pour discuter de ce que nous allons construire.

Si vous vous souvenez du premier article, nous avons instancié un identifiant de session de l'API Video et nous créons activement des jetons pour chaque participant. Ces informations sont transmises au frontend par des variables JavaScript nouvellement créées dans les fichiers de vue ERB. En outre, nous transmettons également les données de nos variables d'environnement au frontend. Nous utiliserons toutes ces informations dans le code que nous écrirons pour créer l'expérience de l'application.

Ruby on Rails a beaucoup progressé dans l'intégration de JavaScript côté client directement dans la pile avec l'introduction de Webpack dans Rails à partir de la version 5.1. JavaScript est incorporé par l'intermédiaire de packs placés à l'intérieur de /app/javascript/packs et ajoutés en tant que import ou require() à l'intérieur du fichier application.js à l'intérieur du répertoire.

Nous allons séparer les différents aspects de notre code dans différents fichiers, de sorte qu'à la fin, votre dossier contiendra les fichiers suivants :

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

Chaque fichier, outre application.jscontiendra du code pour répondre à des besoins distincts :

  • app_helpers.js: Code transversal nécessaire à l'ensemble du frontend

  • chat.js: Création d'une classe Chat qui sera utilisée pour instancier des instances du chat textuel

  • opentok_screenshare.js: Le code côté client pour la vue Screenshare

  • opentok_video.js: Le code côté client pour la vue Video Chat

  • party.js: Création d'une classe Party qui sera utilisée pour instancier des instances du chat vidéo

  • screenshare.js: Création d'une classe Screenshare qui sera utilisée pour instancier des instances de la fonctionnalité screenshare

Avant de créer le code, ajoutons ces fichiers au fichier application.js qui demandera à Webpack de les compiler au moment de l'exécution :

// application.js

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

Création des packs JavaScript

Dans chaque sous-section, nous créerons les fichiers JavaScript que nous avons énumérés ci-dessus.

Leapp_helpers.js Fichier

Le fichier app_helpers.js contiendra des fonctions d'aide génériques que nous exporterons vers le reste du code afin de les utiliser tout au long de l'application. Nous créerons screenshareMode(), setButtonDisplay(), formatChatMsg(), et streamLayout() des fonctions.

La fonction screenshareMode() profitera de l'API Video API Signal de Vonage pour envoyer un message aux navigateurs de tous les participants qui déclenchera une window.location changement. L'API Signal est la même que celle que nous utiliserons pour le chat textuel, qui est son cas d'utilisation le plus simple. Cependant, comme nous le verrons dans cette fonction, l'API Signal offre un moyen intuitif et puissant de diriger le flux de votre application simultanément pour tous les participants sans avoir besoin d'écrire beaucoup de code :

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 fonction suivante, setButtonDisplay() modifie le style de l'élément HTML contenant le bouton "Watch Mode On/Off" pour qu'il soit soit block ou none selon que le participant est le modérateur ou non. Il existe de nombreuses autres façons de procéder, y compris des méthodes plus sûres. Cependant, afin de garder les choses simples pour cette application pour regarder des vidéos entre amis, nous allons garder le minimalisme :

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

La fonction formatChatMsg() prend en argument le message texte envoyé par le participant et le met en forme pour le présenter sur le site. Cette fonction recherche tout texte entre deux points de suspension et tente d'analyser le texte à l'intérieur de ces points de suspension comme un emoji. Elle ajoute également le nom du participant à chaque message afin que tout le monde sache qui parle.

Afin d'ajouter les emojis, nous devons installer un paquetage node appelé node-emoji et nous pouvons le faire en ajoutant const emoji = require('node-emoji); au début du fichier et en exécutant yarn add node-emoji dans la ligne de commande. La fonction utilisera match() avec une expression régulière pour rechercher des chaînes de texte marquées par deux deux-points, et si elle correspond, elle invoquera la const que nous avons définie pour transformer cette chaîne en emojis. emoji que nous avons définie pour transformer cette chaîne en 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 dernière fonction que nous devons créer à l'intérieur de app_helpers.js que nous devons créer est streamLayout() qui prend en argument l'élément HTML et le nombre de participants. La fonction ajoutera ou supprimera des classes CSS à l'élément en fonction du nombre de participants afin de modifier la présentation du chat vidéo en un format de grille :

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");
  }
};

Lechat.js Fichier

Le code chat.js va créer la classe Chat à l'aide d'un constructor(). Cette classe Chat sera appelée et instanciée dans les vues de chat vidéo et de partage d'écran :

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

Nous avons donné plusieurs propriétés à Chatprincipalement en fonction de différents éléments du DOM et de la session Video API. La dernière, this.setupEventListeners() invoque une fonction que nous devons maintenant ajouter au fichier :

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() crée un EventListener pour le bouton de chat textuel submit pour le texte. Lorsqu'un nouveau message est soumis, il est envoyé à l'API Signal pour être traité et envoyé à tous les participants. De même, lorsqu'un nouveau message est reçu, une nouvelle balise <p> est ajoutée à l'élément de discussion et la fenêtre de discussion textuelle du participant défile pour l'afficher.

Les deux prochains fichiers que nous allons créer exécutent une fonctionnalité similaire en créant de nouvelles classes pour la partie de chat vidéo et pour la vue de partage d'écran.

Leparty.js Fichier

Dans ce fichier, nous allons créer la classe Party qui sera utilisée pour instancier de nouvelles instances du chat vidéo :

// 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 fonction constructor() reçoit en argument la session de l'API Video et la transmet à la fonction this.session. Les autres propriétés sont définies et reçoivent des valeurs. Les propriétés watchLink, subscribers, participantCount proviennent des éléments HTML, tandis que videoPublisher se voit attribuer une fonction comme valeur, et clickStatus se voit attribuer la valeur par défaut de off.

Nous allons créer la fonction setupVideoPublisher() à ce stade. La fonction invoque la fonction Video API JavaScript SDK initPublisher() pour lancer la publication de la Video. Elle peut prendre en compte des arguments facultatifs et, à ce titre, nous spécifions que la vidéo doit occuper 100 % de la largeur et de la hauteur de son élément et qu'elle doit être annexée à l'élément :

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

Nous devons également créer des récepteurs d'événements pour plusieurs actions et les ajouter à la classe. Nous devons écouter lorsque la session est connectée, lorsqu'un flux Video a été créé, lorsqu'une connexion a été ajoutée et lorsqu'une connexion a été détruite. Lorsqu'une connexion a été ajoutée ou détruite, nous incrémentons ou décrémentons le nombre de participants, et nous partageons le nombre de participants dans l'élément participant count <div> de la page :

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

Enfin, nous ajoutons un écouteur d'événements supplémentaire. Cet écouteur d'événements est attaché à l'action du bouton "Watch Mode On/Off". click du bouton "Watch Mode On/Off". Lorsque l'on clique sur ce bouton, on passe à l'affichage du partage d'écran, si le statut du clic était désactivé. Vous vous souviendrez que l'état du clic est désactivé par défaut lors de la construction de la classe :

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

Lescreenshare.js Fichier

La dernière classe que nous allons créer est une classe Screenshare qui sera chargée de définir le partage d'écran vidéo. La fonction constructor() prend comme arguments la session de l'API Video et le nom du participant :

// 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';
  }

Contrairement à la Party la classe clickStatus prend ici la valeur par défaut on puisque nous voulons quitter le partage d'écran et revenir au mode de chat vidéo, si le modérateur clique sur le bouton "Activation/désactivation du mode de veille".

Nous utilisons également toggle() pour partager l'écran du participant, si celui-ci est le modérateur, ou pour s'abonner au partage d'écran pour tous les autres participants :

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

La fonction shareScreen() invoquée dans le toggle() doit être définie :

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

Cette fonction comporte elle-même trois fonctions qui doivent également être créées. La première fonction publie l'écran du modérateur. Cependant, la publication de l'écran en elle-même ne comprend pas l'audio. Par conséquent, une deuxième fonction publiera l'audio à partir de l'ordinateur du modérateur. Ensuite, la dernière fonction dans shareScreen() permet de revenir à l'affichage du chat vidéo si l'on clique sur le bouton "Watch Mode On/Off" (activer/désactiver le mode de surveillance) :

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

Tout ce qui précède permet de créer un partage d'écran pour le modérateur. Tous les autres utilisateurs de l'application voudront s'abonner à ce partage d'écran. Nous utiliserons la fonction subscribe() pour ce faire. Ce sera la dernière fonction dans le fichier :

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

Nous sommes maintenant prêts à faire fonctionner toutes les classes que nous avons définies dans l'application en créant des instances de ces classes à l'intérieur des fichiers opentok_screenshare.js et opentok_video.js à l'intérieur de l'application.

Créationopentok_video.js

Le fichier opentok_video.js permet de créer une nouvelle expérience de Video Chat. La plupart du travail a été effectué dans les classes que nous avons définies ci-dessus, ce qui fait que ce fichier est relativement petit. Tout d'abord, importons les classes Chat et Party :

// opentok_video.js

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

Ensuite, nous définirons une variable globale vide pour contenir la session Video API :

var session = ''

Ensuite, nous enveloppons le reste du code dans trois vérifications pour nous assurer que nous sommes sur le bon chemin d'accès au site web, que le DOM est entièrement chargé et que le nom du participant n'est pas vide :

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

Le reste du code lance une nouvelle session Video API s'il n'en existe pas et instancie un nouveau fichier Chat et un nouveau Party. À la fin, nous écoutons également l'API Signal pour envoyer un message de données screenshare avec la valeur de on. Lorsque ce message est reçu, l'élément window.location est déplacé vers /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;
        };
      });
    };
  });
}

Créationopentok_screenshare.js

Le dernier fichier JavaScript que nous allons créer est très similaire au précédent. Il est responsable de l'affichage du partage d'écran et utilise les fonctions Screenshare et Chat que nous avons définies plus tôt :

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

Avant de conclure, nous devons définir le style frontal de l'application. Tout ce code est inutile s'il n'est pas accessible aux participants.

Styliser l'application

La feuille de style de cette application n'aurait pas vu le jour sans l'aide de mon ami et ancien collègue, Hui Jing Chen qui m'a beaucoup appris sur la conception frontale au cours de ce processus. L'application utilise principalement la grille Flexbox Grid pour ordonner les éléments.

Commençons par créer un fichier custom.css à l'intérieur de app/javascript/stylesheets. Nous voulons nous assurer qu'il est inclus dans notre application, donc ajoutons une ligne d'importation à application.scss dans le même dossier, @import './custom.css';.

Tout d'abord, ajoutons le style de base dans 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;
}

Ajoutons ensuite le style de la page d'atterrissage :

.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;
}

Nous voulons également ajouter le style du chat textuel, en veillant notamment à ce qu'il reste en place et ne fasse pas défiler toute la page au fur et à mesure qu'il progresse :

.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;
}

Créons maintenant le style des éléments de chat vidéo et de partage d'écran :

.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;
}

Enfin, nous ajouterons une requête média qui maintiendra la proportionnalité du chat textuel sur les petits écrans :

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

Voilà, c'est fait ! L'application, tant le backend que le frontend, a été créée. Nous sommes maintenant prêts à l'assembler.

La mise en place de l'ensemble

Même si l'application est une combinaison de plusieurs langages de programmation, à savoir Ruby et JavaScript, avec un backend et un frontend imbriqués, il est relativement simple de l'exécuter. En effet, Rails nous permet d'intégrer l'ensemble de manière transparente à l'aide d'une seule commande.

À partir de la ligne de commande, vous pouvez exécuter bundle exec rails s et regarder votre serveur Rails démarrer. La première fois que vous lancerez l'application, vous verrez la ligne suivante, presque magique, dans la sortie de la console :

[Webpacker] Compiling...

En fait, vous verrez cela chaque fois que vous apporterez une modification à l'un de vos packs JavaScript ou CSS. Cette sortie vous indique que Rails utilise Webpack pour compiler et incorporer tous vos packs dans l'application. Une fois que la compilation est terminée, vous verrez une liste de tous les packs que vous avez créés. [Webpacker] Compiling... est terminée, vous verrez une liste de tous vos packs compilés :

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]

Les noms de fichiers reflètent le fait qu'ils ont été compilés, mais vous pouvez toujours y voir les noms de vos paquets si vous y regardez de plus près, par exemple opentok_screenshare, party, app_helpers, etc.

L'exécution locale de votre application est idéale pour la tester avec vous-même, mais vous aimeriez probablement inviter des amis à participer avec vous !

Vous pouvez créer un lien accessible de l'extérieur vers votre application fonctionnant localement à l'aide d'un outil comme ngrok. Il fournit une URL externe pour votre environnement local. La plateforme Nexmo Developer Platform propose un guide pour sur l'utilisation de ngrok que vous pouvez suivre.

Si vous souhaitez simplement être opérationnel, vous pouvez également déployer en un clic cette application depuis GitHub directement sur Heroku.

J'aimerais savoir ce que vous avez créé en utilisant l'API Video de Vonage ! Joignez-vous à la conversation sur notre Communauté Slack et partagez votre histoire !

Partager:

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

Ben est un développeur en seconde carrière qui a auparavant passé une décennie dans les domaines de la formation pour adultes, de l'organisation communautaire et de la gestion d'organisations à but non lucratif. Il a travaillé comme défenseur des développeurs pour Vonage. Il écrit régulièrement sur l'intersection du développement communautaire et de la technologie. Originaire de Californie du Sud et ayant longtemps vécu à New York, Ben réside aujourd'hui près de Tel Aviv, en Israël.