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

Erstellen einer Party mit Ruby on Rails und der Vonage Video API Teil 2

Zuletzt aktualisiert am May 5, 2021

Lesedauer: 12 Minuten

Dies ist der zweite Teil einer zweiteiligen Serie über die Erstellung einer Video-Watch-Party-Anwendung mit der Vonage Video API und Ruby on Rails.

In dem ersten Artikelsind wir die Schritte zur Erstellung des Backends der App durchgegangen. Wenn Sie diesen Beitrag noch nicht gelesen haben, wäre das ein guter Anfang. Jetzt werden wir uns auf das Frontend unserer Anwendung konzentrieren. Während das Backend hauptsächlich in Ruby geschrieben wurde, wird das Frontend aus einer Menge clientseitigem JavaScript bestehen.

Sobald wir fertig sind, haben wir eine App, mit der wir mit unseren Freunden chatten und gemeinsam Videos anschauen können!

Landing Page

Fangen wir an!

tl;dr Wenn Sie das Ganze überspringen und direkt mit dem Einsatz beginnen möchten, finden Sie den gesamten Code für die Anwendung auf GitHub.

Was werden wir bauen?

Bevor wir mit dem Programmieren beginnen, sollten wir uns einen Moment Zeit nehmen und besprechen, was wir bauen werden.

Wenn Sie sich an den ersten Beitrag erinnern, haben wir eine Video API Session ID instanziiert und erstellen aktiv Token für jeden Teilnehmer. Diese Informationen werden durch neu erstellte JavaScript-Variablen in den ERB-Ansichtsdateien an das Frontend weitergegeben. Außerdem werden auch Daten aus unseren Umgebungsvariablen an das Frontend weitergegeben. Wir werden all diese Informationen in dem Code verwenden, den wir schreiben werden, um das Erlebnis der App zu gestalten.

Ruby on Rails hat mit der Einführung von Webpack in Rails ab Version 5.1 einen langen Weg bei der Integration von clientseitigem JavaScript direkt in den Stack zurückgelegt. JavaScript wird eingebunden durch Packs platziert in /app/javascript/packs platziert und entweder als import oder require() Anweisungen innerhalb der application.js Datei innerhalb des Verzeichnisses.

Wir werden die verschiedenen Aspekte unseres Codes in verschiedene Dateien aufteilen, so dass Ihr Ordner am Ende die folgenden Dateien enthält:

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

Jede Datei, außer application.jsCode enthalten, der verschiedene Aspekte abdeckt:

  • app_helpers.js: Funktionsübergreifender Code, der für das gesamte Frontend benötigt wird

  • chat.js: Erstellen einer Chat Klasse, die verwendet wird, um Instanzen des Textchats zu instanziieren

  • opentok_screenshare.js: Der clientseitige Code für die Screenshare-Ansicht

  • opentok_video.js: Der client-seitige Code für die Video-Chat-Ansicht

  • party.js: Erstellen einer Party Klasse, die dazu verwendet wird, Instanzen des Video-Chats zu instanziieren

  • screenshare.js: Erstellen einer Screenshare Klasse, mit der Instanzen der Screenshare-Funktionalität instanziiert werden sollen

Bevor wir den Code erstellen, fügen wir diese Dateien in die application.js Datei hinzufügen, die Webpack anweist, sie zur Laufzeit zu kompilieren:

// application.js

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

Erstellen der JavaScript-Pakete

In jedem Unterabschnitt werden wir die oben genannten JavaScript-Dateien erstellen.

Dieapp_helpers.js Datei

Die Datei app_helpers.js Datei wird allgemeine Hilfsfunktionen enthalten, die wir in den restlichen Code exportieren werden, um sie in der gesamten Anwendung zu verwenden. Wir werden Folgendes erstellen screenshareMode(), setButtonDisplay(), formatChatMsg(), und streamLayout() Funktionen.

Die Funktion screenshareMode() Funktion nutzt die Vonage Video API Signal API, um eine Nachricht an die Browser aller Teilnehmer zu senden, die eine window.location Änderung auslöst. Die Signal-API ist dieselbe API, die wir für den Text-Chat verwenden werden, was der einfachste Anwendungsfall ist. Wie wir jedoch in dieser Funktion sehen werden, bietet die Signal-API eine intuitive und leistungsstarke Möglichkeit, den Fluss Ihrer Anwendung gleichzeitig für alle Teilnehmer zu steuern, ohne dass Sie viel Code schreiben müssen:

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

