iPhone
 Computer >> コンピューター >  >> スマートフォン >> iPhone

React Nativeアプリにビデオ通話機能を追加する完全ガイド

COVID-19パンデミックの影響で、ビデオ通話は日常に欠かせないコミュニケーション手段となりました。チャットアプリ、音声通話、ビデオ通話などの機能を活用することで、私たちは友人や家族とつながり続けることができています。

そこで今回は、ビデオ通話ができるReact Nativeアプリを自分で作ってみましょう。

このチュートリアルでは、TwilioのプログラマブルビデオAPIを使用して、React Nativeアプリにビデオ通話機能を実装する方法を学びます。

手順は非常にシンプルです。ビデオ会議用のルームを作成し、他のユーザーをそのルームに招待するだけです。そのためにはカメラとマイクへのアクセス権限が必要になります。したがって、テストには実機のスマートフォンを使用する必要があります。

Twilio APIへのアクセスに使用する主なパッケージは、react-native-twilio-video-webrtcです。

必要なもの

  • Twilioアカウント
  • テスト用にiOSまたはAndroid端末を2台以上
  • React Nativeの開発環境

それでは始めましょう!

Twilio APIキーの取得方法

Twilio APIキーを取得するには、Twilioアカウントが必要です。まず、Twilioの公式サイトにアクセスしてアカウントを作成してください。アカウント設定が完了したら、下のスクリーンショットが示す場所に移動します。

React Nativeアプリにビデオ通話機能を追加する完全ガイド

アクセストークンを取得するサーバーの構築方法

アクセストークンを取得するために、新しいNodeサーバープロジェクトを作成します。まず、以下のコマンドを実行して必要なパッケージをインストールしましょう。

yarn add dotenv express ngrok nodemon twilio

次に、以下のコードスニペットのように、Twilioの認証情報を環境変数ファイル(.env)に追加します。

PORT=3000
ACCOUNT_SID=AC5ceb0847c50c91b143ce07
API_KEY_SID=SKa173c10de99a26fd86969b
API_KEY_SECRET=Czv7IjNIZJis8s7jb5FePi

続いて、APIエンドポイントを作成します。まず、必要なパッケージをインポートし、アクセストークンを取得するためのオブジェクトインスタンスを作成します。

import 'dotenv/config';
import express from 'express';

import twilio from 'twilio';
import ngrok from 'ngrok';
const AccessToken = twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;

const app = express();

ここでは、アクセストークンを取得するためのAPIエンドポイントを作成します。Expressインスタンスが提供するgetメソッドを使用して、アクセストークンをレスポンスとして返すエンドポイント関数を作成します。

関数内では、Twilioの認証情報を使って新しいインスタンスを作成します。その後、モバイル端末の登録画面から受け取ったユーザー名を、identity属性として追加します。

最後に、ユーザーにビデオ機能へのアクセス権限を付与し、JWTトークンを端末に返します。これらすべてを実装したコードが以下のスニペットです。

app.get('/getToken', (req, res) => {
  if (!req.query || !req.query.userName) {
    return res.status(400).send('Username parameter is required');
  }
  const accessToken = new AccessToken(
    process.env.ACCOUNT_SID,
    process.env.API_KEY_SID,
    process.env.API_KEY_SECRET,
  );

  // トークンのIdentityを設定
  accessToken.identity = req.query.userName;

  // Videoへのアクセス権限を付与
  var grant = new VideoGrant();
  accessToken.addGrant(grant);

  // トークンをJWTとしてシリアライズ
  var jwt = accessToken.toJwt();
  return res.send(jwt);
});

さらに、作成したエンドポイントAPIをインターネットに公開して、簡単にアクセスできるようにします。そのために、以下のコードスニペットを使用します。

app.listen(process.env.PORT, () =>
  console.log(`Server listening on port ${process.env.PORT}!`),
);

ngrok.connect(process.env.PORT).then((url) => {
  console.log(`Server forwarded to public url ${url}`);
});

最後に、以下のスクリーンショットのようにサーバーを実行します。

React Nativeアプリにビデオ通話機能を追加する完全ガイド

