https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-video-conference-app-with-react-native-and-vonage-video-api/Blog_React-Native_Opentok_1200x600.png

Erstellen einer Videokonferenz-App mit React-Native und Vonage Video API

Zuletzt aktualisiert am November 7, 2023

Lesedauer: 8 Minuten

Eine Videoanruf-Funktionalität in Ihrer Anwendung scheint heutzutage ein Muss zu sein. Der Aufbau einer nativen Anwendung von Grund auf und die Handhabung der unterschiedlichen Funktionen von Android und iOS kann jedoch ein sehr langer und teurer Prozess sein.

In diesem Tutorial zeigen wir Ihnen, wie Sie eine Video-Chat-App in React Native erstellen können. Um diese App zu erstellen, nutzen wir die Opentok React Native Bibliothek mit der Vonage Video API.

Eine Videoversion dieses Tutorials finden Sie in dem folgenden Video:

Möchten Sie weitermachen? Sie finden den Code für dieses Tutorial auf GitHub.

Voraussetzungen

Um eine React Native-App für Videogespräche zu erstellen, benötigen Sie Folgendes:

Wie man eine React Native Video Call App erstellt (5 Schritte)

Um eine App für Videogespräche mit React Native und der Vonage API zu erstellen, gehen Sie wie folgt vor:

  1. Ihr Projekt einrichten

  2. Erstellen der Video-Chat-Schnittstelle

  3. Hinzufügen der Video-Streams der Teilnehmer

  4. Erstellen der Symbolleiste für Videoanrufe

  5. Optimieren Sie die App für mehrere Streams

1. Richten Sie Ihr Projekt ein

Als Einstieg bauen wir das Framework für die Video-Chat-App in React Native.

Klonen Sie zunächst das folgende Repo: https://github.com/enricop89/multiparty-video-react-native.git

Als nächstes installieren Sie die erforderlichen Knotenmodule: npm install

Für iOS installieren Sie die Abhängigkeiten der Poddatei: cd ios/ && pod install

Die Projektstruktur setzt sich aus den folgenden Hauptdateien zusammen:

  1. Android und iOS Ordner: Hier wird der native Code kompiliert und ausgeführt.

  2. App.js: ist die Hauptdatei, in der der React-Zustand, die Requisiten, Ereignisbehandler und Methoden definiert werden.

  3. config.js: Hier werden die Berechtigungsnachweise definiert.

Die App-Komponente wird unsere Landing View sein, wenn unsere App gestartet wird. Sie wird eine primäre bedingte Renderfunktion haben, um den Videoanruf zu starten.

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

Die joinCall Eigenschaft des React-Zustands wird je nach Wert verschiedene Ansichten auslösen. Wenn die App gestartet wird, wird dem Benutzer eine einfache Ansicht mit einer Schaltfläche "Join the Call" angezeigt. Die Schaltfläche löst eine Zustandsänderung aus und schaltet den joinCall Wert, um die komplexere Video-Anruf-Ansicht anzuzeigen.

2. Erstellen Sie die Video-Chat-Schnittstelle

Nachdem wir nun ein Gerüst für unser Projekt erstellt haben, werden wir die Schnittstelle für die Anwendung selbst erstellen. Wir bezeichnen diese Schnittstelle als die Video-Anruf-Ansicht.

Bevor Sie sich mit der Video-Call-Ansicht befassen, sollten Sie sich etwas Zeit nehmen, um die opentok-react-native Bibliothek.

Die Bibliothek besteht aus drei Hauptkomponenten: OTSession, OTPublisher und OTSubscriber. Jede dieser Komponenten interagiert mit der nativen Schicht (iOS und Android) und ruft die nativen Methoden zum Verbinden, Veröffentlichen und Abonnieren auf.

Wir müssen auch auf die Ereignisse hören, die von diesen Komponenten ausgelöst werden, insbesondere auf die Sitzungsereignisse, wie sessionConnected, sessionDisconnected, streamCreated und streamDestroyed (Dokumentation: Session-Ereignisse).