Die nächste Funktion, setButtonDisplay() ändert den Stil für das HTML-Element, das die Schaltfläche "Watch Mode On/Off" enthält, entweder in block oder none je nachdem, ob der Teilnehmer der Moderator ist oder nicht. Es gibt viele andere Möglichkeiten, dies zu tun, einschließlich sicherer Methoden. Um jedoch die Dinge für diese App zum Ansehen von Videos unter Freunden einfach zu halten, werden wir den Stil minimalistisch halten:

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

Die Funktion formatChatMsg() Funktion nimmt die Textnachricht, die der Teilnehmer gesendet hat, als Argument und formatiert sie für die Darstellung auf der Website. Die Funktion sucht nach Text, der durch zwei Doppelpunkte eingeklammert ist, und versucht, den Text innerhalb dieser Doppelpunkte als Emoji zu interpretieren. Außerdem wird der Name des Teilnehmers an jede Nachricht angehängt, damit jeder weiß, wer gerade spricht.

Um die Emojis hinzufügen zu können, müssen wir ein Node-Paket namens node-emoji und das können wir tun, indem wir const emoji = require('node-emoji); am Anfang der Datei hinzufügen und yarn add node-emoji in der Befehlszeile aufrufen. Die Funktion verwendet match() mit einem regulären Ausdruck, um nach Textstrings zu suchen, die durch zwei Doppelpunkte gekennzeichnet sind, und wenn sie übereinstimmen, ruft sie die emoji const auf, die wir definiert haben, um diese Zeichenfolge in ein Emoji zu verwandeln:

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

Die letzte Funktion innerhalb von app_helpers.js die wir erstellen müssen, ist streamLayout() die als Argumente das HTML-Element und die Anzahl der Teilnehmer annimmt. Die Funktion fügt dem Element je nach Anzahl der Teilnehmer CSS-Klassen hinzu oder entfernt sie, um die Präsentation des Video-Chats in ein Rasterformat zu ändern:

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

Diechat.js Datei

Der chat.js Code wird die Klasse Chat Klasse unter Verwendung einer constructor(). Diese Chat Klasse wird sowohl in der Video-Chat- als auch in der Screenshare-Ansicht aufgerufen und instanziiert:

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

Wir haben mehrere Eigenschaften für Chatgegeben, die meist auf verschiedenen Elementen im DOM und der Video API-Sitzung basieren. Die letzte Eigenschaft, this.setupEventListeners() ruft eine Funktion auf, die wir nun der Datei hinzufügen müssen:

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() erstellt eine EventListener für die Text-Chat submit Schaltfläche. Wenn eine neue Nachricht übermittelt wird, wird sie an die Signal-API gesendet, damit sie verarbeitet und an alle Teilnehmer gesendet werden kann. Wenn eine neue Nachricht empfangen wird, wird ein neues <p> Tag zum Chat-Element hinzugefügt, und das Text-Chat-Fenster des Teilnehmers wird gescrollt, um es anzuzeigen.

Die nächsten beiden Dateien, die wir erstellen werden, erfüllen ähnliche Funktionen, indem sie neue Klassen für den Video-Chat-Teilnehmer und für die Screenshare-Ansicht erstellen.

Dieparty.js Datei

In dieser Datei werden wir die Party Klasse, die zur Instanziierung neuer Instanzen des Video-Chats verwendet wird:

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

Die Funktion constructor() Funktion erhält die Video API Sitzung als Argument und übergibt diese an this.session. Die übrigen Eigenschaften werden definiert und mit Werten versehen. Die watchLink, subscribers, participantCount Eigenschaften stammen von den HTML-Elementen, während videoPublisher mit einer Funktion als Wert versehen ist, und clickStatus eine Voreinstellung von off.

Wir erstellen die setupVideoPublisher() Funktion an dieser Stelle. Die Funktion ruft die Video API JavaScript SDK initPublisher() Funktion auf, um die Veröffentlichung des Videos zu starten. Sie kann optionale Argumente aufnehmen, und als solche geben wir an, dass das Video 100 % der Breite und Höhe seines Elements einnehmen und an das Element angehängt werden soll:

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

Es gibt mehrere Aktionen, für die wir ebenfalls Ereignis-Listener erstellen und der Klasse hinzufügen müssen. Wir müssen darauf achten, wann die Sitzung verbunden ist, wann ein Videostream erstellt wurde, wann eine Verbindung hinzugefügt wurde und wann eine Verbindung zerstört wurde. Wenn eine Verbindung hinzugefügt oder zerstört wurde, erhöhen oder verringern wir die Teilnehmerzahl und teilen die Anzahl der Teilnehmer im Element participant count <div> Element auf der Seite:

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

Schließlich fügen wir einen weiteren Ereignis-Listener hinzu. Dieser Ereignis-Listener ist an die click Aktion der Schaltfläche "Überwachungsmodus ein/aus" verbunden. Wenn die Schaltfläche angeklickt wird, wird die Screenshare-Ansicht aufgerufen, wenn der Klickstatus "Aus" war. Sie werden sich daran erinnern, dass der Klickstatus bei der Erstellung der Klasse standardmäßig auf "Aus" gesetzt wurde:

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

Diescreenshare.js Datei

Die letzte Klasse, die wir erstellen werden, ist eine Screenshare Klasse, die für die Definition des Video-Screenshares zuständig ist. Die Funktion constructor() übernimmt die Video API-Sitzung und den Namen des Teilnehmers als Argumente:

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

Anders als die Party Klasse, ist die clickStatus hier standardmäßig auf on da wir von der Bildschirmfreigabe zurück in den Video-Chat-Modus wechseln wollen, wenn der Moderator auf die Schaltfläche "Watch Mode On/Off" klickt.

Wir verwenden auch toggle() um entweder den Bildschirm des Teilnehmers freizugeben, wenn der Teilnehmer der Moderator ist, oder um die Bildschirmfreigabe für alle anderen zu abonnieren:

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

Die Funktion shareScreen() Funktion, die in der toggle() aufgerufen wird, muss definiert werden:

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

Diese Funktion selbst hat drei Funktionen, die ebenfalls erstellt werden müssen. Mit der ersten Funktion wird der Bildschirm des Moderators veröffentlicht. Die Veröffentlichung des Bildschirms an sich beinhaltet jedoch nicht auch den Ton. Daher wird eine zweite Funktion den Ton vom Computer des Moderators veröffentlichen. Die letzte Funktion in shareScreen() zurück in die Videochat-Ansicht, wenn die Schaltfläche "Watch Mode On/Off" angeklickt wird:

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

Alle oben genannten Schritte dienen dazu, die Bildschirmfreigabe für den Moderator zu erstellen. Alle anderen in der App werden diese Bildschirmfreigabe abonnieren wollen. Wir verwenden die subscribe() Funktion zu tun. Dies wird die letzte Funktion in der Datei sein:

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

Wir sind nun bereit, alle diese Klassen, die wir definiert haben, in der Anwendung zum Laufen zu bringen, indem wir Instanzen von ihnen innerhalb der opentok_screenshare.js und opentok_video.js Dateien erstellen.

Erstellen vonopentok_video.js

Die opentok_video.js Datei wird ein neues Video-Chat-Erlebnis aufbauen. Der größte Teil der Arbeit wurde in den oben definierten Klassen erledigt, daher ist diese Datei relativ klein. Zuerst importieren wir die Klassen Chat und Party Klassen:

// opentok_video.js

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

Dann definieren wir eine globale leere Variable, die die Video API-Sitzung enthält:

var session = ''

Dann wird der Rest des Codes in drei Prüfungen verpackt, um sicherzustellen, dass wir uns auf dem richtigen Website-Pfad befinden, dass das DOM vollständig geladen ist und dass der Teilnehmername nicht leer ist:

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

Der Rest des Codes initiiert eine neue Video API-Sitzung, falls noch keine vorhanden ist, und instanziiert eine neue Chat und neue Party. Am Ende wird auch auf die Signal-API gewartet, um eine screenshare Datenmeldung mit dem Wert von on. Wenn diese Nachricht empfangen wird, wird die window.location nach /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;
        };
      });
    };
  });
}