これで、アクセストークンを返すAPIエンドポイントの作成が完了しました。

React Nativeプロジェクトの設定方法

React Nativeプロジェクトでは、パッケージの手動セットアップに加えて、AndroidとiOSの両方のプラットフォームでカメラとマイクへのアクセス権限を設定する必要があります。

まず、プロジェクトのターミナルで以下のコマンドを実行して、react-navigationreact-native-twilio-video-webrtcという必要なパッケージをインストールします。

yarn add @react-navigation/native @react-navigation/stack react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view react-native-dotenv react-native-permissions <https://github.com/blackuy/react-native-twilio-video-webrtc>

iOSのセットアップ

iOSの場合は、パッケージを手動でセットアップする必要があります。まず、PodfileでiOSターゲットを11に引き上げます。これは、TwilioのネイティブVideo SDKがiOS 11.0以降しかサポートしていないためです。

platform :ios, '11.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

Podfileで、以下のコードスニペットに従ってパーミッションリクエストを設定します。

permissions_path = '../node_modules/react-native-permissions/ios'
  pod 'Permission-Camera', :path => "#{permissions_path}/Camera.podspec"
  pod 'Permission-Microphone', :path => "#{permissions_path}/Microphone.podspec"

次に、info.plistを開き、以下のコードスニペットのように、カメラとマイクへのアクセス許可をリクエストするコードを追加します。

  <key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
  <key>NSCameraUsageDescription</key>
  <string>We require your permission to access the camera while in a video call</string>
  <key>NSMicrophoneUsageDescription</key>
  <string>We require your permission to access the microphone while in a video call</string>

これでiOSのセットアップは完了です。

Androidのセットアップ

まず最初に、./android/settings.gradleファイルに以下のコード行を追加します。

project(':react-native-twilio-video-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-twilio-video-webrtc/android')

また、./android/app/build.gradleファイルにパッケージ実装のコードを追加します。

implementation project(':react-native-twilio-video-webrtc')

最後に、MainApplication.javaファイルにもこれをインポートします。

import com.twiliorn.library.TwilioPackage;

そして、以下のコードでパッケージを有効化します。

@Override
protected List getPackages() {
  @SuppressWarnings("UnnecessaryLocalVariable")
  List packages = new PackageList(this).getPackages();
  // 以下のコードを追加
  packages.add(new TwilioPackage());
  return packages;
}

ルーム登録画面の作成方法

ここでは、「ルーム登録(Register Room)」画面を作成します。この画面により、ビデオ通話React Nativeアプリのルームに登録できるようになります。

まず、以下のコードスニペットのように必要なパッケージをインポートします。

import React, {useState, useRef, useEffect, useContext} from 'react';
import {
  StyleSheet,
  View,
  Text,
  StatusBar,
  TouchableOpacity,
  TextInput,
  Alert,
  KeyboardAvoidingView,
  Platform,
  ScrollView,
  Dimensions,
} from 'react-native';

import {
  TwilioVideoLocalView,
  TwilioVideoParticipantView,
  TwilioVideo,
} from 'react-native-twilio-video-webrtc';

import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
  • react-navigation:登録画面とビデオ通話画面間のナビゲーションを処理します。
  • react-native:カメラとマイクへのアクセス権限を処理します。
  • react-native-twilio-video-webrtc:Twilioのプログラマブルビデオ通話APIへのアクセスを可能にします。

インスタンスと変数の初期化方法

まず、react-navigationのインスタンスを作成します。次に、状態を各画面に配布するための状態およびコンテキスト変数を初期化します。

const Stack = createStackNavigator();
const initialState = {
  isAudioEnabled: true,
  status: 'disconnected',
  participants: new Map(),
  videoTracks: new Map(),
  userName: '',
  roomName: '',
  token: '',
};

const AppContext = React.createContext(initialState);

const dimensions = Dimensions.get('window');

ナビゲーションのブートストラップ

App.jsファイルで、ナビゲーションコンテナのスタックを作成します。Stackコンポーネントを使用して、コンテキスト経由ですべての画面に状態を配布します。