Die Video Call View besteht aus den folgenden Komponenten:

  • Herausgeber. In der Publisher-Ansicht wird der Videostream des Hauptbenutzers angezeigt.

  • Abonnenten. In den Teilnehmeransichten werden die Videostreams der anderen Teilnehmer angezeigt.

  • Symbolleiste. Die Symbolleiste enthält Schaltflächen für den Zugriff auf das Mikrofon, die Kamera und das Beenden des Anrufs.

An example of the multiparty video view being built in this tutorialAn example of the multiparty video view being built in this tutorial

3. Hinzufügen der Teilnehmerströme

Als Nächstes fügen wir Video-Streams von Chat-Teilnehmern (den Abonnenten und dem Herausgeber) hinzu.

Um der Schnittstelle Videostreams hinzuzufügen, müssen wir den Überblick über die Streams der Abonnenten, den primären Abonnenten und den Veröffentlichungsstatus des lokalen Mikrofons und der Kamera behalten. Der perfekte Ort, um diese Informationen zu speichern, ist der 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 Array speichert die Abonnenten innerhalb einer Sitzung. Jedes Mal wenn wir eine streamCreated Ereignis erhalten, bedeutet dies, dass jemand der Sitzung beigetreten ist und einen Stream veröffentlicht hat, also müssen wir seine streamId in das subscriberIds Array hinzufügen.

Andererseits, wenn wir das Ereignis streamDestroyed Ereignis empfangen, müssen wir die streamId aus dem Array der Abonnenten entfernen.

    <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>
    

In der Renderfunktion der Videoansicht müssen wir Folgendes hinzufügen OTSession, OTPublisher und OTSubscriber aus der opentok-react-native Bibliothek. In der OTSession setzen wir die Credentials und die eventHandler-Funktion als Requisiten der Komponente.

4. Erstellen Sie die Symbolleiste für Videoanrufe

Da die Videostreams nun funktionieren, aktivieren wir die Funktionen in der Symbolleiste für Videoanrufe.

Die Komponente OTPublisher Komponente initialisiert einen Verleger und veröffentlicht das Bild nach dem Einhängen in der angegebenen Sitzung. Es ist möglich, verschiedene Eigenschaften wie Kameraposition, Auflösung und andere anzugeben (Liste der Verlegeroptionen: Verleger-Optionen). In dieser Beispiel-App werden wir nur this.publisherProperties = { cameraPosition: 'front'};.

Stellen Sie sicher, dass Sie sowohl die Kamera- als auch die Mikrofonverwendung aktiviert haben, indem Sie die folgenden Einträge in Ihre Info.plist Datei (iOS-Projekt) hinzufügen:

<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>

Alternativ können Sie für Android Folgendes hinzufügen (neuere Versionen von Android-API Level 23 (Android 6.0) - haben ein anderes Berechtigungsmodell, das bereits von dieser Bibliothek behandelt wird):

    <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" />

Die Website OTPublisher Komponente hat auch eine streamProperty Eigenschaft, die die Publisher-Eigenschaften behandelt, die an die native Instanz übergeben werden. Mit Hilfe des React State können wir Änderungen an der Publisher-Instanz durch Aktualisierung der this.publisherProperties Variable auslösen. Wir verwenden diesen Ansatz, um die Symbolleiste mit den Funktionen zum Stummschalten und Aufheben der Stummschaltung für das Mikrofon und die Kamera zu implementieren. Die Implementierung der Funktion ist einfach; sie schaltet die publishAudio oder publishVideo Wert auf den this.publisherProperties und den localPublishAudio und localPublishVideo um das Schaltflächensymbol je nach Wert anzupassen.

Die Schaltfläche "Anruf beenden" hat einen sehr ähnlichen Ansatz. Die Funktion endCall Funktion schaltet den joinCall Wert des Status und setzt die Ansicht auf den Ausgangswert zurück.

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. Optimieren Sie die App für mehrere Streams

Wenn Sie diesen Punkt erreicht haben, haben wir die Join Call View, die Session- und Publisher-Komponente und die Toolbar implementiert.

