
Partager:
Enrico est un ancien membre de l'équipe Vonage. Il a travaillé en tant qu'ingénieur de solutions, aidant l'équipe de vente avec son expertise technique. Il est passionné par le cloud, les startups et les nouvelles technologies. Il est le cofondateur d'une startup WebRTC en Italie. En dehors du travail, il aime voyager et goûter autant d'aliments bizarres que possible.
Construire une application de Video Conference avec React-Native et l'API Video de Vonage
Temps de lecture : 9 minutes
Avoir une fonctionnalité d'appel vidéo dans votre application semble être un must-have de nos jours. Cependant, construire une application native à partir de zéro et gérer les différentes capacités d'Android et d'iOS peut être un processus très long et coûteux.
Dans ce tutoriel, nous allons vous montrer comment construire une app de Video chat dans React Native. Pour construire cette application, nous nous appuierons sur l'application Opentok React Native avec l'API Video API de Vonage.
Pour une version vidéo de ce tutoriel, consultez la vidéo ci-dessous :
Vous voulez aller plus loin ? Vous pouvez trouver le code de ce tutoriel sur GitHub.
Conditions préalables
Pour créer votre appli d'appels vidéo React Native, vous aurez besoin des éléments suivants :
Un compte gratuit Compte de développeur Vonage gratuit
Une compréhension de base de React Native
Comment construire une application d'appel vidéo React Native (5 étapes).
Pour créer une appli d'appels vidéo avec React Native et l'API de Vonage, suivez les étapes ci-dessous :
Mise en place du projet
Construire l'interface de Video Chat
Ajouter les flux vidéo des participants
Créer la barre d'outils de l'appel vidéo
Optimiser l'application pour les flux multiples
1. Mise en place du projet
Pour commencer, nous allons construire le framework de l'appli de Video chat en React Native.
Tout d'abord, clonez le répertoire suivant : https://github.com/enricop89/multiparty-video-react-native.git
Ensuite, installez les modules de nœuds requis : npm install
Pour iOS, installez les dépendances du fichier Podfile : cd ios/ && pod install
La structure du projet est composée des fichiers principaux suivants :
AndroidetiOSdossiers : c'est là que le code natif est compilé et exécuté.App.js: c'est le fichier principal, où l'état React, les accessoires, les gestionnaires d'événements et les méthodes sont définis.config.js: c'est là que les informations d'identification sont définies.
Le composant App sera notre vue d'atterrissage lorsque notre application sera lancée. Il disposera d'une fonction de rendu conditionnelle primaire pour lancer l'appel vidéo.
class App extends Component {
constructor(props) {
super(props);
this.apiKey = credentials.API_KEY;
this.sessionId = credentials.SESSION_ID;
this.token = credentials.TOKEN;
this.state = {
subscriberIds: [], // Array for storing subscribers
localPublishAudio: true, // Local Audio state
localPublishVideo: true, // Local Video state
joinCall: false, // State variable used to start the call
streamProperties: {}, // Handle individual stream properties,
mainSubscriberStreamId: null
};
}
joinCall = () => {
const { joinCall } = this.state;
if (!joinCall) {
this.setState({ joinCall: true });
}
};
endCall = () => {
const { joinCall } = this.state;
if (joinCall) {
this.setState({ joinCall: !joinCall });
}
};
joinVideoCall = () => {
return (
<View style={styles.fullView}>
<Button
onPress={this.joinCall}
title="JoinCall"
color="#841584"
accessibilityLabel="Join call">
Join Call
</Button>
</View>
);
};
render() {
return this.state.joinCall ? this.videoPartyView() : this.joinVideoCall();
}
}
La propriété joinCall sur l'état React déclenchera différentes vues en fonction de la valeur. Lorsque l'application est lancée, une simple vue avec un bouton "Rejoindre l'appel" sera affichée à l'utilisateur. Le bouton déclenchera un changement d'état et fera basculer la valeur joinCall pour afficher la vue plus complexe de l'appel vidéo.
2. Construire l'interface de Video Chat
Maintenant que nous avons construit un cadre pour notre projet, nous allons construire l'interface de l'application elle-même. Nous appellerons cette interface Vue de l'appel Video.
Avant de se plonger dans l'affichage des appels Video, il est utile de passer un peu de temps à explorer la opentok-react-native bibliothèque.
La bibliothèque est composée de trois éléments principaux : OTSession, OTPublisher et OTSubscriber. Chacun d'entre eux interagit avec la couche native (iOS et Android), en appelant les méthodes natives pour se connecter, publier et s'abonner.
Nous devrons également écouter les événements déclenchés par ces composants, en particulier les événements de session, tels que sessionConnected, sessionDisconnected, streamCreated et streamDestroyed (Documentation : Événements de session).
La vue d'appel Video est composée des éléments suivants :
Éditeur. La vue de l'éditeur affiche le flux vidéo de l'utilisateur principal.
Abonnés. Les vues des abonnés affichent les flux vidéo des autres participants.
Barre d'outils. La barre d'outils contient des boutons permettant d'accéder au microphone, à la caméra et de mettre fin à l'appel.
An example of the multiparty video view being built in this tutorial
3. Ajouter les flux de participants
Ensuite, nous ajouterons les flux vidéo des participants au chat (les abonnés et l'éditeur).
Pour ajouter des flux vidéo à l'interface, nous devons garder une trace des flux des abonnés, de l'abonné principal et de l'état de publication du microphone local et de la caméra. L'endroit idéal pour stocker ces informations est le React State.
constructor(props) {
super(props);
this.apiKey = credentials.API_KEY;
this.sessionId = credentials.SESSION_ID;
this.token = credentials.TOKEN;
this.state = {
subscriberIds: [], // Array for storing subscribers
localPublishAudio: true, // Local Audio state
localPublishVideo: true, // Local Video state
joinCall: false, // State variable used to start the call
streamProperties: {}, // Handle individual stream properties,
mainSubscriberStreamId: null
};
}
this.sessionEventHandlers = {
streamCreated: (event) => {
const streamProperties = {
...this.state.streamProperties,
[event.streamId]: {
subscribeToAudio: true,
subscribeToVideo: true,
},
};
this.setState({
streamProperties,
subscriberIds: [...this.state.subscriberIds, event.streamId],
});
},
streamDestroyed: (event) => {
const indexToRemove = this.state.subscriberIds.indexOf(event.streamId);
const newSubscriberIds = this.state.subscriberIds;
const streamProperties = { ...this.state.streamProperties };
if (indexToRemove !== -1) {
delete streamProperties[event.streamId];
newSubscriberIds.splice(indexToRemove, 1);
this.setState({ subscriberIds: newSubscriberIds });
}
}
}
SubscriberIds stocke les abonnés au sein d'une session. Chaque fois que nous recevons un streamCreated cela signifie que quelqu'un a rejoint la session et publié un flux, nous devons donc ajouter son streamId dans le subscriberIds tableau.
En revanche, lorsque nous recevons l'événement streamDestroyed nous devons supprimer le streamId du tableau des abonnés.
<View style={styles.fullView}>
<OTSession
apiKey={this.apiKey}
sessionId={this.sessionId}
token={this.token}
eventHandlers={this.sessionEventHandlers}>
<OTPublisher
properties={this.publisherProperties}
eventHandlers={this.publisherEventHandlers}
style={styles.publisherStyle}
/>
<OTSubscriber style={{ height: dimensions.height, width: dimensions.width }}
eventHandlers={this.subscriberEventHandlers}
streamProperties={this.state.streamProperties}
>
{this.renderSubscribers}
</OTSubscriber>
</OTSession>
</View>
Dans la fonction de rendu de la vue Video, nous devons ajouter OTSession, OTPublisher et OTSubscriber de la bibliothèque opentok-react-native de la bibliothèque. Dans la fonction OTSession nous définissons les informations d'identification et la fonction eventHandler en tant qu'accessoires du composant.
4. Créer la barre d'outils de l'appel Video
Maintenant que les flux vidéo fonctionnent, nous allons activer les fonctionnalités de notre barre d'outils d'appel vidéo.
Le composant OTPublisher initialisera un éditeur et publiera dans la session spécifiée lors du montage. Il est possible de spécifier différentes propriétés, telles que la position de la caméra, la résolution et d'autres (liste des options de l'éditeur : Options de l'éditeur). Dans cet exemple d'application, nous ne définirons que this.publisherProperties = { cameraPosition: 'front'};.
Assurez-vous d'avoir activé l'utilisation de la caméra et du microphone en ajoutant les entrées suivantes à votre fichier Info.plist (projet iOS) :
<key>NSCameraUsageDescription</key>
<string>Your message to user when the camera is accessed for the first time</string>
<key>NSMicrophoneUsageDescription</key>
<string>Your message to user when the microphone is accessed for the first time</string>Sinon, pour Android, ajoutez ce qui suit (les versions plus récentes d'Android-API Level 23 (Android 6.0) ont un modèle de permissions différent qui est déjà géré par cette bibliothèque) :
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="true" />Le composant OTPublisher possède également une propriété streamProperty qui gère les propriétés de l'éditeur transmises à l'instance native. En utilisant l'état React, nous pouvons déclencher des changements dans l'instance de l'éditeur en mettant à jour la variable this.publisherProperties en mettant à jour la variable Nous utilisons cette approche pour mettre en œuvre la barre d'outils avec les fonctions mute/unmute pour le microphone et la caméra. L'implémentation de la fonction est simple ; elle fait basculer la fonction publishAudio ou publierVidéo sur l'icône this.publisherProperties et les localPublishAudio et localPublishVideo pour ajuster l'icône du bouton en fonction de la valeur.
Le bouton de fin d'appel a une approche très similaire. La fonction endCall fait basculer la valeur joinCall sur l'état et réinitialise la vue à sa valeur initiale.
toggleAudio = () => {
let publishAudio = this.state.localPublishAudio;
this.publisherProperties = { ...this.publisherProperties, publishAudio: !publishAudio };
this.setState({
localPublishAudio: !publishAudio,
});
};
toggleVideo = () => {
let publishVideo = this.state.localPublishVideo;
this.publisherProperties = { ...this.publisherProperties, publishVideo: !publishVideo };
this.setState({
localPublishVideo: !publishVideo,
});
};
endCall = () => {
const { joinCall } = this.state;
if (joinCall) {
this.setState({ joinCall: !joinCall });
}
};
<View style={styles.buttonView}>
<Icon.Button
style={styles.iconStyle}
backgroundColor="#131415"
name={this.state.localPublishAudio ? 'mic' : 'mic-off'}
onPress={this.toggleAudio}
/>
<Icon.Button
style={styles.iconStyle}
backgroundColor="#131415"
name="call-end"
onPress={this.endCall}
/>
<Icon.Button
style={styles.iconStyle}
backgroundColor="#131415"
name={this.state.localPublishVideo ? 'videocam' : 'videocam-off'}
onPress={this.toggleVideo}
/>
</View> 5. Optimiser l'application pour les flux multiples
Si vous êtes arrivé à ce stade, nous avons mis en œuvre la vue de l'appel conjoint, le composant Session et éditeur et la barre d'outils.
Ensuite, nous définirons la vue pour les différents nombres possibles d'abonnés. Après cela, nous ajouterons des fonctionnalités permettant d'optimiser les performances des vidéos dans notre app React Native.
Si nous n'avons aucun utilisateurnous allons afficher un simple texte d'information.
Si nous n'avons qu'un un abonnénous afficherons son flux en mode plein écran.
Enfin, si nous avons plus d'un utilisateurnous afficherons l'abonné principal dans la vue d'ensemble (comme le montre la maquette), et l'autre dans un composant de vue déroulante pour gérer un nombre différent d'abonnés.
Étant donné que le nombre d'abonnés pourrait augmenter et solliciter l'unité centrale de notre appareil et la bande passante du réseau, nous mettrons en œuvre des optimisations pour chacun des abonnés, telles que la réduction de la résolution et la désactivation de la vidéo pour les abonnés qui ne sont pas visibles.
Explorons le composant OTSubscriber pour traiter les cas décrits ci-dessus. Tout d'abord, comme nous voulons avoir le contrôle sur chaque abonné, nous devrions mettre en œuvre une fonction de rendu pour les abonnés (rendu personnalisé des flux).
renderSubscribers = (subscribers) => {
if (this.state.mainSubscriberStreamId) {
subscribers = subscribers.filter(sub => sub !== this.state.mainSubscriberStreamId);
subscribers.unshift(this.state.mainSubscriberStreamId);
}
return subscribers.length > 1 ? (
<>
<View style={styles.mainSubscriberStyle}>
<TouchableOpacity
onPress={() => this.handleSubscriberSelection(subscribers, subscribers[0])}
key={subscribers[0]}>
<OTSubscriberView
streamId={subscribers[0]}
style={{
width: '100%', height: '100%'
}}
/>
</TouchableOpacity>
</View>
<View style={styles.secondarySubscribers}>
<ScrollView
horizontal={true}
decelerationRate={0}
snapToInterval={dimensions.width / 2}
snapToAlignment={'center'}
onScrollEndDrag={(e) => this.handleScrollEnd(e, subscribers.slice(1))}
style={{
width: dimensions.width,
height: dimensions.height / 4,
}}>
{subscribers.slice(1).map((streamId) => (
<TouchableOpacity
onPress={() => this.handleSubscriberSelection(subscribers, streamId)}
style={{
width: dimensions.width / 2,
height: dimensions.height / 4,
}}
key={streamId}
>
<OTSubscriberView
style={{
width: '100%', height: '100%'
}}
key={streamId}
streamId={streamId}
/>
</TouchableOpacity>
))}
</ScrollView>
</View>
</>
) : subscribers.length > 0 ? (
<TouchableOpacity style={styles.fullView}>
<OTSubscriberView streamId={subscribers[0]}
key={subscribers[0]}
style={{ width: '100%', height: '100%' }}
/>
</TouchableOpacity>
) : (<Text>No one connected</Text>)
}; Nous utilisons le rendu conditionnel de React (https://reactjs.org/docs/conditional-rendering.html) pour gérer les différents cas avec zéro, un ou N abonnés.
Tout d'abord, s'il n'y a pas d'abonnés, nous tombons dans le dernier cas, et nous affichons un Text composant.
Deuxièmement, s'il n'y a qu'un seul abonné, nous l'affichons en mode plein écran.
Enfin, le cas le plus intéressant est celui où les abonnés sont plusieurs : Nous avons une vue principale de l'abonné et un composant ScrollView dans lequel nous alimenterons les autres abonnés. La première étape consiste à vérifier si nous disposons d'un composant mainSubscriberStreamId. Si c'est le cas, nous trions le tableau de manière à ce que l'abonné principal soit le premier élément. Les autres abonnés seront affichés dans le tableau ScrollView horizontalement. Le composant ScrollView est idéal pour notre cas d'utilisation, car nous pouvons afficher un nombre relativement élevé d'abonnés sans avoir à modifier la mise en page, et nous pouvons détecter combien d'abonnés se trouvent dans la vue déroulante et combien d'entre eux sont visibles.
Les appels de groupe sur les appareils mobiles peuvent être très difficiles, tant du point de vue du matériel que du réseau. Pour fournir un bon résultat à l'utilisateur final, une application doit mettre en œuvre une liste de bonnes pratiques pour gérer les différents cas d'utilisation et les différentes dispositions. Dans notre cas, nous avons une vue principale des abonnés qui doit avoir la meilleure résolution possible, et le composant Scroll View avec les abonnés restants dans des vignettes plus petites qui pourraient être optimisées en diminuant la résolution reçue. Les SDK Opentok donnent au développeur la possibilité de définir la résolution et la fréquence d'images préférées pour chaque abonné (setPreferredFrameRate et setPreferredResolution).
Nous implémentons la méthode handleSubscriberSelection pour gérer la vue de l'abonné principal et la résolution préférée. La fonction est sur le TouchableOpacity de chacun des abonnés.
const mainSubscribersResolution = { width: 1280, height: 720 };
const secondarySubscribersResolution = { width: 352, height: 288 };
handleSubscriberSelection = (subscribers, streamId) => {
let subscriberToSwap = subscribers.indexOf(streamId);
let currentSubscribers = subscribers;
let temp = currentSubscribers[subscriberToSwap];
currentSubscribers[subscriberToSwap] = currentSubscribers[0];
currentSubscribers[0] = temp;
this.setState(prevState => {
const newStreamProps = { ...prevState.streamProperties };
for (let i = 0; i < currentSubscribers.length; i += 1) {
if (i === 0) {
newStreamProps[currentSubscribers[i]] = { ...prevState.streamProperties[currentSubscribers[i]] }
newStreamProps[currentSubscribers[i]].preferredResolution = mainSubscribersResolution;
} else {
newStreamProps[currentSubscribers[i]] = { ...prevState.streamProperties[currentSubscribers[i]] }
newStreamProps[currentSubscribers[i]].preferredResolution = secondarySubscribersResolution;
}
}
return { mainSubscriberStreamId: streamId, streamProperties: newStreamProps };
})
}En fonction de l'abonné sélectionné, la fonction déplace l'abonné sélectionné en tête du tableau des abonnés. Comme indiqué précédemment, le premier élément du tableau d'abonnés sera affiché dans la vue principale. Ensuite, nous devons mettre à jour le streamProperties du composant OTSubscriber pour définir les différentes résolutions préférées. Nous fixons la résolution maximale (width: 1280, height: 720) pour l'abonné principal et une résolution inférieure pour les autres ({ width: 352, height: 288 }). Si nous voulons également modifier la fréquence d'images préférée, en fonction de la mise en page ou du cas d'utilisation, il nous suffit d'ajouter la propriété preferredFrameRate à l'objet streamProperties sur l'objet
Enfin, nous voulons optimiser le composant ScrollView . Le composant ScrollView peut avoir un grand nombre d'abonnés, mais ne peut en afficher que deux simultanément. Par exemple, si nous avons cinq abonnés, l'un d'entre eux sera affiché dans la vue principale de l'abonné et les quatre autres dans la vue de défilement. Seuls deux d'entre eux sont visibles dans la vue, et les autres ne seront visibles que si l'on fait défiler la page horizontalement.
Le composant ScrollView possède un récepteur d'événements appelé onScrollEndDragqui est appelé lorsque l'utilisateur cesse de faire glisser la vue de défilement et que celle-ci s'arrête ou commence à glisser. Nous pouvons utiliser cet événement pour savoir quels abonnés sont visibles et couper la vidéo des abonnés restants. La mise en sourdine de la vidéo du flux non visible améliorera les performances de l'In-App Video et permettra d'économiser la consommation de l'unité centrale et la bande passante du réseau.
handleScrollEnd = (event, subscribers) => {
let firstVisibleIndex;
if (event && event.nativeEvent && !isNaN(event.nativeEvent.contentOffset.x)) {
firstVisibleIndex = parseInt(event.nativeEvent.contentOffset.x / (dimensions.width / 2), 10);
}
this.setState(prevState => {
const newStreamProps = { ...prevState.streamProperties };
if (firstVisibleIndex !== undefined && !isNaN(firstVisibleIndex)) {
for (let i = 0; i < subscribers.length; i += 1) {
if (i === firstVisibleIndex || i === (firstVisibleIndex + 1)) {
newStreamProps[subscribers[i]] = { ...prevState.streamProperties[subscribers[i]] }
newStreamProps[subscribers[i]].subscribeToVideo = true;
} else {
newStreamProps[subscribers[i]] = { ...prevState.streamProperties[subscribers[i]] }
newStreamProps[subscribers[i]].subscribeToVideo = false;
}
}
}
return { streamProperties: newStreamProps }
})
}
Sur l'événement onScrollEndDrag nous disposons de l'information sur le contentOffset qui est le point auquel l'origine de la vue du contenu est décalée par rapport à l'origine de la vue du défilement. Nous utiliserons cette valeur pour savoir quels flux sont actuellement visibles, en divisant le décalage du contenu par la moitié de la largeur de l'écran (event.nativeEvent.contentOffset.x / (dimensions.width / 2)).
Le résultat sera le premier abonné visible. À ce stade, nous savons que les flux visibles sont le flux en position firstVisibleIndex et firstVisibleIndex + 1. La dernière étape consiste à boucler le tableau des abonnés et à couper la vidéo des abonnés non visibles.
Conclusion
Félicitations ! Vous venez de terminer la construction de votre première appli d'appels vidéo dans React Native.
Ce tutoriel montre à quel point il est facile d'ajouter un chat vidéo aux apps React Native avec l'aide de l'API Video de Vonage. Les appels vidéo devenant de plus en plus populaires, les apps de Video chat comme celle-ci offrent de plus en plus de valeur aux marques et aux entreprises.
Si vous avez des questions ou des commentaires, n'hésitez pas à nous contacter sur sur Twitter ou rejoignez notre canal Slack de la communauté. Vous pouvez accéder au code de ce tutoriel sur GitHub.
Lectures complémentaires
Comment créer une application de Video-App Video avec Python et Flask
Comment créer une application de Video-App Video avec ASP.NET et Angular
Comment ajouter le Video Chat aux applications Next.js
Comment ajouter le Video Chat aux applications Firebase
Comment passer des appels téléphoniques dans les applications Android avec React Native
Partager:
Enrico est un ancien membre de l'équipe Vonage. Il a travaillé en tant qu'ingénieur de solutions, aidant l'équipe de vente avec son expertise technique. Il est passionné par le cloud, les startups et les nouvelles technologies. Il est le cofondateur d'une startup WebRTC en Italie. En dehors du travail, il aime voyager et goûter autant d'aliments bizarres que possible.