export default () => {
  const [props, setProps] = useState(initialState);

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <AppContext.Provider value={{props, setProps}}>
        <NavigationContainer>
          <Stack.Navigator>
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Video Call" component={VideoCallScreen} />
          </Stack.Navigator>
        </NavigationContainer>
      </AppContext.Provider>
    </>
  );
};

登録画面の作成方法

登録画面には、ユーザーの認証情報を入力するモーダルダイアログが表示され、ユーザーがビデオ通話ルームに参加できるようになります。

まず、以下のコードスニペットのように、コンテキストからpropsをregister.jsファイルに取得します。

import React, {useState, useRef, useEffect, useContext} from 'react';
import {
  checkMultiple,
  request,
  requestMultiple,
  PERMISSIONS,
  RESULTS,
} from 'react-native-permissions';

const RegisterScreen = ({navigation}) => {
  const {props, setProps} = useContext(AppContext);

次に、カメラとマイクのパーミッションを処理する関数を作成します。その関数のコードは以下のスニペットのとおりです。

const _checkPermissions = (callback) => {
    const iosPermissions = [PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.MICROPHONE];
    const androidPermissions = [
      PERMISSIONS.ANDROID.CAMERA,
      PERMISSIONS.ANDROID.RECORD_AUDIO,
    ];
    checkMultiple(
      Platform.OS === 'ios' ? iosPermissions : androidPermissions,
    ).then((statuses) => {
      const [CAMERA, AUDIO] =
        Platform.OS === 'ios' ? iosPermissions : androidPermissions;
      if (
        statuses[CAMERA] === RESULTS.UNAVAILABLE ||
        statuses[AUDIO] === RESULTS.UNAVAILABLE
      ) {
        Alert.alert(
          'Error',
          'Hardware to support video calls is not available',
        );
      } else if (
        statuses[CAMERA] === RESULTS.BLOCKED ||
        statuses[AUDIO] === RESULTS.BLOCKED
      ) {
        Alert.alert(
          'Error',
          'Permission to access hardware was blocked, please grant manually',
        );
      } else {
        if (
          statuses[CAMERA] === RESULTS.DENIED &&
          statuses[AUDIO] === RESULTS.DENIED
        ) {
          requestMultiple(
            Platform.OS === 'ios' ? iosPermissions : androidPermissions,
          ).then((newStatuses) => {
            if (
              newStatuses[CAMERA] === RESULTS.GRANTED &&
              newStatuses[AUDIO] === RESULTS.GRANTED
            ) {
              callback && callback();
            } else {
              Alert.alert('Error', 'One of the permissions was not granted');
            }
          });
        } else if (
          statuses[CAMERA] === RESULTS.DENIED ||
          statuses[AUDIO] === RESULTS.DENIED
        ) {
          request(statuses[CAMERA] === RESULTS.DENIED ? CAMERA : AUDIO).then(
            (result) => {
              if (result === RESULTS.GRANTED) {
                callback && callback();
              } else {
                Alert.alert('Error', 'Permission not granted');
              }
            },
          );
        } else if (
          statuses[CAMERA] === RESULTS.GRANTED ||
          statuses[AUDIO] === RESULTS.GRANTED
        ) {
          callback && callback();
        }
      }
    });
  };

次に、アプリが起動するたびにこのパーミッションチェック関数を呼び出す必要があります。そのために、以下のコードスニペットのようにuseEffectフック内で関数を呼び出します。

useEffect(() => {
    _checkPermissions();
  }, []);

最後に、ルーム名とユーザー名を受け付ける2つの入力フィールドを持つシンプルなフォームを作成します。そして、その入力値をサーバーに送信してTwilio APIに登録します。コードは以下のスニペットのとおりです。

return (
    <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      style={styles.container}>
      <ScrollView contentContainerStyle={styles.container}>
        <View style={styles.form}>
          <View style={styles.formGroup}>
            <Text style={styles.text}>User Name</Text>
            <TextInput
              style={styles.textInput}
              autoCapitalize="none"
              value={props.userName}
              onChangeText={(text) => setProps({...props, userName: text})}
            />
          </View>
          <View style={styles.formGroup}>
            <Text style={styles.text}>Room Name</Text>
            <TextInput
              style={styles.textInput}
              autoCapitalize="none"
              value={props.roomName}
              onChangeText={(text) => setProps({...props, roomName: text})}
            />
          </View>
          <View style={styles.formGroup}>
            <TouchableOpacity
              disabled={false}
              style={styles.button}
              onPress={() => {
                _checkPermissions(() => {
                  fetch(`https://ae7a722dc260.ngrok.io/getToken?userName=${props.userName}`)
                    .then((response) => {
                      if (response.ok) {
                        response.text().then((jwt) => {
                          setProps({...props, token: jwt});
                          navigation.navigate('Video Call');
                          return true;
                        });
                      } else {
                        response.text().then((error) => {
                          Alert.alert(error);
                        });
                      }
                    })
                    .catch((error) => {
                      console.log('error', error);
                      Alert.alert('API not available');
                    });
                });
              }}>
              <Text style={styles.buttonText}>Connect to Video Call</Text>
            </TouchableOpacity>
          </View>
        </View>
      </ScrollView>
    </KeyboardAvoidingView>
  );

実行すると、以下のエミュレーターのスクリーンショットのような結果が得られます。

React Nativeアプリにビデオ通話機能を追加する完全ガイド

ここでは、ルーム名とユーザー名を入力して、サーバー上で稼働しているTwilio APIに登録するためのモーダルフォームを備えたルーム登録画面が確認できます。

ビデオ通話画面の作成方法

ビデオ通話画面には、2つのウィンドウを用意します。1つは自分のカメラの映像を表示するためのもので、もう1つは相手のカメラの映像を表示するためのものです。

まず、状態を受け取るためにコンテキストを初期化します。次に、useRefフックを使用して参照変数を作成し、状態にアクセスできるようにします。

const VideoCallScreen = ({navigation}) => {
  const twilioVideo = useRef(null);
  const {props, setProps} = useContext(AppContext);

次に、以下のコードスニペットのように、twilioVideoオブジェクトのconnectメソッドを使用して、ルーム名とアクセストークンを指定して接続を初期化します。

useEffect(() => {
    twilioVideo.current.connect({
      roomName: props.roomName,
      accessToken: props.token,
    });
    setProps({...props, status: 'connecting'});
    return () => {
      _onEndButtonPress();
    };
  }, []);

続いて、ビデオ通話画面のメインのテンプレートを作成します。ここでは、条件付きレンダリングを使用して、接続が確立されストリーミングが開始された場合にのみ、参加者のカメラ映像を表示します。全体のコードは以下のスニペットのとおりです。

{(props.status === 'connected' || props.status === 'connecting') && (
        <View style={styles.callWrapper}>
          {props.status === 'connected' && (
            <View style={styles.grid}>
              {Array.from(props.videoTracks, ([trackSid, trackIdentifier]) => (
                <TwilioVideoParticipantView
                  style={styles.remoteVideo}
                  key={trackSid}
                  trackIdentifier={trackIdentifier}
                />
              ))}
            </View>
          )}
        </View>
      )}

次に、通話の終了、ミュート、前面/背面カメラの切り替えといったビデオ通話中の機能を制御する関数を作成します。必要な関数の実装コードは以下のスニペットのとおりです。

const _onEndButtonPress = () => {
    twilioVideo.current.disconnect();
    setProps(initialState);
  };

  const _onMuteButtonPress = () => {
    twilioVideo.current
      .setLocalAudioEnabled(!props.isAudioEnabled)
      .then((isEnabled) => setProps({...props, isAudioEnabled: isEnabled}));
  };

  const _onFlipButtonPress = () => {
    twilioVideo.current.flipCamera();
  };

ここでは、twilioVideoインスタンスが提供するdisconnectsetLocalAudioEnabledflipCameraメソッドを使用して、必要なビデオ通話機能を呼び出しています。

次に、これらの関数を呼び出すボタンをレンダリングします。そのために、以下のコードスニペットを使用します。

       <View style={styles.optionsContainer}>
        <TouchableOpacity style={styles.button} onPress={_onEndButtonPress}>
          <Text style={styles.buttonText}>End</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={_onMuteButtonPress}>
          <Text style={styles.buttonText}>
            {props.isAudioEnabled ? 'Mute' : 'Unmute'}
          </Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={_onFlipButtonPress}>
          <Text style={styles.buttonText}>Flip</Text>
        </TouchableOpacity>
      </View>

最後のステップは、すべてのビデオ通話イベントを処理・監視するように設定されたTwilioVideoコンポーネントを追加することです。設定済みのTwilioVideoコンポーネントの全体コードは以下のスニペットのとおりです。

      <TwilioVideo
        ref={twilioVideo}
        onRoomDidConnect={() => {
          setProps({...props, status: 'connected'});
        }}
        onRoomDidDisconnect={() => {
          setProps({...props, status: 'disconnected'});
          navigation.goBack();
        }}
        onRoomDidFailToConnect={(error) => {
          Alert.alert('Error', error.error);
          setProps({...props, status: 'disconnected'});
          navigation.goBack();
        }}
        onParticipantAddedVideoTrack={({participant, track}) => {
          if (track.enabled) {
            setProps({
              ...props,
              videoTracks: new Map([
                ...props.videoTracks,
                [
                  track.trackSid,
                  {
                    participantSid: participant.sid,
                    videoTrackSid: track.trackSid,
                  },
                ],
              ]),
            });
          }
        }}
        onParticipantRemovedVideoTrack={({track}) => {
          const videoTracks = props.videoTracks;
          videoTracks.delete(track.trackSid);
          setProps({...props, videoTracks});
        }}
      />

ルーム内のユーザー間で正しく接続を確立できれば、以下のような結果が得られます。

React Nativeアプリにビデオ通話機能を追加する完全ガイド

上のスクリーンショットは、同じルーム内の2人の参加者間で行われているビデオ通話の様子です。

これで、React Nativeアプリへのビデオ通話機能の実装が完了しました。

まとめ

このチュートリアルでは、Twilioのプログラマブルビデオ通話APIを使用して、React Nativeアプリにビデオ通話機能をセットアップする方法を、初心者向けの学習リソースとして解説しました。

React Native側の実装だけでなく、別のNodeサーバープロジェクトにおけるAPI実装全体についても取り上げました。

次のステップとしては、匿名通話の開始や複数参加者対応のビデオ通話ルームといった高度な機能の追加に挑戦してみるとよいでしょう。

機能のアイデアや本格的なビデオ通話アプリについては、強力な機能を備えたビデオチャットアプリを提供しているinstamobile.ioをチェックしてみてください。

それではまた次回お会いしましょう。ハッピーコーディング!


  1. React Nativeで動画を背景に使う方法をわかりやすく解説

    この記事では、React Nativeで背景動画(backgroundVideo)を作成する方法を解説します。React Nativeを始めたばかりの方は、まず「React Nativeでモバイルアプリ開発を始めるために知っておくべきこと」といった入門記事を読んでおくと理解がスムーズになります。背景動画はアプリのUIに洗練された印象を与える演出手段です。広告の表示やユーザーへのメッセージ伝達など、さまざまな用途に活用できます。今回はPeletonアプリのホーム画面のようなデザインを目標に、実装方法を見ていきましょう。前提条件実装を始める前に、以下の環境が整っている必要があります。react-n

  2. iOS 11の写真アプリで「人」を追加・削除する方法

    iOS 10では、写真に写った人物の顔を自動認識し、人物ごとに写真を整理できる革新的な機能が導入されました。これにより、大切な人の写真を見たいときに、膨大な写真をめくる必要はなくなり、その人のサムネイルをタップするだけで関連する写真を一覧できるようになりました。しかし、iOS 11では数多くの変更や改良が加えられ、その一部は従来の使い勝手からの変化を感じさせるものでした。そのひとつが、「人」のサムネイルを追加するオプションへの直接アクセスができなくなった点です。そこで本記事では、iOS 11を搭載したiPhoneの写真アプリで、人物を追加・削除する具体的な手順をご紹介します。写真アプリに人物を