Als Nächstes werden wir die Ansicht für die verschiedenen möglichen Abonnentenzahlen definieren. Danach fügen wir Funktionen hinzu, um die Videoleistung in unserer React Native-App zu optimieren.

Wenn wir keine Benutzerhaben, wird ein einfacher Informationstext angezeigt.

Wenn wir nur einen einen Abonnentenhaben, wird ihr Stream im Vollbildmodus angezeigt.

Schließlich, wenn wir mehr als einen Benutzerhaben, zeigen wir den Hauptabonnenten in der großen Ansicht (wie im Mock-up gezeigt) und den anderen in einer Scroll View Komponente, um eine unterschiedliche Anzahl von Abonnenten zu behandeln.

Da die Anzahl der Teilnehmer wachsen könnte und eine Herausforderung für unsere Geräte-CPU und die Netzwerkbandbreite darstellt, werden wir Optimierungen für jeden einzelnen Teilnehmer durchführen, wie z. B. die Verringerung der Auflösung und die Deaktivierung des Videos für die Teilnehmer, die nicht sichtbar sind.

Lassen Sie uns die OTSubscriber Komponente, um die oben beschriebenen Fälle zu behandeln. Da wir die Kontrolle über jeden Abonnenten haben wollen, müssen wir zunächst eine Renderfunktion für die Abonnenten implementieren (custom-rendering-of-streams).

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