Erstellen vonopentok_screenshare.js

Die letzte JavaScript-Datei, die wir erstellen werden, ist der letzten sehr ähnlich. Sie ist für die Screenshare-Ansicht verantwortlich und nutzt die Screenshare und Chat Klassen, die wir zuvor definiert haben:

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

Bevor wir das Ganze abschließen können, müssen wir zu guter Letzt noch den Stil des Frontends der Anwendung definieren. All dieser Code ist nutzlos, wenn er für die Teilnehmer nicht zugänglich ist.

Gestalten der Anwendung

Das Stylesheet für diese Anwendung wäre ohne die Hilfe meines Freundes und ehemaligen Kollegen nicht möglich gewesen, Hui Jing Chen der mir durch diesen Prozess viel über Front-End-Design beigebracht hat. Die Anwendung verwendet hauptsächlich Flexbox-Raster um die Elemente anzuordnen.

Beginnen wir mit der Erstellung einer custom.css Datei innerhalb von app/javascript/stylesheets. Wir wollen sicherstellen, dass sie in unserer Anwendung enthalten ist, also fügen wir eine Importzeile zu application.scss im gleichen Ordner, @import './custom.css';.

Zuerst fügen wir das Kern-Styling in 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;
}

Dann fügen wir das Styling für die Landing Page hinzu:

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

