
Partager:
Benjamin Aronov est un défenseur des développeurs chez Vonage. C'est un bâtisseur de communauté qui a fait ses preuves, avec une formation en Ruby on Rails. Benjamin apprécie les plages de Tel Aviv, où il vit. Sa base à Tel Aviv lui permet de rencontrer et d'apprendre de certains des meilleurs fondateurs de startups du monde. En dehors de la technologie, Benjamin aime voyager à travers le monde à la recherche du parfait pain au chocolat.
Vonage Video Express avec Ruby on Rails Partie 2
Temps de lecture : 10 minutes
Voici la deuxième partie d'une série de deux articles sur la création d'une application de veille vidéo utilisant Ruby on Rails avec l'API Video de Vonage et la bibliothèque Video Express.
Dans la Partie 1nous avons décrit les étapes de la construction de l'application Rails, montré comment utiliser quelques composants Video, et fait fonctionner le chat vidéo Video Express. Si vous n'avez pas encore lu cet article, c'est un bon point de départ.
Une fois que nous aurons terminé, nous aurons une application watch party que nous pourrons utiliser pour discuter avec nos amis et regarder du sport ou des vidéos ensemble !
Ce que fait l'application
Pour rappel, nous développons une application de Video-conférence qui permet aux utilisateurs de disposer d'une barre d'outils pour différents contrôles audio/vidéo. En outre, l'application donne au modérateur la possibilité d'envoyer le groupe de surveillance dans différents modes de visualisation.
À ce stade, nous disposons d'un Video Express opérationnel. Room. Cet objet nous permet d'appeler différentes fonctions qui exécutent les actions de notre barre d'outils. Nous voulons donner à l'utilisateur un moyen de déclencher cette fonctionnalité, ce que nous ferons avec les composants Vivid. Nous allons organiser notre HTML et notre JS en composants. Avec Webpack, nous allons ensuite import nos modules et require nos composants en application.js ce qui exposera notre Javascript au côté client.
Construction des composants de l'aide
Le reste de ce tutoriel sera consacré à la construction des composants qui permettent aux utilisateurs de contrôler leur salle Video Express. Chaque composant suivra une structure similaire : HTML avec des composants Vivid et Javascript pour déclencher les fonctions de Video Express.
Organiser le HTML
Construisons nos partiels où le HTML vivra. Depuis la racine de votre application, à l'aide de la ligne de commande, exécutez :
mkdir app/views/components
touch app/views/components/_header.html.erb touch app/views/components/_toolbox.html.erb
Et mettez à jour le fichier party.html.erb pour rendre les partiels :
<header>
<%= render partial: 'components/header' %>
</header>
<main class="app">
<div id="roomContainer"></div>
<toolbar>
<%= render partial: 'components/toolbar' %>
</toolbar>
Organiser le Javascript
De la même manière que nous avons un dossier composants dans notre dossier Views, créons un dossier composants dans notre dossier Javascript pour abriter la logique de nos composants correspondants.
mkdir app/javascript/components
C'est ici que nous ajouterons nos fichiers de composants : touch app/javascript/components/header.js touch app/javascript/components/toolbar.js
Pour les requérir pour notre côté client via Webpack, ajoutez les lignes suivantes dans Application.js sous nos importations de modules.
require("components/header");
require("components/toolbar");Parce que notre Javascript répondra aux actions de l'utilisateur dans le DOM, nous voulons nous assurer que le Javascript est chargé par Rails après le chargement du DOM. Nous devons donc faire un petit ajout et ajouter defer:true à l'élément javascript_pack_tag dans application.html.erb:
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload', defer: true %>
Nous sommes maintenant prêts à construire nos composants.
Construction de l'en-tête
Construire le HTML
Un rappel de l'en-tête que nous voulons construire :
The Header in Moderator View
La bonne nouvelle, c'est que Vivid a exactement ce qu'il nous faut, un Barre d'application supérieure . La barre d'application supérieure est livrée avec quelques options d'emplacement, mais deux d'entre elles nous intéressent : title et actionItems. Le titre est idéal pour un logo ou, dans notre cas, un titre. Et le actionitems peut être utilisé comme contenu de la barre d'application. C'est ici que nous ajouterons la bascule pour que le modérateur puisse changer de mode entre le mode "chill" et le mode "party". Nous pouvons accomplir cela avec le composant vwc-switch composant.
A l'intérieur app/view/watch_party/_header.html.erb se présentera comme suit :
<vwc-top-app-bar-fixed alternate="true">
<span slot="title" id="title">Big Game Chill Zone</span>
<% if @name == @moderator_name %>
<span slot="actionItems" id="mode-name">Chill Mode</span>
<vwc-switch slot="actionItems"></vwc-switch>
<% else %>
<span slot="actionItems"><%= @moderator_name %> is the host</span>
<% end %>
</vwc-top-app-bar-fixed>
Construction de l'en-tête Javascript
Dans le fichier components/header.js nous aurons 3 parties essentielles : l'écoute d'un basculement, le basculement de l'écran de partage et le basculement des visuels d'accompagnement.
Note importante concernant Video Express
Dans Video Express, vous disposez de deux configurations : "Grille" et "Haut-parleur actif". Video Express vous offre un appel vidéo standardisé et rapide ! Mais si vous souhaitez vous écarter du comportement par défaut, soyez prudent et utilisez plutôt l'API Video complète.
Dans cet exemple, le modérateur est le seul à partager l'écran. On pourrait penser que la disposition "orateur actif" nous permet de faire de l'écran partagé l'écran dominant. Cependant, dans les vidéoconférences normalisées, le comportement attendu est que le partageur d'écran présente à partir d'un autre onglet. Par défaut, Video Express ne fait donc pas de la fenêtre de l'écran partagé la vue dominante.
Dans notre cas d'utilisation, le modérateur n'a pas besoin de contrôler l'écran et veut voir son partage d'écran en grand, comme tout le monde. Ce n'est pas le comportement par défaut. Nous devrons l'élaborer nous-mêmes. Heureusement, Video Express propose l'option [screenSharingContainer](https://tokbox.com/developer/video-express/reference/room.html) qui nous permettra de construire ce que nous voulons.
Nous pouvons voir dans la documentation qu'il suffit d'ajouter un DIV vide avec l'id de screenSharingContainer. Mais nous devons faire en sorte que cela soit joli et que cela permette de tirer parti du gestionnaire de linéaire de VideoExpress pour la réactivité. De plus, nous ne voulons que cela s'applique au modérateur en mode partage d'écran. Notre solution est donc d'ajouter l'élément screenSharingContainer à côté de layoutContainer et d'écrire du CSS pour que la vue du modérateur soit aussi proche que possible de ce que voient tous les autres !
Commençons par créer la logique de base de l'écouteur pour le basculement :
const switch_btn = document.querySelector('vwc-switch');
if (switch_btn !== null){
switch_btn.addEventListener('change', (event) => {
if (event.target.checked){
<!-- Custom Styles To Scope Moderator View --->
<!-- Start Screen Share -->
}
else if (!event.target.checked){
<!-- Stop Screen Share -->
<!-- Remove Custome Styles From Moderator --->
} else{
console.log("Error in Switch Button Listener");
}
});
}
Avant de pouvoir déclencher le partage d'écran dans Video Express, nous devons préparer la vue du modérateur de manière à ce que le style personnalisé, qui touchera les boutons layoutContainerWrapper et layoutContainer n'affecte pas la vue de tous les autres. Nous appellerons cette fonction addModeratorCustomStyles.
let addModeratorCustomStyles = () => {
mode_name.innerHTML = "Watch Mode"
layoutContainerWrapper.firstElementChild.classList.add("moderator-screenshare");
layoutContainerWrapper.classList.add("moderator-screenshare");
screenShare.setAttribute("id", "screenSharingContainer");
layoutContainer.appendChild(screenShare);
}
Nous pouvons voir que cette fonction fait deux choses : elle met à jour l'étiquette de la bascule et ajoute un identifiant de screenSharingContainer. Cet identifiant ajouté nous permet d'appliquer le CSS uniquement au modérateur.
Lorsque le partage d'écran s'arrête, nous devons supprimer le style personnalisé afin de ne pas perturber l'affichage du modérateur. Nous avons donc une fonction removeModeratorCustomStyles pour annuler tout ce qui a été fait auparavant :
let removeModeratorCustomStyles = () => {
mode_name.innerHTML = "Chill Mode";
layoutContainerWrapper.firstElementChild.classList.remove("moderator-screenshare");
layoutContainerWrapper.classList.remove("moderator-screenshare");
screenShare.removeAttribute("id");
layoutContainer.removeChild(layoutContainer.lastChild);
}
Notre en-tête à bascule est maintenant pratiquement terminé. Il ne nous reste plus qu'à interroger nos éléments et à appeler les fonctions de partage d'écran de Video Express. L'en-tête complet ressemble à ceci :
const switch_btn = document.querySelector('vwc-switch');
const layoutContainer = document.querySelector('#layoutContainerWrapper');
const screenShare = document.createElement('div');
const layoutContainerWrapper = document.querySelector('#layoutContainerWrapper');
const mode_name = document.querySelector('#mode-name');
let addModeratorCustomStyles = (layoutContainer, screenShare, layoutContainerWrapper, mode_name) => {
mode_name.innerHTML = "Watch Mode";
layoutContainerWrapper.firstElementChild.classList.add("moderator-screenshare");
layoutContainerWrapper.classList.add("moderator-screenshare");
screenShare.setAttribute("id", "screenSharingContainer");
layoutContainer.appendChild(screenShare);
}
let removeModeratorCustomStyles = (layoutContainer, screenShare, layoutContainerWrapper, mode_name) => {
mode_name.innerHTML = "Chill Mode";
layoutContainerWrapper.firstElementChild.classList.remove("moderator-screenshare");
layoutContainerWrapper.classList.remove("moderator-screenshare");
screenShare.removeAttribute("id");
layoutContainer.removeChild(layoutContainer.lastChild);
}
if (switch_btn !== null){
switch_btn.addEventListener('change', (event) => {
if (event.target.checked){
addModeratorCustomStyles(layoutContainer, screenShare, layoutContainerWrapper, mode_name);
room.startScreensharing('screenSharingContainer');
} else if (!event.target.checked){
room.stopScreensharing('screenSharingContainer');
removeModeratorCustomStyles(layoutContainer, screenShare, layoutContainerWrapper, mode_name);
} else{
console.log("Error in Switch Button Listener");
}
});
}
Création de la barre d'outils HTML
Nous pouvons maintenant ajouter le dernier composant et le plus compliqué : la barre d'outils. Mais ce ne sera pas si difficile, il suffit de construire le HTML avec des composants Video et d'ajouter du Javascript pour déclencher les fonctions de Video Express. Vous connaissez la marche à suivre !
Un rappel de la barre d'outils que nous voulons construire :
The Toolbar To Control The Video Call
Construction des boutons à bascule
Nous pouvons voir dans la barre d'outils qu'il y a 3 groupes de boutons qui activeront/désactiveront certaines fonctions dans la pièce : mute/unmute all, disable/enable microphone, et disable/enable video camera. Pour ces trois groupes, nous utiliserons deux boutons de Vivid, puis nous utiliserons Javascript pour cacher le bouton inactif.
<!-- Mute all / Unmute all -->
<vwc-icon-button icon="audio-max-solid" shape="circled" layout="filled" id="mute-all" class="white-border"></vwc-icon-button>
<vwc-icon-button icon="audio-off-solid" shape="circled" layout="filled" id="unmute-all" class="hidden white-border"></vwc-icon-button><!-- Mute self / Unmute self -->
<vwc-icon-button icon="mic-mute-solid" shape="circled" layout="ghost" id="mute-self" class="vvd-scheme-alternate" ></vwc-icon-button>
<vwc-icon-button icon="microphone-2-solid" shape="circled" layout="ghost" id="unmute-self" class="hidden vvd-scheme-alternate"></vwc-icon-button>
<!-- Disable camera / Enable camera -->
<vwc-icon-button icon="video-off-solid" shape="circled" layout="ghost" id="hide-self" class="vvd-scheme-alternate" ></vwc-icon-button>
<vwc-icon-button icon="video-solid" shape="circled" layout="ghost" id="unhide-self" class="hidden vvd-scheme-alternate"></vwc-icon-button>
Création de listes déroulantes
Nous pouvons voir que les deuxième et troisième séries de boutons sont un peu différentes. Ils devraient également être accompagnés d'une liste déroulante permettant à l'utilisateur de sélectionner l'entrée associée : microphone ou caméra. Ceci est possible avec l'élément <vwc-action-group élément. La partie gauche du groupe d'action est directement issue de la documentation avec un bouton et un séparateur. Du côté droit, nous utiliserons le composant Vivid vwc-select pour générer un élément select que nous pouvons cibler avec les différentes options que nous recevons de VideoExpress. Nous transmettons également les vwc-select deux options : selected et disabled pour lui demander d'afficher et de désactiver l'élément par défaut, qui servira d'étiquette. vwc-list-item qui servira d'étiquette.
Nos deux groupes d'action avec boutons se présentent désormais comme suit :
<!-- Mute Self / Unmute Self -->
<!-- Select Mic Input -->
<vwc-action-group layout="outlined" shape="pill" class="vvd-scheme-alternate">
<vwc-icon-button icon="mic-mute-solid" shape="circled" layout="ghost" id="mute-self" class="vvd-scheme-alternate" ></vwc-icon-button>
<vwc-icon-button icon="microphone-2-solid" shape="circled" layout="ghost" id="unmute-self" class="hidden vvd-scheme-alternate"></vwc-icon-button>
<span role="separator"></span>
<vwc-select appearance="ghost" id="audio-input" class="select-max-width">
<vwc-list-item
disabled
selected
>
Mic
</vwc-list-item>
</vwc-select>
</vwc-action-group>
<!-- Disable Camera / Enable Camera -->
<!-- Select Camera Input -->
<vwc-action-group layout="outlined" shape="pill" class="vvd-scheme-alternate">
<vwc-icon-button icon="video-off-solid" shape="circled" layout="ghost" id="hide-self" class="vvd-scheme-alternate" ></vwc-icon-button>
<vwc-icon-button icon="video-solid" shape="circled" layout="ghost" id="unhide-self" class="hidden vvd-scheme-alternate"></vwc-icon-button>
<span role="separator"></span>
<vwc-select appearance="ghost" class="select-max-width" id="video-input">
<vwc-list-item
disabled
selected
>
Camera
</vwc-list-item>
</vwc-select>
</vwc-action-group>
Nous pouvons voir que nous avons un troisième groupe d'action : les entrées audio. Nous pouvons utiliser la même structure :
<!-- Select Audio Output -->
<vwc-action-group layout="outlined" shape="pill" class="vvd-scheme-alternate" id="audio-output-target">
<vwc-icon-button icon="headset-solid" shape="circled" layout="ghost" class="vvd-scheme-alternate"></vwc-icon-button>
<span role="separator"></span>
<vwc-select appearance="ghost" id="audio-output" class="select-max-width">
<vwc-list-item
disabled
selected
>
Audio
</vwc-list-item>
</vwc-select>
</vwc-action-group>
Construire le ToolTips HTML
Donnons à nos utilisateurs un peu plus d'informations sur ces composants. Nous pouvons le faire de manière élégante en utilisant les infobulles de Vivid.
Les infobulles sont composées de trois parties : l'élément anchor, le corneret la id. L'ancre indique à la bulle d'aide l'élément HTML auquel s'accrocher. L'ancrage coin indique à la bulle d'aide dans quelle direction s'afficher, par rapport à l'ancre. Enfin, l'élément id est un élément que nous utiliserons en Javascript pour déclencher l'affichage de l'infobulle lorsqu'un utilisateur survole l'ancre.
Avec les infobulles ajoutées, notre _toolbar.html.erb complet ressemble à ceci :
<!-- Mute all / Unmute all -->
<vwc-icon-button icon="audio-max-solid" shape="circled" layout="filled" id="mute-all" class="white-border"></vwc-icon-button>
<vwc-icon-button icon="audio-off-solid" shape="circled" layout="filled" id="unmute-all" class="hidden white-border"></vwc-icon-button>
<vwc-tooltip anchor="mute-all" text="Mute All" corner="top" id="mute-all-tooltip"></vwc-tooltip>
<vwc-tooltip anchor="unmute-all" text="Un-Mute All" corner="top" id="unmute-all-tooltip"></vwc-tooltip>
<!-- Mute Self / Unmute Self -->
<!-- Select Mic Input -->
<vwc-action-group layout="outlined" shape="pill" class="vvd-scheme-alternate">
<vwc-icon-button icon="mic-mute-solid" shape="circled" layout="ghost" id="mute-self" class="vvd-scheme-alternate" ></vwc-icon-button>
<vwc-icon-button icon="microphone-2-solid" shape="circled" layout="ghost" id="unmute-self" class="hidden vvd-scheme-alternate"></vwc-icon-button>
<vwc-tooltip anchor="mute-self" text="Disable Mic" corner="top" id="mute-self-tooltip"></vwc-tooltip>
<vwc-tooltip anchor="unmute-self" text="Enable Mic" corner="top" id="unmute-self-tooltip"></vwc-tooltip>
<span role="separator"></span>
<vwc-select appearance="ghost" id="audio-input" class="select-max-width">
<vwc-list-item
disabled
selected
>
Mic
</vwc-list-item>
</vwc-select>
</vwc-action-group>
<!-- Disable Camera / Enable Camera -->
<!-- Select Camera Input -->
<vwc-action-group layout="outlined" shape="pill" class="vvd-scheme-alternate">
<vwc-icon-button icon="video-off-solid" shape="circled" layout="ghost" id="hide-self" class="vvd-scheme-alternate" ></vwc-icon-button>
<vwc-icon-button icon="video-solid" shape="circled" layout="ghost" id="unhide-self" class="hidden vvd-scheme-alternate"></vwc-icon-button>
<vwc-tooltip anchor="hide-self" text="Disable Camera" corner="top" id="hide-self-tooltip"></vwc-tooltip>
<vwc-tooltip anchor="unhide-self" text="Enable Camera" corner="top" id="unhide-self-tooltip"></vwc-tooltip>
<span role="separator"></span>
<vwc-select appearance="ghost" class="select-max-width" id="video-input">
<vwc-list-item
disabled
selected
>
Camera
</vwc-list-item>
</vwc-select>
</vwc-action-group>
<!-- Select Audio Output -->
<vwc-action-group layout="outlined" shape="pill" class="vvd-scheme-alternate" id="audio-output-target">
<vwc-tooltip anchor="audio-output-target" text="Select Audio Output" corner="top" id="audio-output-tooltip"></vwc-tooltip>
<vwc-icon-button icon="headset-solid" shape="circled" layout="ghost" class="vvd-scheme-alternate"></vwc-icon-button>
<span role="separator"></span>
<vwc-select appearance="ghost" id="audio-output" class="select-max-width">
<vwc-list-item
disabled
selected
>
Audio
</vwc-list-item>
</vwc-select>
</vwc-action-group>
Style de la barre d'outils
Avant de développer le Javascript pour les composants, faisons en sorte que la barre d'outils ressemble à notre maquette :
// toolbar styles
toolbar {
display: flex;
justify-content: space-around;
margin: 0 auto;
width: 650px;
background-color: var(--vvd-color-primary);
padding: 10px;
border-radius: 8px 8px 0 0;
}
.hidden {
display: none;
}
.white-border {
border: 2px solid white;
border-radius: 50%;
}
.select-max-width {
max-width: 130px;
}
vwc-tooltip {
--tooltip-inline-size: 100px;
text-align: center;
} Construire la barre d'outils en Javascript
En allant de gauche à droite, le premier composant que nous devons construire est le bouton "Mute All". Nous voulons qu'il s'agisse d'un bouton bascule qui active et désactive le son de tous les autres participants. Mais comment pouvons-nous avoir un bouton à bascule ? Nous avons besoin de deux boutons : un bouton "Mute All" et un bouton "Unmute All". Ce modèle sera répété plusieurs fois dans la barre d'outils, alors écrivons une fonction d'aide pour cela.
// toggle hide/display of buttons
let toggleButtonView = (buttonToHide, buttonToShow) => {
buttonToHide.style.display = "none";
buttonToShow.style.display = "block";
}
Construction du bouton Mute Others
Pour mettre tous les participants en sourdine, nous devons attendre que l'utilisateur déclenche l'action, puis faire défiler tous les participants dans l'instance de salle de cet utilisateur. Il est important de noter que la salle est locale à chaque utilisateur.
Pour désactiver l'audio, nous devons donc parcourir tous les participants de la salle et utiliser la fonction .camera.disableAudio() pour désactiver l'audio. Vous remarquerez que nous appelons cette fonction sur participant[1] parce que l'objet participant renvoie un tableau : [id, participantObject].
// toggle Mute All / Unmute All
let toggleMuteAllButton = (button, state, participants) =>{
button.addEventListener("click", function(){
Object.entries(participants).forEach(participant => {
if (state === "mute"){
toggleButtonView(mute_all_btn, unmute_all_btn)
participant[1].camera.disableAudio();
} else if (state === "unmute") {
toggleButtonView(unmute_all_btn, mute_all_btn)
participant[1].camera.enableAudio();
} else {
console.log("Error in toggleMuteAll")
}
})
})
}
const mute_all_btn = document.querySelector('#mute-all');
const unmute_all_btn = document.querySelector('#unmute-all');toggleMuteAllButton(mute_all_btn, "mute", room.participants);
toggleMuteAllButton(unmute_all_btn, "unmute", room.participants); Construction des boutons Mute Self et Disable Camera
Les boutons Mute/Unmute Self et Disable/Enable Camera suivent la même logique que le bouton MuteAll. Ils sont à l'écoute d'une action de l'utilisateur, vérifient un état et appellent une action dans VideoExpress. Cependant, ils sont beaucoup plus simples car VideoExpress nous fournit des fonctions pour vérifier l'état. Les deux fonctions sont room.camera.isVideoEnabled et room.camera.isAudioEnabled. De plus, nous n'avons besoin de déclencher l'action que pour un seul utilisateur.
Nous pouvons créer cette fonction toggleInputButton qui acceptera une condition, le booléen que nous recevons de nos fonctions Video Express, puis appellera l'action Video Express correspondante. Elle mettra également à jour la vue avec toggleButtonView.
// toggle button (display and functionality) of any audio and video input devices
let toggleInputButton = (condition, defaultBtn, altBtn, action) => {
if (condition()){
toggleButtonView(defaultBtn, altBtn);
action();
} else if (!condition()){
toggleButtonView(altBtn, defaultBtn);
action();
} else {
console.log(`Error in toggleInputButton. Condition: ${condition}`);
}
}
The toggle will need to be triggered on a user click, so we can wrap this in the `listenForToggle` function.
// listen for clicks to trigger toggle of input buttons
let listenForToggle = (condition, defaultBtn, altBtn, defaultAction, altAction) => {
defaultBtn.addEventListener("click", function(){
console.log("inside listenfortoggle listener")
toggleInputButton(condition, defaultBtn, altBtn, defaultAction)
})
altBtn.addEventListener("click", function(){
toggleInputButton(condition, defaultBtn, altBtn, altAction)
})
}
Nous aurons besoin des éléments DOM spécifiques à écouter et à passer listenForToggleNous allons donc créer des sélecteurs de requête.
const mute_self_btn = document.querySelector('#mute-self');
const unmute_self_btn = document.querySelector('#unmute-self');
const hide_self_btn = document.querySelector('#hide-self');
const unhide_self_btn = document.querySelector('#unhide-self');Enfin, nous pouvons appeler notre code en passant les conditions, les boutons et les actions :
listenForToggle(room.camera.isVideoEnabled, hide_self_btn, unhide_self_btn, room.camera.disableVideo, room.camera.enableVideo);
listenForToggle(room.camera.isAudioEnabled, mute_self_btn, unmute_self_btn, room.camera.disableAudio, room.camera.enableAudio); Construction des entrées de l'appareil de sélection
Nos listes déroulantes de sélection pour les entrées audio et vidéo sont actuellement vides. Remplissons-les avec les appareils de chaque utilisateur. Video Express nous offre cette fonctionnalité dès sa sortie de l'emballage avec VideoExpress.getDevices(). Cette fonction renvoie un tableau d'entrées audio et vidéo pour l'instance VideoExpress locale.
Nous devons récupérer cette liste et la trier afin de pouvoir ajouter des périphériques audio à l'entrée du microphone et des périphériques vidéo à l'entrée de la caméra. Pour ce faire, nous utilisons cette fonction asynchrone, qui attend que la promesse revienne après avoir interrogé VideoExpress, puis itère sur les appareils et les ajoute à l'élément select.
// Retrieve available input devices from VideoExpress
// add retrieved input devices to select options
async function getDeviceInputs(audioTarget, videoTarget){
const audio = document.querySelector(`${audioTarget}`);
const video = document.querySelector(`${videoTarget}`);
const availableDevices = VideoExpress.getDevices();
availableDevices.then(devices=> {
devices.forEach(device => {
if (device.kind === "audioInput"){
let opt = document.createElement('vwc-list-item');
opt.value = device.deviceId;
opt.innerHTML = device.label;
audio.appendChild(opt);
}
else if (device.kind === "videoInput"){
let opt = document.createElement('vwc-list-item');
opt.value = device.deviceId;
opt.innerHTML = device.label;
video.appendChild(opt);
}
else{
console.log("Error in retrieveDevices");
}
})
})
}
Nous souhaitons également mettre à jour la salle lorsqu'un utilisateur modifie sa sélection. Nous pouvons déclencher cela en écoutant les changements dans les menus de sélection et en appelant soit room.camera.setAudioDevice() ou room.camera.setVideoDevice().
// // listen for changes to selected audio/video inputs
// // update room when inputs are changed
let listenInputChange = (target) => {
const targetSelect = document.querySelector(`${target}`);
targetSelect.addEventListener('change', (inputOption) => {
if (target === "vwc-select#audio-input"){
room.camera.setAudioDevice(inputOption.target.value);
}
else if (target === "vwc-select#video-input"){
room.camera.setVideoDevice(inputOption.target.value);
}
else{
console.log("Error in listenInputChange");
}
})
}
Pour résumer, l'appel de nos fonctions se présente comme suit :
getDeviceInputs("vwc-select#audio-input", "vwc-select#video-input");
listenInputChange("vwc-select#audio-input");
listenInputChange("vwc-select#video-input"); Construction de la sortie audio Select
La récupération de la liste des sorties audio de VideoExpress est presque identique à celle des entrées. Cependant, ici, nous n'avons pas de périphériques pour les sorties Video, notre code est donc presque identique, sauf sans le filtrage. Tout comme nous avions VideoExpress.getDevices()nous avons maintenant VideoExpress.getAudioOutputDevices(). Et comme pour room.camera.setAudioDevice() ou room.camera.setVideoDevice(), maintenant nous avons VideoExpress.setAudioOutputDevice().
Comme nous n'écoutons qu'un seul changement, nous pouvons tout regrouper dans une seule fonction :
// Retrieve lists of auidoOutput
// add audioOutputs to select menu
// On user select new option, update audio input
async function audioOutputs() {
var audioOutputs = await VideoExpress.getAudioOutputDevices();
const audioOutputSelect = document.querySelector('vwc-select#audio-output');
audioOutputs.forEach(output => {
let opt = document.createElement('vwc-list-item');
opt.value = output.deviceId;
opt.innerHTML = output.label;
audioOutputSelect.appendChild(opt);
})
audioOutputSelect.addEventListener('change', (audioOutputOption) => {
VideoExpress.setAudioOutputDevice(audioOutputOption.target.value);
});
}
Et n'oubliez pas de l'appeler dans votre code !
audioOutputs(); Construction des infobulles
Notre toute dernière étape dans la barre d'outils est d'ajouter le comportement d'apparition et de disparition des infobulles. Nous faisons cela avec vanilla.js en ciblant l'ancre que nous appellerons le target et l'infobulle associée que nous appelons targertToolTip. Les éléments mouseover et mouseout nous permettent d'écouter quand un utilisateur survole une ancre et quand il s'arrête.
Notre addToolTipListeners ressemble à ceci :
// toggle tooltips on hover
let addToolTipListeners = (toolTipsToListen) => {
toolTipsToListen.forEach(toolTipToListen => {
const target = document.querySelector(`#${toolTipToListen.targetId}`);
const targetToolTip = document.querySelector(`#${toolTipToListen.toolTipId}`);
target.addEventListener('mouseover', (event) => targetToolTip.open = !targetToolTip.open);
target.addEventListener('mouseout', (event) => targetToolTip.open = !targetToolTip.open);
})
}
Nous définirons ensuite les infobulles à écouter :
const toolTipsToListen = [
{targetId: "hide-self", toolTipId:"hide-self-tooltip"},
{targetId: "unhide-self", toolTipId: "unhide-self-tooltip"},
{targetId: "mute-self", toolTipId: "mute-self-tooltip"},
{targetId: "unmute-self", toolTipId: "unmute-self-tooltip"},
{targetId: "mute-all", toolTipId: "mute-all-tooltip"},
{targetId: "unmute-all", toolTipId: "unmute-all-tooltip"},
{targetId: "audio-output-target", toolTipId: "audio-output-tooltip"},
]Enfin, nous appelons notre code.
addToolTipListeners(toolTipsToListen);Notre code final pour toolbar.js ressemble à ceci :
// Start of Toolbar Code
// toggle hide/display of buttons
let toggleButtonView = (buttonToHide, buttonToShow) => {
buttonToHide.style.display = "none";
buttonToShow.style.display = "block";
}
// toggle Mute All / Unmute All
let toggleMuteAllButton = (button, state, participants) =>{
button.addEventListener("click", function(){
Object.entries(participants).forEach(participant => {
if (state === "mute"){
toggleButtonView(mute_all_btn, unmute_all_btn)
// why need both here???
participant[1].camera.disableAudio();
} else if (state === "unmute") {
toggleButtonView(unmute_all_btn, mute_all_btn)
participant[1].camera.enableAudio();
} else {
console.log("Error in toggleMuteAll")
}
})
})
}
// toggle button (display and functionality) of any audio and video input devices
let toggleInputButton = (condition, defaultBtn, altBtn, action) => {
if (condition()){
toggleButtonView(defaultBtn, altBtn);
action();
} else if (!condition()){
toggleButtonView(altBtn, defaultBtn);
action();
} else {
console.log(`Error in toggleInputButton. Condition: ${condition}`);
}
}
// listen for clicks to trigger toggle of input buttons
let listenForToggle = (condition, defaultBtn, altBtn, defaultAction, altAction) => {
defaultBtn.addEventListener("click", function(){
toggleInputButton(condition, defaultBtn, altBtn, defaultAction)
})
altBtn.addEventListener("click", function(){
toggleInputButton(condition, defaultBtn, altBtn, altAction)
})
}
// Retrieve available input devices from VideoExpress
// Add retrieved input devices to select options
async function getDeviceInputs(audioTarget, videoTarget){
const audio = document.querySelector(`${audioTarget}`);
const video = document.querySelector(`${videoTarget}`);
const availableDevices = VideoExpress.getDevices();
availableDevices.then(devices=> {
devices.forEach(device => {
if (device.kind === "audioInput"){
let opt = document.createElement('vwc-list-item');
opt.value = device.deviceId;
opt.innerHTML = device.label;
audio.appendChild(opt);
}
else if (device.kind === "videoInput"){
let opt = document.createElement('vwc-list-item');
opt.value = device.deviceId;
opt.innerHTML = device.label;
video.appendChild(opt);
}
else{
console.log("Error in retrieveDevices");
}
})
})
}
// listen for changes to selected audio/video inputs
// update room when inputs are changed
let listenInputChange = (target) => {
const targetSelect = document.querySelector(`${target}`);
targetSelect.addEventListener('change', (inputOption) => {
if (target === "vwc-select#audio-input"){
room.camera.setAudioDevice(inputOption.target.value);
}
else if (target === "vwc-select#video-input"){
room.camera.setVideoDevice(inputOption.target.value);
}
else{
console.log("Error in listenInputChange");
}
})
}
// Retrieve lists of auidoOutput
// Add audioOutputs to select menu
// On user select new option, update audio input
async function audioOutputs() {
var audioOutputs = await VideoExpress.getAudioOutputDevices();
const audioOutputSelect = document.querySelector('vwc-select#audio-output');
audioOutputs.forEach(output => {
let opt = document.createElement('vwc-list-item');
opt.value = output.deviceId;
opt.innerHTML = output.label;
audioOutputSelect.appendChild(opt);
})
audioOutputSelect.addEventListener('change', (audioOutputOption) => {
VideoExpress.setAudioOutputDevice(audioOutputOption.target.value);
});
}
// toggle tooltips on hover
let addToolTipListeners = (toolTipsToListen) => {
toolTipsToListen.forEach(toolTipToListen => {
const target = document.querySelector(`#${toolTipToListen.targetId}`);
const targetToolTip = document.querySelector(`#${toolTipToListen.toolTipId}`);
target.addEventListener('mouseover', (event) => targetToolTip.open = !targetToolTip.open);
target.addEventListener('mouseout', (event) => targetToolTip.open = !targetToolTip.open);
})
}
// define all our DOM elements which we'll want to listen to
const mute_all_btn = document.querySelector('#mute-all');
const unmute_all_btn = document.querySelector('#unmute-all');
const mute_self_btn = document.querySelector('#mute-self');
const unmute_self_btn = document.querySelector('#unmute-self');
const hide_self_btn = document.querySelector('#hide-self');
const unhide_self_btn = document.querySelector('#unhide-self');
const toolTipsToListen = [
{targetId: "hide-self", toolTipId:"hide-self-tooltip"},
{targetId: "unhide-self", toolTipId: "unhide-self-tooltip"},
{targetId: "mute-self", toolTipId: "mute-self-tooltip"},
{targetId: "unmute-self", toolTipId: "unmute-self-tooltip"},
{targetId: "mute-all", toolTipId: "mute-all-tooltip"},
{targetId: "unmute-all", toolTipId: "unmute-all-tooltip"},
{targetId: "audio-output-target", toolTipId: "audio-output-tooltip"},
]
// Call all of our functions!
toggleMuteAllButton(mute_all_btn, "mute", room.participants);
toggleMuteAllButton(unmute_all_btn, "unmute", room.participants);
listenForToggle(room.camera.isVideoEnabled, hide_self_btn, unhide_self_btn, room.camera.disableVideo, room.camera.enableVideo);
listenForToggle(room.camera.isAudioEnabled, mute_self_btn, unmute_self_btn, room.camera.disableAudio, room.camera.enableAudio);
getDeviceInputs("vwc-select#audio-input", "vwc-select#video-input");
listenInputChange("vwc-select#audio-input");
listenInputChange("vwc-select#video-input");
audioOutputs();
addToolTipListeners(toolTipsToListen);
Finale
Et....c'est tout ! Avec un peu de Ruby et un peu plus de manipulation de Javascript, vous avez une application de vidéoconférence complète.
Nous pouvons lancer l'application et l'essayer avec des amis !
La course à pied ngrok
Le moyen le plus simple est d'utiliser ngrok. Installez ngrok si vous ne l'avez pas.
Utilisez ensuite ngrok pour lancer un serveur accessible au public. À partir de la ligne de commande, exécutez ngrok http 3000
Dans votre terminal, vous verrez une fenêtre qui ressemble à ceci :
ngrok server screenshot
Vous devez copier la ligne qui se termine par ngrok.io. C'est l'URL temporairement accessible vers laquelle ngrok redirigera votre serveur Rails. Nous avons besoin que Rails donne la permission à ngrok d'être un hôte. Dans notre fichier config/environments/development.rb à l'intérieur de la ligne Rails.application.configure do nous devons ajouter la ligne suivante :
config.hosts << "[ngrok url]"
Par exemple, dans l'exemple ci-dessus d'exécution de ngrok, j'ajouterais :
config.hosts << "c3b9-146-185-57-50.ngrok.io"
Nous pouvons maintenant lancer notre serveur rails :
rails s
Et maintenant, notre application est opérationnelle et vous pouvez inviter vos amis à rejoindre votre watch party en leur envoyant l'URL de ngrok. Et le mot de passe !
GIF Preview Of Finished Video Conferencing App Built With Vonage Video Express
Prendre contact
Comment avez-vous trouvé ce tutoriel ? Vous avez des questions ou des commentaires sur Video Express ou Vivid ? Rejoignez-nous sur le Slack des développeurs de Vonage. Et suivez-nous sur Twitter pour rester au courant des dernières mises à jour de Vonage Developer.
Partager:
Benjamin Aronov est un défenseur des développeurs chez Vonage. C'est un bâtisseur de communauté qui a fait ses preuves, avec une formation en Ruby on Rails. Benjamin apprécie les plages de Tel Aviv, où il vit. Sa base à Tel Aviv lui permet de rencontrer et d'apprendre de certains des meilleurs fondateurs de startups du monde. En dehors de la technologie, Benjamin aime voyager à travers le monde à la recherche du parfait pain au chocolat.