Wir verwenden das bedingte Rendering in React (https://reactjs.org/docs/conditional-rendering.html), um die verschiedenen Fälle mit null, einem oder N Abonnenten zu behandeln.

Erstens, wenn es keine Abonnenten gibt, tritt der letzte Fall ein, und wir zeigen eine Text Komponente.

Zweitens, wenn es einen Abonnenten gibt, zeigen wir den Abonnenten im Vollbildmodus an.

Der interessanteste Fall ist schließlich, wenn es mehr als einen Abonnenten gibt: Wir haben eine Hauptabonnentenansicht und eine ScrollView Komponente, in die wir die anderen Abonnenten einspeisen werden. Der erste Schritt ist zu prüfen, ob wir eine mainSubscriberStreamId. Wenn ja, wird das Array so sortiert, dass der Hauptabonnent das erste Element ist. Die übrigen Abonnenten werden in der ScrollView horizontal angezeigt. Die ScrollView Komponente ist ideal für unseren Anwendungsfall, da wir eine relativ große Anzahl von Abonnenten anzeigen können, ohne das Layout ändern zu müssen, und wir können erkennen, wie viele Abonnenten sich in der Scroll-Ansicht befinden und wie viele von ihnen sichtbar sind.

Gruppenanrufe auf mobilen Geräten können sowohl aus Sicht der Hardware als auch des Netzwerks eine große Herausforderung darstellen. Um dem Endbenutzer ein gutes Ergebnis zu liefern, sollte eine App eine Liste von Best Practices implementieren, um verschiedene Anwendungsfälle und Layouts zu behandeln. In unserem Fall haben wir eine Hauptteilnehmeransicht, die die bestmögliche Auflösung haben muss, und die Scroll View-Komponente mit den übrigen Teilnehmern in kleineren Miniaturansichten, die durch eine Verringerung der empfangenen Auflösung optimiert werden könnten. Die Opentok SDKs geben dem Entwickler die Möglichkeit, die bevorzugte Auflösung und Bildrate für jeden einzelnen Teilnehmer einzustellen (setPreferredFrameRate und setPreferredResolution).

Wir implementieren die handleSubscriberSelection Methode, um die mainSubscriber View und die bevorzugte Auflösung zu behandeln. Die Funktion ist auf der TouchableOpacity übergeordneten Komponente der einzelnen Abonnenten.

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

Je nach ausgewähltem Abonnenten verschiebt die Funktion den ausgewählten Abonnenten an den Anfang des Abonnenten-Arrays. Wie bereits erwähnt, wird das erste Element im Abonnenten-Array in der Hauptansicht angezeigt. Danach müssen wir die streamProperties der OTSubscriber Komponente aktualisieren, um die verschiedenen bevorzugten Auflösungen einzustellen. Wir setzen die maximale Auflösung (width: 1280, height: 720) für den primären Abonnenten und eine niedrigere Auflösung für die anderen ({ width: 352, height: 288 }). Wenn wir auch die bevorzugte Bildrate je nach Layout oder Anwendungsfall ändern wollen, müssen wir nur die preferredFrameRate Eigenschaft auf das streamProperties Objekt hinzufügen.

Schließlich wollen wir die ScrollView Komponente optimieren. Die ScrollView-Komponente könnte eine große Anzahl von Abonnenten haben, kann aber nur zwei gleichzeitig anzeigen. Wenn wir z. B. fünf Abonnenten haben, befindet sich einer in der Hauptansicht des Abonnenten, die restlichen vier befinden sich in der ScrollView. Nur zwei von ihnen sind in der Ansicht sichtbar, und die übrigen werden nur sichtbar, wenn wir horizontal scrollen.

Die ScrollView-Komponente hat einen Ereignis-Listener namens onScrollEndDragder aufgerufen wird, wenn der Benutzer aufhört, die Bildlaufansicht zu ziehen, und diese entweder anhält oder zu gleiten beginnt. Anhand dieses Ereignisses können wir feststellen, welche Abonnenten sichtbar sind, und das Video der übrigen stummschalten. Das Stummschalten des Videos der nicht sichtbaren Streams verbessert die Leistung der App und spart CPU-Verbrauch und Netzwerkbandbreite.

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

Über das onScrollEndDrag Ereignis haben wir die Information über den contentOffset Koordinaten, d. h. den Punkt, an dem der Ursprung der Inhaltsansicht gegenüber dem Ursprung der Bildlaufansicht versetzt ist. Wir werden diesen Wert verwenden, um zu verstehen, welche Streams derzeit sichtbar sind, indem wir den Content-Offset durch die halbe Breite des Bildschirms teilen (event.nativeEvent.contentOffset.x / (dimensions.width / 2)).

Das Ergebnis ist der erste sichtbare Teilnehmer. Zu diesem Zeitpunkt wissen wir, dass die sichtbaren Streams die Streams an der Position firstVisibleIndex und firstVisibleIndex + 1. Der letzte Schritt besteht darin, das Array der Abonnenten in einer Schleife laufen zu lassen und das Video der nicht sichtbaren Abonnenten stumm zu schalten.

Schlussfolgerung

Herzlichen Glückwunsch! Sie haben soeben Ihre erste Videogesprächs-App in React Native erstellt.

Dieses Tutorial zeigt, wie einfach es ist, mit Hilfe der Vonage Video API Videochats zu React Native Apps hinzuzufügen. Da Videogespräche immer beliebter werden, bieten Videochat-Apps wie diese einen immer größeren Wert für Marken und Unternehmen.

Wenn Sie Fragen oder Kommentare haben, wenden Sie sich bitte an uns auf Twitter oder treten Sie unserem Slack-Kanal der Gemeinschaft. Sie können auf den Code für dieses Tutorial auf GitHub.

Weitere Lektüre

Wie man eine Video-Chat-App mit Python und Flask erstellt

Wie man eine Video-Chat-App mit ASP.NET und Angular erstellt

Hinzufügen von Video-Chat zu Next.js-Apps

Hinzufügen von Video-Chat zu Firebase-Apps

Wie man in Android-Apps mit React Native telefoniert

Teilen Sie:

https://a.storyblok.com/f/270183/400x266/5bd495df3c/enrico-portolan.png
Enrico PortolanGastautor

Enrico ist ein ehemaliges Mitglied des Vonage-Teams. Er arbeitete als Solutions Engineer und unterstützte das Vertriebsteam mit seinem technischen Fachwissen. Er begeistert sich für die Cloud, Startups und neue Technologien. Er ist der Mitbegründer eines WebRTC-Startups in Italien. Außerhalb der Arbeit reist er gerne und probiert so viele verrückte Gerichte wie möglich.