Wir wollen auch das Styling für den Text-Chat hinzufügen und vor allem sicherstellen, dass er an Ort und Stelle bleibt und nicht die ganze Seite scrollt, wenn er fortschreitet:

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

Lassen Sie uns nun das Styling für die Elemente Video-Chat und Bildschirmfreigabe erstellen:

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

Zum Schluss fügen wir eine Medienabfrage hinzu, die den Text-Chat auf kleineren Bildschirmen im richtigen Verhältnis hält:

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

Das war's! Die Anwendung, sowohl das Backend als auch das Frontend, wurde erstellt. Wir sind nun bereit, alles zusammenzufügen.

Alles zusammenfügen

Obwohl die Anwendung eine Kombination aus mehreren Programmiersprachen ist, nämlich Ruby und JavaScript, mit einem verflochtenen Backend und Frontend, ist es relativ einfach, sie auszuführen. Das liegt daran, dass Rails es uns ermöglicht, alles mit einem einzigen Befehl nahtlos zu integrieren.

In der Befehlszeile können Sie bundle exec rails s ausführen und beobachten, wie Ihr Rails-Server startet. Wenn Sie die Anwendung zum ersten Mal ausführen, werden Sie in Ihrer Konsolenausgabe die folgende, fast magische Zeile sehen:

[Webpacker] Compiling...

Tatsächlich werden Sie das jedes Mal sehen, wenn Sie eine Änderung an einem Ihrer JavaScript- oder CSS-Pakete vornehmen. Diese Ausgabe zeigt Ihnen, dass Rails Webpack verwendet, um alle Ihre Packs zu kompilieren und in die Anwendung einzubinden. Sobald die [Webpacker] Compiling... abgeschlossen ist, werden Sie eine Liste aller kompilierten Pakete sehen:

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]

Die Dateinamen spiegeln wider, dass sie herunterkompiliert wurden, aber Sie können immer noch Ihre Pack-Namen darin sehen, wenn Sie genau hinschauen, wie opentok_screenshare, party, app_helpers, usw.

Wenn Sie Ihre Anwendung lokal ausführen, können Sie sie mit sich selbst testen, aber wahrscheinlich möchten Sie auch Freunde einladen, mit Ihnen zu testen!

Mit einem Tool wie ngrok können Sie einen von außen zugänglichen Link zu Ihrer lokal laufenden Anwendung erstellen. Damit erhalten Sie eine externe URL für Ihre lokale Umgebung. Die Nexmo Developer Platform bietet eine Anleitung zur ngrok einrichten und nutzen die Sie befolgen können.

Wenn Sie einfach loslegen möchten, können Sie diese Anwendung auch mit einem Klick von GitHub direkt auf Heroku bereitstellen.

Ich würde gerne hören, was Sie mit der Vonage Video API entwickelt haben! Bitte beteiligen Sie sich an der Diskussion auf unserem Gemeinschaft Slack und teilen Sie Ihre Geschichte!

Teilen Sie:

https://a.storyblok.com/f/270183/384x384/e5480d2945/ben-greenberg.png
Ben GreenbergVonage Ehemalige

Ben ist ein Entwickler im zweiten Beruf, der zuvor ein Jahrzehnt in den Bereichen Erwachsenenbildung, Community-Organisation und Non-Profit-Management tätig war. Er arbeitete als Anwalt für Entwickler bei Vonage. Er schreibt regelmäßig über die Überschneidung von Gemeindeentwicklung und Technologie. Ursprünglich aus Südkalifornien stammend und lange Zeit in New York City ansässig, wohnt Ben jetzt in der Nähe von Tel Aviv, Israel.